From 111593bc38d7e25c6a130d0ca4ea30aef7ac1825 Mon Sep 17 00:00:00 2001 From: Jonatan Date: Sat, 17 Oct 2026 12:48:02 +0200 Subject: [PATCH 01/53] Fix OCI management after changing Proxmox tags --- .github/workflows/test-oci-registries.yml | 6 + oci/remote/install_oci.sh | 11 +- oci/remote/oci_instance_transaction.py | 16 +- oci/tests/test_external_tags.py | 189 ++++++++++++++++++++++ 4 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 oci/tests/test_external_tags.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..235bb0c1 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -32,3 +32,9 @@ jobs: env: PYTHONDONTWRITEBYTECODE: '1' run: python3 -m unittest discover -s oci/tests -p 'test_registry_*.py' -v + - name: Run external OCI settings regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: | + python3 -m unittest discover -s oci/tests -p 'test_external_tags.py' -v + python3 -m unittest discover -s oci/tests -p 'test_instance_reconcile.py' -v diff --git a/oci/remote/install_oci.sh b/oci/remote/install_oci.sh index 7d1d8e85..3b549001 100755 --- a/oci/remote/install_oci.sh +++ b/oci/remote/install_oci.sh @@ -1025,15 +1025,18 @@ apply_extra_hosts() { } # Settings an update gives back to the rebuilt container before it starts: -# the LAN leg of a suite or stack member and its start order. +# the LAN leg of a suite or stack member, its start order and current tags. apply_kept_settings() { local key value while IFS=$'\t' read -r key value; do [[ -n $key ]] || continue - [[ $key == net1 || $key == startup ]] \ + [[ $key == net1 || $key == startup || $key == tags ]] \ || die "$(translate "Unsupported kept setting:") $key" - oci_quiet pct set "$VMID" "--$key" "$value" \ - || die "$(translate "Could not restore the container setting:") $key" + if [[ $key == tags && -z $value ]]; then + oci_quiet pct set "$VMID" --delete tags + else + oci_quiet pct set "$VMID" "--$key" "$value" + fi || die "$(translate "Could not restore the container setting:") $key" done < <(jq -r '.kept_proxmox_settings // {} | to_entries[] | [.key, .value] | @tsv' "$DEPLOYMENT_FILE") } diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index 59579967..cdde917b 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -415,10 +415,9 @@ def candidate_contract(record, operation, proposal=None): return with_default_healthcheck(result) -# Resource settings edited in Proxmox that the new container keeps. # Proxmox settings the rebuilt container gets back exactly as they are: the -# LAN leg ProxMenux adds to a suite or stack member, and the start order. -KEPT_AS_IS = ('net1', 'startup') +# LAN leg ProxMenux adds to a suite or stack member, the start order and tags. +KEPT_AS_IS = ('net1', 'startup', 'tags') ADOPTABLE = {'memory': ('resources', 'memory_mb'), 'swap': ('resources', 'swap_mb'), 'cores': ('resources', 'cores'), 'cpulimit': ('resources', 'cores'), 'cpuunits': ('resources', 'cpu_units'), 'onboot': (None, 'onboot')} @@ -1051,6 +1050,9 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i 'original_gpu_devices': original_gpu, 'desired_gpu_devices': desired_gpu, 'was_running': run('pct', 'status', str(vmid)).strip() == b'status: running'} state['kept_config'] = {key: cfg[key] for key in KEPT_AS_IS if key in cfg} + # The installer adds default tags. Preserve an explicit absence too, so + # deleting all tags in Proxmox does not bring them back on the next update. + state['kept_config']['tags'] = cfg.get('tags', '') if not coordinated and state['kept_config']: # Applied by the installer before the new container starts, so the # application has its LAN leg from the first second. @@ -1058,9 +1060,10 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i if coordinated: state['coordinated'] = coordinated state['preserved_stack_config'] = {key: cfg[key] for key in ('net1', 'startup', 'hookscript') if key in cfg} + state['preserved_stack_config']['tags'] = cfg.get('tags', '') if operation == 'update': state['preserved_stack_config'].update({key: cfg[key] for key in - ('net0', 'features', 'cmode', 'console', 'tty', 'cpuunits', 'tags') if key in cfg}) + ('net0', 'features', 'cmode', 'console', 'tty', 'cpuunits') if key in cfg}) checkpoint(journal, state, 'prepared') pending = copy.deepcopy(record) pending.update(status='updating', pending_transaction=str(journal), transaction_id=state['id']) @@ -1141,7 +1144,10 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i restore_firewall(vmid, state) if coordinated: for key, value in state['preserved_stack_config'].items(): - run('pct', 'set', str(vmid), '--' + key, value) + if key == 'tags' and not value: + run('pct', 'set', str(vmid), '--delete', 'tags') + else: + run('pct', 'set', str(vmid), '--' + key, value) check_runtime_mounts(run('pct', 'config', str(vmid)), candidate['deployment']) gpu_devices.check(run('pct', 'config', str(vmid)), candidate['deployment']) state['staged_config_sha256'] = sha(owned(vmid, record['installation_id'])) diff --git a/oci/tests/test_external_tags.py b/oci/tests/test_external_tags.py new file mode 100644 index 00000000..a28dd420 --- /dev/null +++ b/oci/tests/test_external_tags.py @@ -0,0 +1,189 @@ +"""Proxmox tags can change without preventing OCI management or being lost.""" +import copy +import io +import json +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile +import unittest +from contextlib import redirect_stdout +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'remote')) + +import oci_instance_reconcile as reconcile +import oci_instance_transaction as transaction +from oci_installation_state import sha + +MARKER = '11111111-1111-4111-8111-111111111111' +BASE = (f'description: proxmenux-instance={MARKER}\n' + 'rootfs: local-lvm:vm-200-disk-0,size=8G\n' + 'unprivileged: 1\ncores: 2\nmemory: 512\n' + 'net0: name=eth0,bridge=vmbr0,hwaddr=02:00:00:00:00:01,ip=dhcp\n' + 'net1: name=eth1,bridge=vmbr1,ip=dhcp\nstartup: order=20\n').encode() + + +def config(tags): + return BASE + (f'tags: {tags}\n'.encode() if tags is not None else b'') + + +def record(tags='oci;proxmenux'): + before = config(tags) + return { + 'schema_version': 1, 'vmid': 200, 'status': 'installed', + 'installation_id': MARKER, + 'template': {'id': 'test-app', 'container_contract': {'volumes': []}}, + 'deployment': { + 'vmid': 200, 'rootfs': {'storage': 'local-lvm', 'size_gb': 8}, + 'network': {}, 'resources': {'cores': 2, 'memory_mb': 512}, + 'mounts': [], 'devices': [], + }, + 'observed': { + 'config': before.decode(), 'config_sha256': sha(before), + 'image': {'architecture': 'amd64', 'manifest_digest': 'sha256:old', + 'defaults': {'Volumes': {}}}, + }, + } + + +class ExternalTagsTests(unittest.TestCase): + def test_adding_editing_reordering_and_removing_tags_need_no_adoption(self): + for old, new in ((None, 'homelab'), ('oci;proxmenux', 'homelab;oci;proxmenux'), + ('oci;proxmenux', 'proxmenux;oci'), ('oci;proxmenux', None), + ('oci;proxmenux', '')): + with self.subTest(old=old, new=new): + saved, current = record(old), config(new) + self.assertIsNone(reconcile.propose(saved, current)) + self.assertEqual(transaction.external_changes(saved, current), {}) + + def test_tags_can_change_together_with_cpu_and_memory(self): + current = config('homelab').replace(b'cores: 2', b'cores: 4').replace(b'memory: 512', b'memory: 2048') + self.assertIsNone(reconcile.propose(record(), current)) + self.assertEqual(transaction.external_changes(record(), current), {'cores': 4, 'memory': 2048}) + + def test_tag_changes_do_not_bypass_identity_or_network_checks(self): + for current in (config('homelab').replace(MARKER.encode(), b'22222222-2222-4222-8222-222222222222'), + config('homelab').replace(b'bridge=vmbr0', b'bridge=vmbr2')): + with self.subTest(config=current): + with self.assertRaises(ValueError): + reconcile.propose(record(), current) + with self.assertRaises(ValueError): + transaction.external_changes(record(), current) + + def test_new_volume_still_requires_adoption_when_tags_also_change(self): + saved = record() + current = config('homelab') + b'mp0: local-lvm:vm-200-disk-1,mp=/data,size=8G,backup=1\n' + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + path = root / '200/oci-compose.json' + path.parent.mkdir() + path.write_text(json.dumps(saved)) + with patch.object(reconcile.instances, 'ROOT', root): + proposal = reconcile.propose(saved, current) + self.assertEqual(proposal['candidate']['deployment']['mounts'][0]['container_path'], '/data') + self.assertEqual(len(proposal['details']), 1) + self.assertEqual(json.loads(path.read_text()), saved) + + +class Prepared(Exception): + """Stop a mocked transaction before it can stop or modify any container.""" + + +class TagReplayTests(unittest.TestCase): + def prepare(self, folder, tags, operation, coordinated=None): + current, saved = config(tags), record() + archive = folder / 'image.tar' + archive.write_bytes(b'fixture') + states = [] + + def checkpoint(_path, state, phase): + self.assertEqual(phase, 'prepared') + states.append(copy.deepcopy(state)) + raise Prepared() + + def command(*args): + if args[:2] == ('pct', 'config'): + return current + if args[:3] == ('pvesh', 'get', '/cluster/ha/resources'): + return b'[]' + if args[:2] == ('pct', 'status'): + return b'status: running' + self.fail(f'Unexpected host command: {args}') + + proposal = {'operation': 'recreate', 'candidate': copy.deepcopy(saved)} if operation == 'recreate' else None + image = {'architecture': 'amd64', 'os': 'linux', 'manifest_digest': 'sha256:new', 'defaults': {}} + with (patch.object(transaction.instances, 'read', return_value=saved), + patch.object(transaction, 'run', side_effect=command), + patch.object(transaction, 'check_archive'), + patch.object(transaction, 'image_from_archive', return_value=image), + patch.object(transaction.work_backup, 'locate', side_effect=lambda directory, *_: directory), + patch.object(transaction, 'open_log'), + patch.object(transaction, 'checkpoint', side_effect=checkpoint), + redirect_stdout(io.StringIO()), self.assertRaises(Prepared)): + transaction.apply(folder, 200, archive, operation, proposal=proposal, coordinated=coordinated) + return states[0] + + @unittest.skipUnless(shutil.which('bash') and shutil.which('jq'), 'bash and jq are needed') + def test_update_and_modify_replay_current_tags_including_no_tags(self): + installer = (ROOT / 'remote/install_oci.sh').read_text() + function = re.search(r'^apply_kept_settings\(\) \{\n.*?^\}\n', installer, re.M | re.S) + self.assertIsNotNone(function) + for operation in ('update', 'recreate'): + for tags in ('homelab;oci;proxmenux', 'oci', None, ''): + with self.subTest(operation=operation, tags=tags), tempfile.TemporaryDirectory() as name: + folder = Path(name) + state = self.prepare(folder, tags, operation) + kept = state['runtime_deployment']['kept_proxmox_settings'] + deployment = folder / 'deployment.json' + deployment.write_text(json.dumps({'kept_proxmox_settings': kept})) + calls = folder / 'calls' + script = ''' +pct() { printf '%s\\0' "$@" >> "$CALLS"; printf '\\n' >> "$CALLS"; } +oci_quiet() { "$@"; } +translate() { printf '%s' "$1"; } +die() { printf '%s\\n' "$*" >&2; exit 1; } +''' + done = subprocess.run(['bash', '-euc', script + function.group(0) + '\napply_kept_settings'], + env=dict(os.environ, VMID='200', DEPLOYMENT_FILE=str(deployment), CALLS=str(calls)), + capture_output=True, text=True) + self.assertEqual(done.returncode, 0, done.stderr) + commands = [line.split(b'\0')[:-1] for line in calls.read_bytes().splitlines()] + expected = [b'set', b'200', b'--tags', tags.encode()] if tags else [b'set', b'200', b'--delete', b'tags'] + self.assertIn(expected, commands) + self.assertIn([b'set', b'200', b'--net1', b'name=eth1,bridge=vmbr1,ip=dhcp'], commands) + self.assertIn([b'set', b'200', b'--startup', b'order=20'], commands) + + def test_stack_operations_also_preserve_tags_and_their_absence(self): + for operation in ('update', 'recreate'): + for tags in ('homelab;oci', None, ''): + with self.subTest(operation=operation, tags=tags), tempfile.TemporaryDirectory() as name: + state = self.prepare(Path(name), tags, operation, coordinated={'stack': 'test'}) + self.assertEqual(state['preserved_stack_config']['tags'], tags or '') + + @unittest.skipUnless(shutil.which('bash') and shutil.which('jq'), 'bash and jq are needed') + def test_installer_still_refuses_unsupported_kept_settings(self): + installer = (ROOT / 'remote/install_oci.sh').read_text() + function = re.search(r'^apply_kept_settings\(\) \{\n.*?^\}\n', installer, re.M | re.S).group(0) + with tempfile.TemporaryDirectory() as name: + deployment = Path(name) / 'deployment.json' + deployment.write_text(json.dumps({'kept_proxmox_settings': {'unprivileged': '0'}})) + script = ''' +oci_quiet() { echo 'must not run' >&2; exit 99; } +translate() { printf '%s' "$1"; } +die() { printf '%s\\n' "$*" >&2; exit 1; } +''' + done = subprocess.run(['bash', '-euc', script + function + '\napply_kept_settings'], + env=dict(os.environ, VMID='200', DEPLOYMENT_FILE=str(deployment)), + capture_output=True, text=True) + self.assertEqual(done.returncode, 1) + self.assertIn('Unsupported kept setting: unprivileged', done.stderr) + self.assertNotIn('must not run', done.stderr) + + +if __name__ == '__main__': + unittest.main() From b7b412b45de3ad2032ab88692ba38c0c151303ef Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:23:38 +0000 Subject: [PATCH 02/53] fix(oci): harden runtime HOME resolution --- oci/remote/install_oci.sh | 2 +- oci/remote/oci_runtime.py | 37 +++- oci/tests/test_runtime_home_contract.py | 4 +- oci/tests/test_runtime_home_hardening.py | 260 +++++++++++++++++++++++ 4 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 oci/tests/test_runtime_home_hardening.py diff --git a/oci/remote/install_oci.sh b/oci/remote/install_oci.sh index 7d1d8e85..88d5f0fe 100755 --- a/oci/remote/install_oci.sh +++ b/oci/remote/install_oci.sh @@ -1091,7 +1091,7 @@ ensure_runtime_home() { passwd_file="${rootfs}/etc/passwd" mount_ct_rootfs if ! home=$(python3 "$OCI_RUNTIME_RESOLVER" --home "$passwd_file" "$uid" 2>>"${OCI_LOG:-/dev/stderr}"); then - oci_quiet pct unmount "$VMID" || true + oci_quiet pct unmount "$VMID" || die "$(translate "Could not unmount the container filesystem")" return 0 fi oci_quiet pct unmount "$VMID" || die "$(translate "Could not unmount the container filesystem")" diff --git a/oci/remote/oci_runtime.py b/oci/remote/oci_runtime.py index ee3b9d56..3d842fd3 100644 --- a/oci/remote/oci_runtime.py +++ b/oci/remote/oci_runtime.py @@ -4,7 +4,9 @@ from __future__ import annotations import json +import os import shlex +import stat import sys import tarfile from pathlib import Path @@ -90,14 +92,39 @@ def effective_entrypoint( def home_directory(passwd_path: Path, uid: int) -> str | None: - """Return the safe home directory recorded for a container UID.""" - for line in passwd_path.read_text(encoding="utf-8").splitlines(): + """Read a regular image passwd file; only an absent UID defaults to /.""" + if uid < 0: + return None + # The caller supplies /etc/passwd. Pin etc so an image + # symlink or a rename during lookup cannot redirect the host-side read. + directory = os.open(passwd_path.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) + try: + if not stat.S_ISREG(os.stat(passwd_path.name, dir_fd=directory, follow_symlinks=False).st_mode): + return None + descriptor = os.open( + passwd_path.name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory, + ) + try: + # Recheck the opened object, not its name; a FIFO replacement + # must neither block open() nor reach a read operation. + if not stat.S_ISREG(os.fstat(descriptor).st_mode): + return None + with os.fdopen(descriptor, encoding="utf-8", closefd=False) as stream: + content = stream.read() + finally: + os.close(descriptor) + finally: + os.close(directory) + for line in content.splitlines(): fields = line.split(":") - if len(fields) < 7 or fields[2] != str(uid): + if len(fields) < 3 or fields[2] != str(uid): continue + if len(fields) != 7: + return None home = fields[5] - return home if home.startswith("/") else None - return None + safe = home.startswith("/") and not any(ord(char) < 32 or ord(char) == 127 for char in home) + return home if safe else None + return "/" def main() -> int: diff --git a/oci/tests/test_runtime_home_contract.py b/oci/tests/test_runtime_home_contract.py index c14e8e26..25e9ca9e 100644 --- a/oci/tests/test_runtime_home_contract.py +++ b/oci/tests/test_runtime_home_contract.py @@ -31,14 +31,14 @@ class RuntimeHomeResolutionTests(unittest.TestCase): ) self.assertEqual(oci_runtime.home_directory(passwd, 1001), "/srv/app") - def test_unknown_or_unsafe_home_is_not_invented(self): + def test_unsafe_record_is_rejected_but_unknown_uid_defaults_to_root(self): passwd = self.passwd( "root:x:0:0:root:relative-home:/bin/sh\n" "app:x:1001:1001:App user::/sbin/nologin\n" ) self.assertIsNone(oci_runtime.home_directory(passwd, 0)) self.assertIsNone(oci_runtime.home_directory(passwd, 1001)) - self.assertIsNone(oci_runtime.home_directory(passwd, 1234)) + self.assertEqual(oci_runtime.home_directory(passwd, 1234), "/") class RuntimeHomeInstallerContractTests(unittest.TestCase): diff --git a/oci/tests/test_runtime_home_hardening.py b/oci/tests/test_runtime_home_hardening.py new file mode 100644 index 00000000..6418709d --- /dev/null +++ b/oci/tests/test_runtime_home_hardening.py @@ -0,0 +1,260 @@ +"""Exercise HOME resolution without reading image symlinks or changing guests.""" + +from contextlib import redirect_stderr, redirect_stdout +import io +import os +from pathlib import Path +import re +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "remote")) +import oci_runtime + + +class HomeHardeningTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + (self.root / "etc").mkdir() + self.passwd = self.root / "etc/passwd" + self.passwd.write_text("root:x:0:0:root:/root:/bin/sh\n", encoding="utf-8") + + def cli(self, uid="1234"): + output, error = io.StringIO(), io.StringIO() + with patch.object(sys, "argv", ["oci_runtime.py", "--home", str(self.passwd), uid]), \ + redirect_stdout(output), redirect_stderr(error): + status = oci_runtime.main() + return status, output.getvalue(), error.getvalue() + + def assert_rejected_without_read(self): + with patch.object(os, "fdopen", side_effect=AssertionError("unsafe file read")): + status, output, _ = self.cli() + self.assertEqual(status, 1) + self.assertEqual(output, "") + + def test_unknown_uid_uses_root_only_after_reading_a_regular_database(self): + self.assertEqual(self.cli(), (0, "/\n", "")) + + def test_empty_regular_database_uses_root(self): + self.passwd.write_text("") + self.assertEqual(self.cli(), (0, "/\n", "")) + + def test_final_symlink_is_rejected_without_reading_its_target(self): + target = self.root / "outside" + target.write_text("app:x:1234:1234::/host-secret:/bin/sh\n") + self.passwd.unlink() + self.passwd.symlink_to(target) + self.assert_rejected_without_read() + self.assertEqual(target.read_text(), "app:x:1234:1234::/host-secret:/bin/sh\n") + + def test_symlinked_etc_is_rejected_without_reading_its_target(self): + directory = self.root / "etc" + directory.rename(self.root / "outside-etc") + directory.symlink_to(self.root / "outside-etc", target_is_directory=True) + self.assert_rejected_without_read() + self.assertEqual(self.cli("0")[:2], (1, "")) + + def test_pinned_etc_does_not_follow_a_later_directory_replacement(self): + original_open = os.open + outside = self.root / "outside" + outside.mkdir() + (outside / "passwd").write_text("root:x:0:0::/outside:/bin/sh\n") + replaced = [] + def replace(path, flags, *args, **kwargs): + if str(path) == "passwd": + (self.root / "etc").rename(self.root / "original-etc") + (self.root / "etc").symlink_to(outside, target_is_directory=True) + replaced.append(True) + return original_open(path, flags, *args, **kwargs) + with patch.object(os, "open", side_effect=replace): + self.assertEqual(self.cli("0"), (0, "/root\n", "")) + self.assertEqual(replaced, [True]) + + def test_directory_is_rejected_without_reading(self): + self.passwd.unlink() + self.passwd.mkdir() + self.assert_rejected_without_read() + + def test_fifo_is_rejected_without_reading_or_waiting_for_a_writer(self): + self.passwd.unlink() + os.mkfifo(self.passwd) + done = subprocess.run( + [sys.executable, "-B", str(ROOT / "remote/oci_runtime.py"), "--home", str(self.passwd), "0"], + capture_output=True, text=True, timeout=3, + ) + self.assertEqual(done.returncode, 1) + self.assertEqual(done.stdout, "") + self.assert_rejected_without_read() + + def test_missing_database_does_not_masquerade_as_unknown_uid(self): + self.passwd.unlink() + self.assert_rejected_without_read() + + def test_permission_failure_does_not_masquerade_as_unknown_uid(self): + with patch.object(os, "open", side_effect=PermissionError("denied")): + self.assert_rejected_without_read() + + def test_invalid_encoding_does_not_produce_a_fallback(self): + self.passwd.write_bytes(b"\xff\xfe") + status, output, _ = self.cli() + self.assertEqual((status, output), (1, "")) + + def test_partial_matching_record_does_not_produce_a_fallback(self): + self.passwd.write_text("app:x:1234:1234\n") + self.assertEqual(self.cli()[:2], (1, "")) + + def test_read_error_closes_both_descriptors_without_a_fallback(self): + descriptors = [] + original_open = os.open + def opened(*args, **kwargs): + fd = original_open(*args, **kwargs) + descriptors.append(fd) + return fd + with patch.object(os, "open", side_effect=opened), \ + patch.object(os, "fdopen", side_effect=OSError("read failed")): + self.assertEqual(self.cli()[:2], (1, "")) + self.assertEqual(len(descriptors), 2) + for fd in descriptors: + with self.assertRaises(OSError): + os.fstat(fd) + + def test_matching_unsafe_home_is_not_replaced_with_root(self): + for value in ("", "relative", "/bad\tvalue", "/bad\x00value", "/bad\x7fvalue"): + with self.subTest(value=repr(value)): + self.passwd.write_text(f"app:x:1234:1234::{value}:/bin/sh\n") + self.assertEqual(self.cli()[:2], (1, "")) + + def test_invalid_uid_does_not_open_the_database(self): + for uid in ("-1", "nobody"): + with self.subTest(uid=uid), patch.object(os, "open") as opened: + self.assertEqual(self.cli(uid)[:2], (1, "")) + opened.assert_not_called() + + def test_symlink_replacement_between_check_and_open_is_not_read(self): + original_open = os.open + outside = self.root / "outside" + outside.write_text("app:x:1234:1234::/outside:/bin/sh\n") + def replace(path, flags, *args, **kwargs): + if str(path) == "passwd": + self.passwd.unlink() + self.passwd.symlink_to(outside) + return original_open(path, flags, *args, **kwargs) + with patch.object(os, "open", side_effect=replace): + self.assert_rejected_without_read() + + def test_fifo_replacement_between_check_and_open_is_not_read(self): + original_open = os.open + def replace(path, flags, *args, **kwargs): + if str(path) == "passwd": + self.passwd.unlink() + os.mkfifo(self.passwd) + return original_open(path, flags, *args, **kwargs) + with patch.object(os, "open", side_effect=replace): + self.assert_rejected_without_read() + + +class HomeInstallerExecutionTests(unittest.TestCase): + """Run the real shell HOME function and Python CLI with fake mount boundaries.""" + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + (self.root / "etc").mkdir() + self.passwd = self.root / "etc/passwd" + self.passwd.write_text("root:x:0:0:root:/root:/bin/sh\napp:x:1001:1001::/srv/app:/bin/sh\n") + self.conf = self.root / "guest.conf" + self.trace = self.root / "trace" + self.source = (ROOT / "remote/install_oci.sh").read_text() + + def run_home(self, config="unrelated: keep\n", unmount_status=0, mount_status=0): + self.conf.write_text(config) + self.trace.write_text("") + functions = [] + for name in ("ensure_runtime_home", "set_runtime_environment"): + found = re.search(rf"^{name}\(\) \{{\n.*?^\}}\n", self.source, re.M | re.S) + self.assertIsNotNone(found, name) + functions.append(found.group(0)) + script = r''' +set -euo pipefail +CONF=$1 +FIXTURE_ROOT=$2 +TRACE=$3 +OCI_RUNTIME_RESOLVER=$4 +PYTHON=$5 +UNMOUNT_STATUS=$6 +MOUNT_STATUS=$7 +OCI_LOG="$FIXTURE_ROOT/resolver.log" +VMID=123456789 +translate() { printf '%s' "$1"; } +die() { printf '%s\n' "$*" >&2; exit 1; } +mount_ct_rootfs() { + printf 'mount\n' >> "$TRACE" + [ "$MOUNT_STATUS" = 0 ] || die 'mount failed' + rootfs=$FIXTURE_ROOT + passwd_file="$rootfs/etc/passwd" +} +oci_quiet() { printf '%s\n' "$*" >> "$TRACE"; return "$UNMOUNT_STATUS"; } +python3() { printf 'resolve\n' >> "$TRACE"; command "$PYTHON" -B "$@"; } +''' + done = subprocess.run( + ["bash", "-c", script + "\n".join(functions) + "\nensure_runtime_home\nprintf 'continued\\n'\n", + "bash", str(self.conf), str(self.root), str(self.trace), str(ROOT / "remote/oci_runtime.py"), + sys.executable, str(unmount_status), str(mount_status)], + capture_output=True, text=True, timeout=5, + ) + return done, self.conf.read_text(), self.trace.read_text().splitlines() + + def test_explicit_home_including_empty_never_mounts_or_resolves(self): + for home in ("/custom", ""): + config = f"unrelated: keep\nlxc.environment.runtime: HOME={home}\n" + done, after, trace = self.run_home(config) + self.assertEqual((done.returncode, after, trace), (0, config, [])) + + def test_default_and_effective_uid_write_the_resolved_home_after_unmount(self): + for uid, expected in ((None, "/root"), (1001, "/srv/app"), (9999, "/")): + config = "unrelated: keep\n" + (f"lxc.init.uid: {uid}\n" if uid is not None else "") + done, after, trace = self.run_home(config) + self.assertEqual(done.returncode, 0, done.stderr) + self.assertEqual(after, config + f"lxc.environment.runtime: HOME={expected}\n") + self.assertEqual(trace, ["mount", "resolve", "pct unmount 123456789"]) + + def test_invalid_uid_leaves_config_unchanged_without_mount(self): + config = "lxc.init.uid: not-a-uid\n" + done, after, trace = self.run_home(config) + self.assertEqual((done.returncode, after, trace), (0, config, [])) + + def test_rejected_input_unmounts_and_preserves_config(self): + self.passwd.unlink() + self.passwd.symlink_to(self.root / "missing") + config = "unrelated: keep\n" + done, after, trace = self.run_home(config) + self.assertEqual((done.returncode, after), (0, config)) + self.assertEqual(trace, ["mount", "resolve", "pct unmount 123456789"]) + + def test_failed_unmount_stops_before_config_write_for_success_and_failure(self): + for valid in (True, False): + if not valid: + self.passwd.unlink() + config = "unrelated: keep\n" + done, after, trace = self.run_home(config, unmount_status=1) + self.assertNotEqual(done.returncode, 0) + self.assertEqual(after, config) + self.assertNotIn("continued", done.stdout) + self.assertEqual(trace, ["mount", "resolve", "pct unmount 123456789"]) + + def test_failed_mount_never_reads_or_writes(self): + config = "unrelated: keep\n" + done, after, trace = self.run_home(config, mount_status=1) + self.assertNotEqual(done.returncode, 0) + self.assertEqual(after, config) + self.assertEqual(trace, ["mount"]) + + +if __name__ == "__main__": + unittest.main() From e89f2596b2f0270a49178c1c3f0c73846d53fdb7 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:09:07 +0000 Subject: [PATCH 03/53] feat: discover native LXC applications on demand with evidence --- .github/scripts/run_offline_qualified.py | 2 + .github/workflows/test-offline-qualified.yml | 8 +- AppImage/components/lxc-app-panel.tsx | 119 ++++- AppImage/messages/en/common.json | 10 + AppImage/messages/sk/common.json | 10 + AppImage/scripts/build_appimage.sh | 1 + AppImage/scripts/flask_server.py | 16 + AppImage/scripts/lxc_apps.py | 7 + AppImage/scripts/native_app_discovery.py | 487 ++++++++++++++++++ .../tests/test_native_app_discovery.py | 441 ++++++++++++++++ CONTRIBUTING.md | 6 +- .../test_native_app_discovery_ui.cjs | 164 ++++++ 12 files changed, 1248 insertions(+), 23 deletions(-) create mode 100644 AppImage/scripts/native_app_discovery.py create mode 100644 AppImage/scripts/tests/test_native_app_discovery.py create mode 100644 tests/lxc_updates/test_native_app_discovery_ui.cjs diff --git a/.github/scripts/run_offline_qualified.py b/.github/scripts/run_offline_qualified.py index 9bd13abc..fe6c26d4 100644 --- a/.github/scripts/run_offline_qualified.py +++ b/.github/scripts/run_offline_qualified.py @@ -13,6 +13,7 @@ import sys import tempfile PYTHON_FILES = ( + "AppImage/scripts/tests/test_native_app_discovery.py", "tests/test_audit_presentation.py", "tests/test_audit_safety_wording.py", "tests/test_audit_policy.py", @@ -24,6 +25,7 @@ PYTHON_FILES = ( "AppImage/scripts/tests/test_health_persistence_recurrence.py", ) NODE_FILES = ( + "tests/lxc_updates/test_native_app_discovery_ui.cjs", "tests/lxc_updates/test_docker_delegated_ui.cjs", "tests/test_audit_diagnostic_document.cjs", "tests/test_audit_policy.cjs", diff --git a/.github/workflows/test-offline-qualified.yml b/.github/workflows/test-offline-qualified.yml index 29af7416..a562c6b3 100644 --- a/.github/workflows/test-offline-qualified.yml +++ b/.github/workflows/test-offline-qualified.yml @@ -12,6 +12,7 @@ on: - 'AppImage/package-lock.json' - 'AppImage/scripts/*.py' - 'AppImage/scripts/tests/test_health_persistence_recurrence.py' + - 'AppImage/scripts/tests/test_native_app_discovery.py' - 'AppImage/app/**' - 'AppImage/components/**' - 'AppImage/hooks/**' @@ -32,6 +33,7 @@ on: - 'AppImage/package-lock.json' - 'AppImage/scripts/*.py' - 'AppImage/scripts/tests/test_health_persistence_recurrence.py' + - 'AppImage/scripts/tests/test_native_app_discovery.py' - 'AppImage/app/**' - 'AppImage/components/**' - 'AppImage/hooks/**' @@ -56,7 +58,9 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.11' - - name: Run nine qualified Python files + - name: Install native discovery route fixture dependency + run: python3 -m pip install 'Flask>=3,<4' + - name: Run qualified Python files env: PYTHONDONTWRITEBYTECODE: '1' run: python3 -I -B .github/scripts/run_offline_qualified.py --lane python @@ -82,7 +86,7 @@ jobs: run: | npm ci --prefix .github/ci/offline-node --legacy-peer-deps --ignore-scripts --no-audit --no-fund ln -s ../.github/ci/offline-node/node_modules AppImage/node_modules - - name: Run eleven qualified Node files + - name: Run qualified Node files env: NODE_PATH: ${{ github.workspace }}/AppImage/node_modules PYTHONDONTWRITEBYTECODE: '1' diff --git a/AppImage/components/lxc-app-panel.tsx b/AppImage/components/lxc-app-panel.tsx index 59939f0b..666009cb 100644 --- a/AppImage/components/lxc-app-panel.tsx +++ b/AppImage/components/lxc-app-panel.tsx @@ -19,7 +19,7 @@ * Secure Gateway. */ -import { useCallback, useEffect, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { Loader2, Save, RefreshCw, Trash2, Package, ExternalLink, AlertTriangle, Info, PlusCircle, Pencil, ChevronDown, ChevronRight, EyeOff, @@ -93,6 +93,7 @@ interface AppConfig { health_path?: string logo_url?: string helper_slug?: string + discovery_id?: string // Set when the application runs inside Docker: registered for its identity // and installed version, updated by the image it comes from. update_via?: string @@ -121,7 +122,18 @@ interface PortDetail { logo_url: string } +interface NativeDiscovery { + vmid: number + status: "not_scanned" | "complete" | "partial" | "error" | "busy" + stale: boolean + candidates: DetectedApp[] +} + interface DetectedApp { + discovery_id?: string + source?: "native_metadata" + installed_version?: string | null + evidence?: Array<{ kind: string; value: string }> slug: string name: string logo_url?: string | null @@ -462,12 +474,34 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: const [loading, setLoading] = useState(!seed) const [sidecar, setSidecar] = useState(seed?.sidecar ?? null) const [suggestions, setSuggestions] = useState(seed?.suggestions ?? null) + const [nativeDiscovery, setNativeDiscovery] = useState(null) + const targetRef = useRef(vmid) + targetRef.current = vmid + const targetGeneration = useRef(0) + const previousTarget = useRef(vmid) + if (previousTarget.current !== vmid) { + previousTarget.current = vmid + targetGeneration.current += 1 + } + useEffect(() => { + const generation = targetGeneration.current + setNativeDiscovery(null) + setEditing(null) + setDetectionNotice(null) + setSearchingApplications(false) + setSaving(false) + if (managed) return + fetchApi(`/api/vms/${vmid}/apps/native-discovery`).then(result => { + if (targetRef.current === vmid && targetGeneration.current === generation) setNativeDiscovery(result) + }).catch(() => { /* cached metadata is optional; never initiate a scan */ }) + return () => { targetGeneration.current += 1 } + }, [vmid, managed]) const [adguardSetupAvailable, setAdguardSetupAvailable] = useState(false) const [error, setError] = useState(null) const [searchingApplications, setSearchingApplications] = useState(false) const [detectionNotice, setDetectionNotice] = useState<{ found: boolean; text: string } | null>(null) // Editor state - const [editing, setEditing] = useState<{ appId: string | null; draft: AppConfig } | null>(null) + const [editing, setEditing] = useState<{ appId: string | null; draft: AppConfig; targetVmid: number } | null>(null) const [binaryArgsInput, setBinaryArgsInput] = useState("") const [commandArgvInput, setCommandArgvInput] = useState("") const [saving, setSaving] = useState(false) @@ -522,6 +556,8 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: // `initialData` or the shared cache, the sidecar is already // populated and the user shouldn't see a flash of "Loading…" // while we revalidate. + const generation = targetGeneration.current + const isCurrent = () => targetRef.current === vmid && targetGeneration.current === generation if (!sidecar) setLoading(true) setError(null) try { @@ -533,14 +569,14 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: // the "Loading applications…" flash that used to show while a // second, racing fetch caught up. const bundle = await fetchLxcApps(vmid) - if (bundle) { + if (bundle && isCurrent()) { setSidecar(bundle.sidecar) setSuggestions(bundle.suggestions) } } catch (e: any) { - setError(e?.message || t("vmLxc.appEditor.loadFailed")) + if (isCurrent()) setError(e?.message || t("vmLxc.appEditor.loadFailed")) } finally { - setLoading(false) + if (isCurrent()) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [vmid, managed]) @@ -617,7 +653,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: return msg } - const editorOpen = !!editing + const editorOpen = !!editing && editing.targetVmid === vmid // The picker catalog is only needed after the user opens the editor. useEffect(() => { @@ -653,8 +689,8 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: // (`extras[]` from the backend). Both use the same DetectedApp // shape so the empty state renders them uniformly. const detectedList: DetectedApp[] = useMemo(() => { - if (!suggestions) return [] - const out: DetectedApp[] = [] + const out: DetectedApp[] = [...(nativeDiscovery?.vmid === vmid ? nativeDiscovery.candidates : [])] + if (!suggestions) return out // A ProxMenux OCI install has no helper slug — nothing was installed by a // community script — so its template id identifies it instead. const primarySlug = suggestions.helper_slug || suggestions.oci_instance?.template_id @@ -679,12 +715,12 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: } } return out - }, [suggestions]) + }, [suggestions, nativeDiscovery, vmid]) // Registered slugs — used to filter the detection list down to // what the user hasn't already registered on this CT. const registeredSlugs = useMemo( - () => new Set(apps.map((a) => a.helper_slug).filter(Boolean) as string[]), + () => new Set(apps.flatMap((a) => [a.helper_slug, a.discovery_id]).filter(Boolean) as string[]), [apps], ) // Dismissed slugs — persisted in the sidecar. Chips the user @@ -750,14 +786,30 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: } const searchInstalledApplications = async () => { + const generation = targetGeneration.current + const isCurrent = () => targetRef.current === vmid && targetGeneration.current === generation const before = new Set([...visibleDetected, ...visibleWorkloads].map((item) => item.slug)) setSearchingApplications(true) setDetectionNotice(null) setError(null) try { + let native = await fetchApi(`/api/vms/${vmid}/apps/native-discovery`, { + method: "POST", + }) + if (!isCurrent()) return + setNativeDiscovery(native) + if (native.status === "error" || native.status === "busy") { + setDetectionNotice({ found: false, text: t("vmLxc.appEditor.nativeScanFailed") }) + return + } const result: Suggestions = await fetchApi(`/api/vms/${vmid}/apps/suggestions`, { method: "POST", }) + if (!isCurrent()) return + // Recompose against the refreshed catalog without another native scan. + native = await fetchApi(`/api/vms/${vmid}/apps/native-discovery`) + if (!isCurrent()) return + setNativeDiscovery(native) setSuggestions(result) if (sidecar) setLxcAppsCached(vmid, sidecar, result) @@ -765,11 +817,14 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: if (result.helper_slug) detected.add(result.helper_slug) for (const item of result.extras || []) detected.add(item.slug) for (const item of result.docker_workloads || []) detected.add(item.slug) + for (const item of native.candidates) detected.add(item.slug) const visible = [...detected].filter( (slug) => !registeredSlugs.has(slug) && !dismissedSlugs.has(slug), ) const newCount = visible.filter((slug) => !before.has(slug)).length - if (newCount === 1) { + if (native.status === "partial") { + setDetectionNotice({ found: visible.length > 0, text: t("vmLxc.appEditor.nativeScanPartial") }) + } else if (newCount === 1) { setDetectionNotice({ found: true, text: t("vmLxc.appEditor.oneNewApplicationDetected") }) } else if (newCount > 1) { setDetectionNotice({ @@ -785,9 +840,9 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: }) } } catch (e: any) { - setError(e?.message || t("vmLxc.appEditor.detectionFailed")) + if (isCurrent()) setError(e?.message || t("vmLxc.appEditor.detectionFailed")) } finally { - setSearchingApplications(false) + if (isCurrent()) setSearchingApplications(false) } } @@ -804,6 +859,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: existing?: AppEntry, opts?: { withTracking?: boolean, preset?: DetectedApp }, ) => { + const generation = targetGeneration.current let seed: AppConfig if (existing) { seed = { @@ -832,6 +888,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: health_path: existing.health_path || "", logo_url: existing.logo_url || "", helper_slug: existing.helper_slug || "", + ...(existing.discovery_id ? { discovery_id: existing.discovery_id } : {}), // Preserved explicitly: saving replaces the whole record, so dropping // it here would silently un-delegate the app the first time someone // edits one of its ports. @@ -862,7 +919,11 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: const p = opts.preset seed.name = p.name seed.logo_url = p.logo_url || "" - seed.helper_slug = p.slug + if (p.discovery_id) { + seed.discovery_id = p.discovery_id + seed.update_method = "none" + seed.update_command = "" + } else seed.helper_slug = p.slug // Docker endpoints come from the real published host-port mappings // listed under Web links. Do not pre-save a catalog default such as // 9000; the user explicitly chooses which workload links to add. @@ -943,9 +1004,10 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: setShowAdvanced(false) } } + if (targetRef.current !== vmid || targetGeneration.current !== generation) return setBinaryArgsInput((seed.binary_args || []).join(", ")) setCommandArgvInput((seed.command_argv || []).join(", ")) - setEditing({ appId: existing?.id || null, draft: seed }) + setEditing({ appId: existing?.id || null, draft: seed, targetVmid: vmid }) setDetectorTest(null) setError(null) }, [suggestions, vmid, sidecar, isOciAdguard, adguardSetupAvailable]) @@ -959,7 +1021,9 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: } const save = async () => { - if (!editing) return + if (!editing || editing.targetVmid !== vmid || targetRef.current !== vmid) return + const generation = targetGeneration.current + const isCurrent = () => targetRef.current === vmid && targetGeneration.current === generation setSaving(true) setError(null) try { @@ -973,6 +1037,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: body: JSON.stringify(editing.draft), }) if ((r as any).error) throw new Error((r as any).error) + if (!isCurrent()) return setSidecar(r) setLxcAppsCached(vmid, r, suggestions) setEditing(null) @@ -980,9 +1045,9 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: setCommandArgvInput("") onChange?.() } catch (e: any) { - setError(e?.message || t("vmLxc.appEditor.saveFailed")) + if (isCurrent()) setError(e?.message || t("vmLxc.appEditor.saveFailed")) } finally { - setSaving(false) + if (isCurrent()) setSaving(false) } } @@ -2452,6 +2517,24 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: ? t("vmLxc.appEditor.versionDetected", { version: d.tracking_suggestion.detected_version || "" }) : t("vmLxc.appEditor.detectedInContainer")} + {d.source === "native_metadata" && ( +
+
{d.installed_version + ? t("vmLxc.appEditor.versionDetected", { version: d.installed_version }) + : t("vmLxc.appEditor.nativeVersionUnknown")}
+ {(nativeDiscovery?.stale || nativeDiscovery?.status === "partial") && ( +
{t("vmLxc.appEditor.nativeEvidenceStale")}
+ )} + {d.evidence?.map((e, index) =>
+ {t(e.kind === "tcp_listener" ? "vmLxc.appEditor.nativeEvidencePort" + : e.kind === "installation_file" ? "vmLxc.appEditor.nativeEvidenceFile" + : e.kind === "process_executable" ? "vmLxc.appEditor.nativeEvidenceProcess" + : e.kind === "working_directory" ? "vmLxc.appEditor.nativeEvidenceDirectory" + : "vmLxc.appEditor.nativeEvidenceService")}: {e.value} +
)} +
{t("vmLxc.appEditor.nativeRegisterOnly")}
+
+ )} {d.tracking_suggestion?.detector_source === "legacy_fallback" && (
{t("vmLxc.appEditor.legacyDetectorUsed")} diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index fa596302..64129339 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -1730,6 +1730,16 @@ "searchApplications": "Find applications", "searchingApplications": "Searching applications…", "noApplicationsDetected": "No applications were detected.", + "nativeEvidencePort": "Listening TCP port", + "nativeEvidenceFile": "Installed file", + "nativeEvidenceProcess": "Running executable", + "nativeEvidenceDirectory": "Working directory", + "nativeEvidenceService": "Service definition", + "nativeScanFailed": "Application search could not finish. Previous evidence may be outdated; try again.", + "nativeScanPartial": "Application search is incomplete. Review the available evidence and try again.", + "nativeVersionUnknown": "Installed version unknown", + "nativeEvidenceStale": "Evidence may be outdated or incomplete.", + "nativeRegisterOnly": "Confirm registration in the editor. A known version is a manual value; no updater is configured.", "noNewApplicationsDetected": "No new applications were detected.", "dockerDetectedWithWorkloads": "Docker detected with {count} containerised application(s)", "dockerWorkloadsHeading": "Running inside Docker", diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 692fcce6..5d792990 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -1682,6 +1682,16 @@ "searchApplications": "Hľadať aplikácie", "searchingApplications": "Hľadajú sa aplikácie…", "noApplicationsDetected": "Neboli zistené žiadne aplikácie.", + "nativeEvidencePort": "Otvorený TCP port", + "nativeEvidenceFile": "Nainštalovaný súbor", + "nativeEvidenceProcess": "Spustený program", + "nativeEvidenceDirectory": "Pracovný priečinok", + "nativeEvidenceService": "Definícia služby", + "nativeScanFailed": "Vyhľadávanie aplikácií sa nepodarilo dokončiť. Predchádzajúce dôkazy môžu byť zastarané; skús to znova.", + "nativeScanPartial": "Vyhľadávanie aplikácií je neúplné. Skontroluj dostupné dôkazy a skús to znova.", + "nativeVersionUnknown": "Nainštalovaná verzia nie je známa", + "nativeEvidenceStale": "Dôkazy môžu byť zastarané alebo neúplné.", + "nativeRegisterOnly": "Registráciu potvrď v editore. Známa verzia je ručný údaj; aktualizátor nie je nastavený.", "noNewApplicationsDetected": "Neboli zistené žiadne nové aplikácie.", "oneNewApplicationDetected": "Bola zistená jedna nová aplikácia.", "newApplicationsDetected": "Boli zistené {count} nové aplikácie.", diff --git a/AppImage/scripts/build_appimage.sh b/AppImage/scripts/build_appimage.sh index 40560c21..0b02bbb6 100755 --- a/AppImage/scripts/build_appimage.sh +++ b/AppImage/scripts/build_appimage.sh @@ -149,6 +149,7 @@ cp "$SCRIPT_DIR/managed_installs.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo " cp "$SCRIPT_DIR/lxc_update_observation.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/lxc_update_finalization.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/lxc_check_job.py" "$APP_DIR/usr/bin/" +cp "$SCRIPT_DIR/native_app_discovery.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/lxc_apps.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ lxc_apps.py not found" cp "$SCRIPT_DIR/custom_links.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ custom_links.py not found" cp "$SCRIPT_DIR/recreate_docker_container.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ recreate_docker_container.py not found" diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 3ffa0c4c..f1d6c302 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -2276,6 +2276,8 @@ def _handle_guest_lifecycle(vmid: str, vm_type: str, action: str) -> None: # or stops; drop the cached IP so the next /api/vms poll re-reads # it via `lxc-info`. QEMU guests do not touch this cache. if guest_type == 'lxc': + import native_app_discovery + native_app_discovery.mark_stale(guest_id) _invalidate_lxc_ip(guest_id) if action in ('start', 'reboot'): if guest_type == 'lxc': @@ -14090,6 +14092,20 @@ def api_vm_apps_suggestions(vmid): return jsonify({'error': str(e)}), 500 +@app.route('/api/vms//apps/native-discovery', methods=['GET', 'POST']) +@require_auth +def api_vm_apps_native_discovery(vmid): + """Only the Find applications button requests POST. GET never probes.""" + import native_app_discovery + import lxc_apps + result = (native_app_discovery.discover(vmid) if request.method == 'POST' + else native_app_discovery.cached(vmid)) + suggestions = _vm_cache_get(_vm_app_suggestions_cache, vmid, _VM_APP_SUGGESTIONS_TTL) or {} + sidecar = lxc_apps.load_sidecar(vmid) or {} + return jsonify(native_app_discovery.filter_candidates( + result, suggestions, sidecar.get('apps') or [])) + + @app.route('/api/vms//apps/adguard-setup', methods=['GET']) @require_auth def api_vm_apps_adguard_setup(vmid): diff --git a/AppImage/scripts/lxc_apps.py b/AppImage/scripts/lxc_apps.py index 494c9db5..4b6a5a2e 100644 --- a/AppImage/scripts/lxc_apps.py +++ b/AppImage/scripts/lxc_apps.py @@ -1179,6 +1179,13 @@ def validate_config(payload: dict) -> tuple[bool, Any]: return _err("logo_url must be an http(s) URL (max 512 chars)") conf["logo_url"] = logo + # Native discovery identity is separate from a curated helper/updater. + discovery_id = payload.get("discovery_id") + if discovery_id is not None: + if not isinstance(discovery_id, str) or not re.fullmatch(r"native-[0-9a-f]{24}", discovery_id): + return _err("invalid discovery_id") + conf["discovery_id"] = discovery_id + # Optional helper_slug — set by the frontend when the user picks # an auto-detected app (primary or extra). Persisted so we can # filter the "also detected" chip list against apps already diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py new file mode 100644 index 00000000..a87dfc6e --- /dev/null +++ b/AppImage/scripts/native_app_discovery.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python3 +# ========================================================== +# ProxMenux - A menu-driven script for Proxmox VE management +# Author : VAIO73 +# GitHub : github.com/Vaso73 +# Maintainer : MacRimi +# Copyright : (c) 2026 MacRimi & contributors +# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE) +# Version : 1.0 +# Last Updated: 14/10/2026 +# ========================================================== +# Description: +# On-demand native LXC discovery from service and installation metadata. +# Reads bounded data only; never executes discovered programs, starts guests, +# registers applications or infers their update mechanism. +# ========================================================== +"""Separate from catalog detection: callers must explicitly request a scan.""" + +import copy +import fcntl +import hashlib +import json +import os +from pathlib import Path +import re +import selectors +import signal +import subprocess +import threading +import time + +TOTAL_SECONDS = 20 +PROBE_SECONDS = 3 +OUTPUT_BYTES = 64 * 1024 +MAX_RECORDS = 256 +MAX_CANDIDATES = 50 +CACHE_SECONDS = 300 +LOCAL_CONFIG = Path('/etc/pve/local/lxc') +OCI_ROOT = Path('/usr/local/share/proxmenux/oci/instances') +UPDATE_LOCK_ROOT = Path('/run/lock') +_cache = {} +_locks = {} +_mutex = threading.Lock() +_PATH = re.compile(r'/[A-Za-z0-9_.+/@-]{1,510}\Z') +_NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.+@-]{0,63}\Z') +_VERSION = re.compile(r'v?\d+(?:\.\d+){1,4}(?:[-+._][A-Za-z0-9.-]+)?\Z') +_SYSTEM = { + 'ssh', 'sshd', 'cron', 'crond', 'postfix', 'rsyslog', 'syslog', 'syslog-ng', + 'dbus', 'dbus-broker', 'systemd', 'udev', 'networking', 'network', 'networkmanager', + 'dhcpcd', 'dhclient', 'chrony', 'chronyd', 'ntp', 'ntpd', 'openntpd', 'agetty', + 'getty', 'login', 'polkit', 'polkitd', 'accounts-daemon', 'acpid', 'anacron', + 'docker', 'containerd', 'containerd-shim', 'podman', 'qemu-guest-agent', 'rngd', + 'auditd', 'logrotate', 'local', 'bootmisc', 'hostname', 'hwclock', 'modules', 'master', + 'proxmox-boot-sync', 'pve-container', 'pve-firewall', 'pvefw-logger', + 'pvedaemon', 'pvestatd', 'pveproxy', 'pvescheduler', 'spiceproxy', 'lxc', + 'mount-ro', 'mdev', 'sysctl', 'sysfs', 'devfs', 'cgroups', 'rc', 'rcs', 'swap', + 'sleep', 'true', 'false', 'echo', 'cat', 'head', 'tail', 'tee', 'logger', + 'apt', 'apt-get', 'dpkg', 'dpkg-query', 'apk', 'unattended-upgrades', + 'fstrim', 'fsck', 'fsckd', 'e2scrub', 'e2scrub-all', 'e2scrub-reap', + 'irqbalance', 'smartd', 'smartmontools', 'rpcbind', 'nfs-common', 'nfs-server', + 'rpc-statd', 'rpc-statd-notify', 'rpc-gssd', 'rpc-svcgssd', 'nftables', 'iptables', + 'ip6tables', 'firewalld', 'ufw', 'resolvconf', 'wpa-supplicant', 'modemmanager', + 'console-setup', 'keyboard-setup', 'plymouth', 'halt', 'reboot', 'shutdown', + 'rc-local', 'ntpsec', 'networkd-dispatcher', 'busybox', 'atd', 'uuidd', +} +_RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|sh|bash|dash|env|java|ruby|perl)\Z') + + +class ProbeError(Exception): + """Safe error code only; guest output must never become an API error.""" + + +def bounded_run(argv, deadline): + """Cap combined stdout/stderr while reading, and kill/reap the process group.""" + remaining = min(PROBE_SECONDS, deadline - time.monotonic()) + if remaining <= 0: + raise ProbeError('deadline') + end = time.monotonic() + remaining + try: + proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + start_new_session=True) + except OSError: + raise ProbeError('unavailable') from None + outputs = [bytearray(), bytearray()] + try: + with selectors.DefaultSelector() as poll: + for index, stream in enumerate((proc.stdout, proc.stderr)): + os.set_blocking(stream.fileno(), False) + poll.register(stream, selectors.EVENT_READ, index) + while poll.get_map(): + wait = end - time.monotonic() + if wait <= 0: + raise ProbeError('timeout') + for key, _ in poll.select(wait): + block = os.read(key.fileobj.fileno(), 4096) + if not block: + poll.unregister(key.fileobj) + continue + if sum(map(len, outputs)) + len(block) > OUTPUT_BYTES: + raise ProbeError('output_limit') + outputs[key.data].extend(block) + try: + proc.wait(timeout=max(0.001, end - time.monotonic())) + except subprocess.TimeoutExpired: + raise ProbeError('timeout') from None + if proc.returncode: + raise ProbeError('read_failed') + return outputs[0].decode('utf-8', errors='strict') + except UnicodeError: + raise ProbeError('invalid_data') from None + finally: + # Also clean up descendants that outlive a successfully exited wrapper. + try: + os.killpg(proc.pid, signal.SIGKILL) + except ProcessLookupError: + pass + proc.wait() + proc.stdout.close() + proc.stderr.close() + + +# These are fixed read-only scripts, not guest-supplied shell or init scripts. +# awk emits only executable/script paths and WorkingDirectory, never arguments +# or environment values. OpenRC files are parsed as data, never sourced. +SERVICE_SCRIPT = r''' +command -v awk >/dev/null && command -v head >/dev/null || exit 1 +n=0 +for dir in /etc/systemd/system /usr/lib/systemd/system /lib/systemd/system /etc/init.d; do + [ ! -d "$dir" ] || [ -r "$dir" ] || exit 1 + for f in "$dir"/*; do + [ -f "$f" ] || continue + case "$dir:$f" in /etc/init.d:*) kind=openrc;; *:*.service) kind=systemd;; *) continue;; esac + n=$((n+1)); [ "$n" -le 256 ] || { printf 'LIMIT\n'; exit 0; } + [ -r "$f" ] || exit 1 + content=$(timeout -s KILL 1 head -c 16385 "$f") || exit 1 + [ "${#content}" -le 16384 ] || { printf 'LIMIT\n'; exit 0; } + printf '%s\n' "$content" | awk -v kind="$kind" -v file="$f" ' + function clean(v) {gsub(/^[ \t]+|[ \t]+$/, "", v); gsub(/^["\047]|["\047]$/, "", v); return v} + /^ExecStart=/ {v=substr($0,11); sub(/^[-+!:@]+/, "", v); split(v,a,/[ \t]+/); exe=clean(a[1]); script=clean(a[2]); if(script !~ /^\//) script=""} + /^WorkingDirectory=/ {dir=clean(substr($0,18))} + /^command=/ {exe=clean(substr($0,9))} + /^directory=/ {dir=clean(substr($0,11))} + /^command_args=/ {v=clean(substr($0,14)); split(v,a,/[ \t]+/); script=clean(a[1]); if(script !~ /^\//) script=""} + END {printf "S\t%s\t%s\t%s\t%s\t%s\n", file, kind, exe, script, dir}' + done +done +printf 'END\n' +''' +PROCESS_SCRIPT = r''' +command -v readlink >/dev/null && command -v head >/dev/null || exit 1 +n=0 +for dir in /proc/[0-9]*; do + [ -d "$dir" ] || continue + n=$((n+1)); [ "$n" -le "$1" ] || { printf 'LIMIT\n'; exit 0; } + # Never inspect Docker/OCI child workloads or read cmdline/environ. + [ -d "$dir" ] || continue + [ -r "$dir/cgroup" ] || exit 1 + group=$(timeout -s KILL 1 head -c 8193 "$dir/cgroup") || exit 1 + [ "${#group}" -le 8192 ] || { printf 'LIMIT\n'; exit 0; } + case "$group" in *docker*|*libpod*|*kubepods*|*containerd*) continue;; esac + # Kernel threads and vanished processes have no application executable. + [ -L "$dir/exe" ] || continue + exe=$(timeout -s KILL 1 readlink "$dir/exe") || continue + cwd=$(timeout -s KILL 1 readlink "$dir/cwd") || continue + printf 'P\t%s\t%s\t%s\n' "${dir##*/}" "$exe" "$cwd" +done +printf 'END\n' +''' +PORT_SCRIPT = r''' +command -v awk >/dev/null || exit 1 +if command -v ss >/dev/null; then + (timeout -s KILL 1 ss -H -lntp || printf 'FAILED\n') | awk -v cap="$1" ' + /^FAILED$/ {failed=1; exit} + NR > cap {limited=1; exit} + {port=$4; sub(/^.*:/,"",port); rest=$0 + # More than one owner is ambiguous; never choose the first arbitrarily. + if (gsub(/pid=/,"pid=",rest)!=1) next + if (match(rest,/pid=[0-9]+/)) {pid=substr(rest,RSTART+4,RLENGTH-4); printf "L\t%s\t%s\n",pid,port}} + END {print failed ? "FAILED" : (limited ? "LIMIT" : "END")}' +elif command -v netstat >/dev/null; then + (timeout -s KILL 1 netstat -lntp || printf 'FAILED\n') | awk -v cap="$1" ' + /^FAILED$/ {failed=1; exit} + /^tcp/ {n++; if(n>cap) {limited=1; exit}; port=$4; sub(/^.*:/,"",port) + pid=$7; sub(/\/.*$/,"",pid); if(pid ~ /^[0-9]+$/) printf "L\t%s\t%s\n",pid,port} + END {print failed ? "FAILED" : (limited ? "LIMIT" : "END")}' +else + exit 1 +fi +''' +FOOTPRINT_SCRIPT = r''' +[ -f "$1" ] && [ -r "$1" ] || exit 1 +printf 'FILE\n' +if [ -f "$2/package.json" ]; then + [ -r "$2/package.json" ] || exit 1 + timeout -s KILL 1 head -c 8192 "$2/package.json" +fi +''' + + +def _path(value): + return bool(isinstance(value, str) and _PATH.fullmatch(value) + and '..' not in value.split('/')) + + +def _system(name): + raw = name.lower().removesuffix('.service') + stem = raw.split('@')[0] + return raw.startswith('user@') or stem in _SYSTEM or stem.startswith(( + 'systemd-', 'dbus-', 'getty', 'containerd-', 'apt-', 'dpkg-', 'e2scrub', + 'unattended-', 'ifup', 'ifdown', 'user@', 'btrfs-', 'lvm2-', 'lxc-')) + + +def _custom(path): + return _path(path) and path.startswith(('/opt/', '/srv/', '/usr/local/')) + + +def parse_records(text, kind): + lines = text.splitlines() + if not lines or lines[-1] not in ('END', 'LIMIT'): + raise ProbeError('incomplete') + if len(lines) - 1 > MAX_RECORDS: + raise ProbeError('metadata_limit') + records = [] + seen_services = set() + for line in lines[:-1]: + parts = line.split('\t') + if kind == 'S' and len(parts) == 6 and parts[0] == 'S': + _, source, manager, exe, script, directory = parts + if not _path(source) or manager not in ('systemd', 'openrc'): + raise ProbeError('invalid_data') + service = Path(source).name.removesuffix('.service') + if service in seen_services: + continue # /etc overrides vendor units; no duplicate installations. + seen_services.add(service) + if not _NAME.fullmatch(service) or _system(service) or not _path(exe): + continue + runtime = bool(_RUNTIMES.fullmatch(Path(exe).name)) + install = script if runtime else exe + if (not _path(install) or _system(Path(install).name) + or install.startswith(('/usr/lib/systemd/', '/lib/systemd/', + '/usr/lib/udev/', '/lib/udev/', '/usr/lib/apt/'))): + continue + root = directory if _custom(directory) else str(Path(install).parent) + # Generic runtime without an absolute application script is ambiguous. + if runtime and not _custom(install): + continue + records.append({'name': service, 'path': install, 'root': root, + 'service': source, 'manager': manager, 'executable': exe}) + elif kind == 'P' and len(parts) == 4 and parts[0] == 'P': + _, pid, exe, directory = parts + if not pid.isdigit() or not _path(exe) or not _path(directory): + continue + records.append({'pid': pid, 'executable': exe, 'root': directory}) + elif kind == 'L' and len(parts) == 3 and parts[0] == 'L': + _, pid, port = parts + if pid.isdigit() and port.isdigit() and 0 < int(port) < 65536: + records.append({'pid': pid, 'port': int(port)}) + else: + raise ProbeError('invalid_data') + else: + raise ProbeError('invalid_data') + return records, lines[-1] == 'LIMIT', len(lines) - 1 + + +def candidate(vmid, record, footprint): + if not footprint.startswith('FILE\n'): + raise ProbeError('invalid_data') + version, metadata_issue = None, None + metadata = footprint[5:].strip() + if metadata: + try: + manifest = json.loads(metadata) + except (ValueError, TypeError): + manifest = None + # Only the whitelisted version field leaves this function. Never retain + # manifest scripts, repository guesses, config or arbitrary extra fields. + if not isinstance(manifest, dict): + metadata_issue = 'invalid_metadata' + manifest = {} + value = manifest.get('version') + if (isinstance(value, str) and len(value) <= 64 and _VERSION.fullmatch(value) + and record['path'].startswith(record['root'].rstrip('/') + '/')): + version = value + identity = 'native-' + hashlib.sha256( + f'{int(vmid)}:{record["path"]}:{record["root"]}'.encode()).hexdigest()[:24] + evidence = [{'kind': 'installation_file', 'value': record['path']}] + if record.get('service'): + evidence.append({'kind': record['manager'], 'value': record['service']}) + else: + evidence.extend([{'kind': 'process_executable', 'value': record['process']}, + {'kind': 'working_directory', 'value': record['root']}]) + ports = sorted(set(record.get('listening_ports') or [])) + evidence.extend({'kind': 'tcp_listener', 'value': str(port)} for port in ports) + return {'slug': identity, 'discovery_id': identity, 'source': 'native_metadata', + 'name': record['name'], 'installed_version': version, 'evidence': evidence, + 'installation_path': record['path'], 'default_ports': [], + 'listening_ports': ports, + 'metadata_issue': metadata_issue, + 'tracking_suggestion': ({'installed_via': 'manual', 'installed_version': version, + 'detected_version': version, 'detector_verified': True} + if version else None)} + + +def _guard(vmid, deadline, run): + # The local node path rejects QEMU, missing and remote CTs before pct exec. + path = LOCAL_CONFIG / f'{vmid}.conf' + if not path.is_file(): + raise ProbeError('not_local_lxc') + # Presence, including a damaged/in-progress record, excludes generic probes. + try: + os.lstat(OCI_ROOT / str(vmid)) + except FileNotFoundError: + pass + except OSError: + raise ProbeError('oci_unknown') from None + else: + raise ProbeError('managed_oci') + try: + with path.open('rb') as handle: + raw = handle.read(OUTPUT_BYTES + 1) + if len(raw) > OUTPUT_BYTES: + raise ProbeError('config_limit') + config = raw.decode('utf-8') + except (OSError, UnicodeError): + raise ProbeError('config_unavailable') from None + if re.search(r'^lock:', config, re.M): + raise ProbeError('guest_busy') + update_lock = UPDATE_LOCK_ROOT / f'proxmenux-lxc-update-{vmid}.lock' + try: + with update_lock.open('rb') as handle: + try: + fcntl.flock(handle, fcntl.LOCK_SH | fcntl.LOCK_NB) + except BlockingIOError: + raise ProbeError('guest_busy') from None + finally: + fcntl.flock(handle, fcntl.LOCK_UN) + except FileNotFoundError: + pass + except OSError: + raise ProbeError('lock_unavailable') from None + if re.search(r'^ostype:\s*unmanaged\s*$', config, re.M): + raise ProbeError('unsupported_guest') + if not re.search(r'^ostype:\s*(?:debian|ubuntu|alpine|archlinux|centos|fedora|opensuse|gentoo)\s*$', config, re.M): + raise ProbeError('unsupported_guest') + status = run(['pct', 'status', str(vmid)], deadline).strip() + if status != 'status: running': + raise ProbeError('guest_stopped' if status == 'status: stopped' else 'status_unknown') + + +def scan(vmid, run=bounded_run): + deadline = time.monotonic() + TOTAL_SECONDS + _guard(vmid, deadline, run) + records, issues, consumed, processes, listeners = [], [], 0, [], [] + for kind, script in (('S', SERVICE_SCRIPT), ('P', PROCESS_SCRIPT), ('L', PORT_SCRIPT)): + try: + if consumed >= MAX_RECORDS: + issues.append('metadata_limit') + break + parsed, limited, count = parse_records(run( + ['pct', 'exec', str(vmid), '--', 'timeout', '-s', 'KILL', '2', + 'sh', '-c', script, 'discovery', str(MAX_RECORDS - consumed)], deadline), kind) + if consumed + count > MAX_RECORDS: + issues.append('metadata_limit') + parsed = parsed[:MAX_RECORDS - consumed] + consumed = min(MAX_RECORDS, consumed + count) + # The entire collector, not each metadata source, has 256 records. + capacity = MAX_RECORDS - len(records) + if kind == 'S': + records.extend(parsed[:capacity]) + elif kind == 'P': + processes = parsed[:capacity] + else: + listeners = parsed[:capacity] + if limited or len(parsed) > capacity: + issues.append('metadata_limit') + except ProbeError as exc: + issues.append(str(exc)) + for process in processes: + exe, directory = process['executable'], process['root'] + related = [record for record in records if record['executable'] == exe and ( + not _RUNTIMES.fullmatch(Path(exe).name) or record['root'] == directory)] + ports = [entry['port'] for entry in listeners if entry['pid'] == process['pid']] + if len(related) == 1: + related[0].setdefault('listening_ports', []).extend(ports) + elif not related and (_custom(exe) and _custom(directory) + and not _system(Path(exe).name) + and not _RUNTIMES.fullmatch(Path(exe).name) + and exe.startswith(directory.rstrip('/') + '/') + and _NAME.fullmatch(Path(exe).name)): + records.append({'name': Path(exe).name, 'path': exe, 'root': directory, + 'process': exe, 'executable': exe, 'listening_ports': ports}) + items, seen = [], set() + for record in records: + key = (record['path'], record['root']) + if key in seen: + continue + seen.add(key) + if len(items) >= MAX_CANDIDATES: + issues.append('candidate_limit') + break + try: + footprint = run(['pct', 'exec', str(vmid), '--', 'timeout', '-s', 'KILL', '2', + 'sh', '-c', FOOTPRINT_SCRIPT, + 'discovery', record['path'], record['root']], deadline) + item = candidate(vmid, record, footprint) + items.append(item) + if item['metadata_issue']: + issues.append(item['metadata_issue']) + except ProbeError as exc: + issues.append(str(exc)) + if str(exc) == 'deadline': + break + return {'vmid': vmid, 'status': 'partial' if issues else 'complete', + 'candidates': items, 'issues': sorted(set(issues)), 'stale': False, + 'scanned_at': time.time()} + + +def cached(vmid): + with _mutex: + value = copy.deepcopy(_cache.get(int(vmid))) + if value is None: + return {'vmid': int(vmid), 'status': 'not_scanned', 'candidates': [], 'stale': False} + if time.time() - value['scanned_at'] >= CACHE_SECONDS: + value['stale'] = True + return value + + +def mark_stale(vmid): + """Lifecycle events retire freshness, never collect new guest metadata.""" + with _mutex: + if int(vmid) in _cache: + _cache[int(vmid)]['stale'] = True + + +def discover(vmid): + vmid = int(vmid) + with _mutex: + lock = _locks.setdefault(vmid, threading.Lock()) + if not lock.acquire(blocking=False): + return {**cached(vmid), 'status': 'busy', 'stale': True} + try: + try: + result = scan(vmid) + except ProbeError as exc: + result = {**cached(vmid), 'status': 'error', 'issues': [str(exc)], 'stale': True} + result.setdefault('scanned_at', time.time()) + with _mutex: + _cache[vmid] = copy.deepcopy(result) + return result + previous = cached(vmid) + if result['status'] != 'complete' and previous['candidates']: + # Partial output cannot prove that previously seen apps disappeared. + ids = {item['discovery_id'] for item in result['candidates']} + result['candidates'].extend(item for item in previous['candidates'] + if item['discovery_id'] not in ids) + result['candidates'] = result['candidates'][:MAX_CANDIDATES] + result['stale'] = True + with _mutex: + _cache[vmid] = copy.deepcopy(result) + return result + finally: + lock.release() + + +def filter_candidates(result, suggestions, apps): + """Pure composition: never run catalog probes, load or write registries.""" + known = [*apps, *(suggestions.get('extras') or []), + *(suggestions.get('docker_workloads') or [])] + if suggestions.get('helper_slug'): + known.append({'name': suggestions.get('name_suggestion'), + 'helper_slug': suggestions['helper_slug'], + 'tracking_suggestion': suggestions.get('tracking_suggestion')}) + identities = {a.get('discovery_id') for a in known if a.get('discovery_id')} + names, paths = set(), set() + for app in known: + for field in ('name', 'slug', 'helper_slug', 'package'): + if app.get(field): + names.add(str(app[field]).casefold()) + detector = app.get('tracking_suggestion') or app + for field in ('binary_path', 'file_path'): + if detector.get(field): + paths.add(detector[field]) + items = [item for item in result['candidates'] + if item['discovery_id'] not in identities + and item['name'].casefold() not in names + and item['installation_path'] not in paths] + return {**result, 'candidates': items} diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py new file mode 100644 index 00000000..cc1539b1 --- /dev/null +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -0,0 +1,441 @@ +"""Host-independent discovery fixtures; no real pct, guest, auth or registry.""" +import ast +import copy +import fcntl +from functools import wraps +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import threading +import time +import unittest +from unittest.mock import Mock, patch + +SCRIPTS = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(SCRIPTS)) +import native_app_discovery as native +import lxc_apps + + +def service(name='handmade', exe='/opt/handmade/server', directory='/opt/handmade', manager='systemd', script=''): + path = ('/etc/systemd/system/' + name + '.service' if manager == 'systemd' + else '/etc/init.d/' + name) + return '\t'.join(['S', path, manager, exe, script, directory]) + '\nEND\n' + + +class DiscoveryFixtures(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.config = self.root / 'lxc' + self.config.mkdir() + (self.config / '101.conf').write_text('ostype: debian\n') + self.oci = self.root / 'oci' + self.oci.mkdir() + for context in (patch.object(native, 'LOCAL_CONFIG', self.config), + patch.object(native, 'OCI_ROOT', self.oci), + patch.object(native, 'UPDATE_LOCK_ROOT', self.root)): + context.start() + self.addCleanup(context.stop) + native._cache.clear() + native._locks.clear() + self.calls = [] + + def runner(self, services=None, processes='END\n', footprint='FILE\n', status='status: running', ports='END\n'): + def run(argv, deadline): + self.calls.append(argv) + self.assertLessEqual(deadline - time.monotonic(), native.TOTAL_SECONDS) + if argv[1] == 'status': + return status + self.assertEqual(argv[:6], ['pct', 'exec', '101', '--', 'timeout', '-s']) + self.assertEqual(argv[6:9], ['KILL', '2', 'sh']) + script = argv[10] + if script == native.SERVICE_SCRIPT: + return services if services is not None else service() + if script == native.PROCESS_SCRIPT: + return processes + if script == native.PORT_SCRIPT: + return ports + if script == native.FOOTPRINT_SCRIPT: + return footprint + self.fail('Unexpected probe') + return run + + def test_unknown_version_is_present_with_evidence_and_no_capabilities(self): + result = native.scan(101, self.runner()) + self.assertEqual(result['status'], 'complete') + item, = result['candidates'] + self.assertIsNone(item['installed_version']) + self.assertIsNone(item['tracking_suggestion']) + self.assertEqual(item['default_ports'], []) + self.assertEqual({e['kind'] for e in item['evidence']}, {'installation_file', 'systemd'}) + for field in ('helper_slug', 'repo', 'update_command', 'binary_path', 'upstream_type'): + self.assertNotIn(field, item) + + def test_openrc_node_script_known_version_is_manual_and_secret_free(self): + result = native.scan(101, self.runner( + service(exe='/usr/bin/node', script='/opt/handmade/server.js', manager='openrc'), + footprint='FILE\n' + json.dumps({'version': '1.2.3', 'scripts': {'start': 'SECRET'}, 'env': 'PASSWORD'}))) + item, = result['candidates'] + self.assertEqual(item['installed_version'], '1.2.3') + self.assertEqual(item['tracking_suggestion']['installed_via'], 'manual') + self.assertNotIn('SECRET', json.dumps(result)) + self.assertNotIn('PASSWORD', json.dumps(result)) + self.assertFalse(any('systemctl' in arg for call in self.calls for arg in call)) + + def test_inactive_service_and_packaged_application_are_not_hidden(self): + result = native.scan(101, self.runner(service(name='legit-app', exe='/usr/bin/legit-app', directory=''))) + self.assertEqual(result['candidates'][0]['name'], 'legit-app') + + def test_system_services_and_ambiguous_runtimes_have_zero_footprint_probes(self): + for name, exe in [('ssh', '/usr/sbin/sshd'), ('cron', '/usr/sbin/cron'), + ('postfix', '/usr/lib/postfix/master'), ('rsyslog', '/usr/sbin/rsyslogd'), + ('systemd-networkd', '/usr/lib/systemd/systemd-networkd'), + ('ambiguous', '/usr/bin/python3')]: + self.calls.clear() + result = native.scan(101, self.runner(service(name=name, exe=exe))) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + + def test_missing_remote_qemu_stopped_busy_oci_guards_precede_exec(self): + scenarios = ['missing', 'stopped', 'busy', 'oci', 'invalid-oci', 'unmanaged'] + for scenario in scenarios: + with self.subTest(scenario=scenario): + (self.config / '101.conf').write_text('ostype: debian\n') + if scenario == 'missing': + (self.config / '101.conf').unlink() + if scenario == 'busy': + (self.config / '101.conf').write_text('lock: backup\n') + if scenario == 'unmanaged': + (self.config / '101.conf').write_text('ostype: unmanaged\n') + if 'oci' in scenario: + (self.oci / '101').mkdir(exist_ok=True) + (self.oci / '101' / 'oci-compose.json').write_text('broken') + self.calls.clear() + with self.assertRaises(native.ProbeError): + native.scan(101, self.runner(status='status: stopped' if scenario == 'stopped' else 'status: running')) + self.assertFalse(any(call[1] == 'exec' for call in self.calls)) + if (self.oci / '101').exists(): + (self.oci / '101' / 'oci-compose.json').unlink() + (self.oci / '101').rmdir() + + def test_failure_and_partial_output_are_not_successful_empty_results(self): + for text in ('', 'malformed\nEND\n', 'S\tpartial', 'LIMIT\n'): + result = native.scan(101, self.runner(services=text)) + self.assertEqual(result['status'], 'partial') + run = self.runner() + def failed(argv, deadline): + if argv[1] == 'exec': + raise native.ProbeError('read_failed') + return run(argv, deadline) + self.assertEqual(native.scan(101, failed)['status'], 'partial') + + def test_optional_invalid_version_metadata_keeps_proven_candidate(self): + result = native.scan(101, self.runner(footprint='FILE\n{broken')) + self.assertEqual(result['status'], 'partial') + item, = result['candidates'] + self.assertIsNone(item['installed_version']) + self.assertIsNone(item['tracking_suggestion']) + + def test_working_directory_manifest_does_not_version_an_unrelated_executable(self): + result = native.scan(101, self.runner(service(exe='/opt/other/server'), + footprint='FILE\n{"version":"1.2.3"}')) + self.assertIsNone(result['candidates'][0]['installed_version']) + + def test_path_injection_and_port_only_data_do_not_become_candidates(self): + for text in (service(exe='/opt/app/$(id)'), service(exe='/opt/../server'), + service(name='app;id'), service(exe='/bin/sh')): + self.assertEqual(native.scan(101, self.runner(text))['candidates'], []) + with self.assertRaises(native.ProbeError): + native.parse_records('PORT\t8080\nEND\n', 'P') + + def test_same_process_deduplicates_but_two_apps_using_node_do_not_merge(self): + services = service(exe='/usr/bin/node', script='/opt/handmade/server.js').removesuffix('END\n') + services += service(name='second', exe='/usr/bin/node', script='/opt/second/server.js', directory='/opt/second') + result = native.scan(101, self.runner(services)) + self.assertEqual(len(result['candidates']), 2) + result = native.scan(101, self.runner(processes='P\t123\t/opt/handmade/server\t/opt/handmade\nEND\n')) + self.assertEqual(len(result['candidates']), 1) + other = native.candidate(102, native.parse_records(service(), 'S')[0][0], 'FILE\n') + self.assertNotEqual(other['discovery_id'], result['candidates'][0]['discovery_id']) + + def test_record_and_candidate_caps(self): + too_many = 'P\t123\t/opt/a/app\t/opt/a\n' * 257 + 'END\n' + with self.assertRaises(native.ProbeError): + native.parse_records(too_many, 'P') + services = ''.join(service(name=f'app{i}', exe=f'/opt/app{i}/server', directory=f'/opt/app{i}').removesuffix('END\n') for i in range(51)) + 'END\n' + result = native.scan(101, self.runner(services)) + self.assertEqual(len(result['candidates']), 50) + self.assertIn('candidate_limit', result['issues']) + + def test_cached_ttl_never_runs_scan_and_error_preserves_last_known(self): + complete = native.scan(101, self.runner()) + with patch.object(native, 'scan', return_value=complete) as scan: + native.discover(101) + for age, stale in ((299.9, False), (300, True), (300.1, True)): + with patch.object(native.time, 'time', return_value=complete['scanned_at'] + age): + self.assertEqual(native.cached(101)['stale'], stale) + self.assertEqual(scan.call_count, 1) + with patch.object(native, 'scan', side_effect=native.ProbeError('timeout')): + result = native.discover(101) + self.assertEqual(result['candidates'], complete['candidates']) + self.assertEqual(native.cached(101)['status'], 'error') + self.assertTrue(native.cached(101)['stale']) + partial = {**complete, 'status': 'partial', 'candidates': [], 'issues': ['read_failed']} + with patch.object(native, 'scan', return_value=partial): + self.assertEqual(native.discover(101)['candidates'], complete['candidates']) + with patch.object(native, 'scan', return_value={**complete, 'candidates': []}): + self.assertEqual(native.discover(101)['candidates'], []) + + def test_same_ct_busy_and_different_ct_cache_isolation(self): + lock = threading.Lock() + lock.acquire() + native._locks[101] = lock + with patch.object(native, 'scan') as scanner: + self.assertEqual(native.discover(101)['status'], 'busy') + scanner.assert_not_called() + self.assertEqual(native.cached(102)['status'], 'not_scanned') + + def test_filter_registered_curated_and_docker_children_without_mutation(self): + result = native.scan(101, self.runner()) + original = copy.deepcopy(result) + for suggestions, apps in (({}, [{'discovery_id': result['candidates'][0]['discovery_id']}]), + ({'extras': [{'name': 'Handmade'}]}, []), + ({'docker_workloads': [{'name': 'Handmade'}]}, []), + ({'helper_slug': 'handmade', 'name_suggestion': 'Handmade'}, [])): + self.assertEqual(native.filter_candidates(result, suggestions, apps)['candidates'], []) + self.assertEqual(result, original) + + def test_actual_save_first_check_and_recheck_never_probe_or_configure_updater(self): + with patch.object(lxc_apps, '_APPS_DIR', str(self.root / 'apps')), \ + patch.object(lxc_apps, '_pct_exec', side_effect=AssertionError('forbidden guest execution')) as execute, \ + patch.object(lxc_apps, 'fetch_latest_upstream_details', side_effect=AssertionError('forbidden upstream')) as upstream: + for version in (None, '1.2.3'): + item = native.candidate(101, native.parse_records(service(), 'S')[0][0], + 'FILE\n' + (json.dumps({'version': version}) if version else '')) + payload = {'name': item['name'], 'discovery_id': item['discovery_id'], + 'update_method': 'none', **(item['tracking_suggestion'] or {})} + ok, sidecar = lxc_apps.add_app(101, payload) + self.assertTrue(ok, sidecar) + app = sidecar['apps'][-1] + self.assertEqual(app['state']['installed_version'], version) + self.assertIsNone(app['state']['update_available']) + self.assertEqual(app['update_method'], 'none') + for field in ('helper_slug', 'update_command', 'repo', 'upstream_type'): + self.assertNotIn(field, app) + lxc_apps.check_app(101, app['id'], force=True) + self.assertIsNone(lxc_apps.get_schedule(101)) + self.assertIsNone(lxc_apps.get_bulk_update(101)) + execute.assert_not_called() + upstream.assert_not_called() + + def test_failed_save_has_no_first_check(self): + with patch.object(lxc_apps, '_write_sidecar', return_value=False), \ + patch.object(lxc_apps, '_read_sidecar', return_value=None), \ + patch.object(lxc_apps, 'check_app') as check: + self.assertFalse(lxc_apps.add_app(101, {'name': 'handmade'})[0]) + check.assert_not_called() + + def test_active_update_lock_blocks_all_guest_probes_without_writing(self): + path = self.root / 'proxmenux-lxc-update-101.lock' + path.write_bytes(b'unchanged') + with path.open('rb') as handle: + fcntl.flock(handle, fcntl.LOCK_EX) + with self.assertRaisesRegex(native.ProbeError, 'guest_busy'): + native.scan(101, self.runner()) + self.assertEqual(path.read_bytes(), b'unchanged') + self.assertEqual(self.calls, []) + + def test_actual_service_parser_reads_data_never_executes_or_sources_it(self): + for manager in ('systemd', 'openrc'): + directory = self.root / manager + directory.mkdir() + marker = self.root / ('forbidden-' + manager) + unit = directory / ('handmade.service' if manager == 'systemd' else 'handmade') + content = ('[Service]\nExecStart=/usr/bin/node /opt/handmade/server.js --password SECRET\nWorkingDirectory=/opt/handmade\n' + if manager == 'systemd' else 'command="/usr/bin/node"\ncommand_args="/opt/handmade/server.js --password SECRET"\ndirectory="/opt/handmade"\n') + unit.write_text(content + '\ntouch ' + str(marker) + '\n') + script = native.SERVICE_SCRIPT.replace('/etc/systemd/system /usr/lib/systemd/system /lib/systemd/system /etc/init.d', str(directory)) + # Substitute only fixture paths, not the collector/parser behavior. + if manager == 'openrc': + script = script.replace('/etc/init.d:*)', str(directory) + ':*)') + output = native.bounded_run(['sh', '-c', script], time.monotonic() + 3) + record, = native.parse_records(output, 'S')[0] + self.assertEqual(record['path'], '/opt/handmade/server.js') + self.assertNotIn('SECRET', output) + self.assertFalse(marker.exists()) + + def test_actual_process_parser_skips_docker_and_never_reads_environ_cmdline(self): + proc = self.root / 'proc' + proc.mkdir() + for pid, group in ((1, '0::/\n'), (2, '0::/docker/child\n')): + directory = proc / str(pid) + directory.mkdir() + (directory / 'cgroup').write_text(group) + (directory / 'exe').symlink_to('/opt/handmade/server') + (directory / 'cwd').symlink_to('/opt/handmade') + (directory / 'cmdline').write_text('SECRET') + (directory / 'environ').write_text('PASSWORD') + script = native.PROCESS_SCRIPT.replace('/proc/[0-9]*', str(proc) + '/[0-9]*') + output = native.bounded_run(['sh', '-c', script, 'fixture', '256'], time.monotonic() + 3) + self.assertEqual(output, 'P\t1\t/opt/handmade/server\t/opt/handmade\nEND\n') + self.assertNotIn('"$dir/cmdline"', native.PROCESS_SCRIPT) + self.assertNotIn('"$dir/environ"', native.PROCESS_SCRIPT) + self.assertNotIn('SECRET', output) + + def test_ports_require_exact_process_ownership_and_do_not_infer_http(self): + process = 'P\t123\t/opt/handmade/server\t/opt/handmade\nEND\n' + result = native.scan(101, self.runner(processes=process, ports='L\t123\t8080\nL\t999\t9000\nEND\n')) + item, = result['candidates'] + self.assertEqual(item['listening_ports'], [8080]) + self.assertEqual(item['default_ports'], [], 'TCP listener does not prove HTTP') + result = native.scan(101, self.runner(ports='L\t123\t8080\nEND\n')) + self.assertEqual(result['candidates'][0]['listening_ports'], []) + + def test_shared_runtime_ports_are_assigned_only_by_working_directory(self): + services = service(exe='/usr/bin/node', script='/opt/handmade/server.js').removesuffix('END\n') + services += service(name='second', exe='/usr/bin/node', script='/opt/second/server.js', directory='/opt/second') + process = 'P\t123\t/usr/bin/node\t/opt/second\nEND\n' + result = native.scan(101, self.runner(services, processes=process, ports='L\t123\t8080\nEND\n')) + self.assertEqual(result['candidates'][0]['listening_ports'], []) + self.assertEqual(result['candidates'][1]['listening_ports'], [8080]) + + def test_ss_and_alpine_netstat_parser_are_bounded_and_secret_free(self): + for tool, line in (('ss', 'LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("SECRET",pid=123,fd=7))'), + ('netstat', 'tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 123/SECRET')): + bin_dir = self.root / tool + bin_dir.mkdir() + executable = bin_dir / tool + executable.write_text('#!/bin/sh\nprintf \'%s\\n\' \'' + line + '\'\n') + executable.chmod(0o755) + (bin_dir / 'awk').symlink_to('/usr/bin/awk') + (bin_dir / 'timeout').symlink_to('/usr/bin/timeout') + script = 'PATH=' + str(bin_dir) + '\n' + native.PORT_SCRIPT + output = native.bounded_run(['sh', '-c', script, 'fixture', '256'], time.monotonic() + 3) + self.assertEqual(output, 'L\t123\t8080\nEND\n') + self.assertNotIn('SECRET', output) + executable.write_text('#!/bin/sh\nexit 1\n') + output = native.bounded_run(['sh', '-c', script, 'fixture', '256'], time.monotonic() + 3) + with self.assertRaises(native.ProbeError): + native.parse_records(output, 'L') + + +class BoundedRunner(unittest.TestCase): + def test_output_limit_is_enforced_during_read(self): + with self.assertRaisesRegex(native.ProbeError, 'output_limit'): + native.bounded_run([sys.executable, '-c', 'import os; os.write(2,b"x"*1000000)'], time.monotonic() + 3) + + def test_pipe_holding_child_does_not_bypass_timeout(self): + start = time.monotonic() + with self.assertRaisesRegex(native.ProbeError, 'timeout'): + native.bounded_run([sys.executable, '-c', 'import os,time; os.fork(); time.sleep(10)'], start + .15) + self.assertLess(time.monotonic() - start, .8) + + def test_failures_never_reveal_stderr_and_deadline_blocks_spawn(self): + with self.assertRaisesRegex(native.ProbeError, '^read_failed$'): + native.bounded_run([sys.executable, '-c', 'import sys; sys.stderr.write("SECRET"); sys.exit(1)'], time.monotonic() + 3) + with patch.object(native.subprocess, 'Popen') as launch: + with self.assertRaisesRegex(native.ProbeError, 'deadline'): + native.bounded_run(['forbidden'], time.monotonic() - 1) + launch.assert_not_called() + + +class Routes(unittest.TestCase): + """Execute actual Flask route bodies; fixture auth, no server startup jobs.""" + def setUp(self): + from flask import Flask, request, jsonify, g + self.app = Flask('native-fixture') + self.authorized = True + def require_auth(fn): + @wraps(fn) + def wrapped(*args, **kwargs): + if not self.authorized: + return jsonify({'error': 'auth_required'}), 401 + return fn(*args, **kwargs) + return wrapped + self.globals = {'app': self.app, 'request': request, 'jsonify': jsonify, + 'require_auth': require_auth, '_vm_app_suggestions_cache': {}, + '_VM_APP_SUGGESTIONS_TTL': 300, + '_vm_cache_get': lambda cache, vmid, ttl: cache.get(vmid), + '_vm_cache_put': lambda cache, vmid, value: cache.update({vmid: value}), + '_pvesh_cache': {}, '_vm_disk_cache': {}, '_vm_cache_invalidate': Mock(), + '_invalidate_lxc_ip': Mock(), '_schedule_started_guest_refresh': Mock(), + '_publish_guest_modal_cache_revision': Mock()} + tree = ast.parse((SCRIPTS / 'flask_server.py').read_text()) + routes = [n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name in ( + 'api_vm_apps_suggestions', 'api_vm_apps_native_discovery', 'api_vm_apps_add', + '_handle_guest_lifecycle')] + self.assertEqual(len(routes), 4) + exec(compile(ast.Module(body=routes, type_ignores=[]), str(SCRIPTS / 'flask_server.py'), 'exec'), self.globals) + self.client = self.app.test_client() + + def test_explicit_post_only_get_and_existing_post_never_native_scan(self): + result = {'vmid': 101, 'status': 'complete', 'candidates': [], 'stale': False} + with patch.object(native, 'discover', return_value=result) as scan, \ + patch.object(native, 'cached', return_value=result), \ + patch.object(lxc_apps, 'get_suggestions', return_value={'extras': []}) as suggestions, \ + patch.object(lxc_apps, 'load_sidecar', return_value={'apps': []}), \ + patch.object(lxc_apps, '_write_sidecar') as write: + self.assertEqual(self.client.get('/api/vms/101/apps/native-discovery').status_code, 200) + self.client.get('/api/vms/101/apps/suggestions') + # OCI refresh and Find's existing catalog path retain their purpose. + self.client.post('/api/vms/101/apps/suggestions') + with self.app.test_request_context('/api/vms/101/apps/suggestions'): + from flask import g + g._internal_call = True + self.globals['_vm_app_suggestions_cache'].clear() + self.globals['api_vm_apps_suggestions'](101) + scan.assert_not_called() + self.client.post('/api/vms/101/apps/native-discovery') + scan.assert_called_once_with(101) + self.assertEqual(suggestions.call_count, 2) + write.assert_not_called() + + def test_unauthorized_and_wrong_method_have_zero_scans(self): + with patch.object(native, 'discover') as scan, patch.object(lxc_apps, 'load_sidecar') as read: + self.authorized = False + self.assertEqual(self.client.post('/api/vms/101/apps/native-discovery').status_code, 401) + self.assertEqual(self.client.put('/api/vms/101/apps/native-discovery').status_code, 405) + scan.assert_not_called() + read.assert_not_called() + + def test_actual_lifecycle_marks_last_known_stale_without_scanning(self): + native._cache[101] = {'vmid': 101, 'status': 'complete', 'stale': False, + 'candidates': [], 'scanned_at': time.time()} + with patch.object(native, 'discover') as scan, \ + patch.object(lxc_apps, 'clear_schedule_reboot_required') as clear: + for action in ('start', 'reboot', 'stop'): + self.globals['_handle_guest_lifecycle']('101', 'lxc', action) + self.assertTrue(native.cached(101)['stale']) + scan.assert_not_called() + self.assertEqual(clear.call_count, 2) + + def test_actual_repeated_prewarmer_has_zero_native_scans(self): + tree = ast.parse((SCRIPTS / 'flask_server.py').read_text()) + warmer = next(node for node in tree.body if isinstance(node, ast.FunctionDef) + and node.name == '_vm_modal_prewarmer_pass') + environment = dict(self.globals) + for name in ('_vm_details_cache', '_vm_backups_cache', '_vm_schedule_cache', '_vm_mounts_cache'): + environment[name] = {} + for name in ('_VM_DETAILS_TTL', '_VM_BACKUPS_TTL', '_VM_SCHEDULE_TTL', '_VM_MOUNTS_TTL'): + environment[name] = 300 + for name in ('get_vm_config', 'api_vm_backups', 'api_vm_apps_schedule', 'api_lxc_mount_points'): + environment[name] = lambda vmid: None + environment.update({'get_cached_pvesh_cluster_resources_vm': lambda: [ + {'vmid': 101, 'type': 'lxc', 'status': 'running'}], 'time': Mock(), 'sys': sys}) + exec(compile(ast.Module(body=[warmer], type_ignores=[]), 'flask_server.py', 'exec'), environment) + with patch.object(native, 'discover') as scan, \ + patch.object(lxc_apps, 'ensure_oci_registration'), \ + patch.object(lxc_apps, 'load_sidecar', return_value={'apps': []}), \ + patch.object(lxc_apps, 'get_suggestions', return_value={'extras': []}): + for _ in range(2): + environment['_vm_app_suggestions_cache'].clear() + self.assertEqual(environment['_vm_modal_prewarmer_pass'](), 1) + scan.assert_not_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e76a028..6e645e4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -876,8 +876,8 @@ and traceback before changing catalogs or translation-provider settings. #### Qualified offline `tests/` CI lanes `.github/workflows/test-offline-qualified.yml` runs **only** the explicitly listed -nine Python files (including the inert active-notification payload fixture and -the focused health recurrence tests) and eleven +ten Python files (including the native discovery fixtures, the inert active-notification payload fixture and +the focused health recurrence tests) and twelve Node files in `.github/scripts/run_offline_qualified.py`. The Python runner gives each test file a private temporary directory through `TMPDIR`. It removes that directory after the test file finishes. @@ -900,7 +900,7 @@ dangling symlink) rather than overwrite or reuse it. In Bash: ) ``` -Requires Python 3.11, Node 22.14, npm and Bash/coreutils for the bounded shell +Requires Python 3.11, Flask 3.x for the native route fixtures, Node 22.14, npm and Bash/coreutils for the bounded shell fixtures. The separate lockfile installs only TypeScript, React, React DOM, Lucide icons and the locked scheduler dependency; it does not build the Monitor or run npm lifecycle scripts. `--legacy-peer-deps` admits the existing Lucide diff --git a/tests/lxc_updates/test_native_app_discovery_ui.cjs b/tests/lxc_updates/test_native_app_discovery_ui.cjs new file mode 100644 index 00000000..51f759b7 --- /dev/null +++ b/tests/lxc_updates/test_native_app_discovery_ui.cjs @@ -0,0 +1,164 @@ +// Execute the real panel, handlers and editor with inert hook/UI/API fixtures. +// This is component interaction evidence, not browser or Proxmox acceptance. +const assert = require('node:assert/strict') +const fs = require('node:fs') +const path = require('node:path') +const vm = require('node:vm') +const root = path.resolve(__dirname, '../../AppImage') +const ts = require(path.join(root, 'node_modules/typescript')) +const source = fs.readFileSync(path.join(root, 'components/lxc-app-panel.tsx'), 'utf8') +const compiled = ts.transpileModule(source, {compilerOptions: { + module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.ReactJSX, +}, reportDiagnostics: true}) +assert.equal(compiled.diagnostics.length, 0) + +const flatten = node => !node || typeof node !== 'object' ? [] : [node, ...( + [node.props?.children].flat(Infinity).flatMap(flatten) +)] +const text = node => node == null || node === false ? '' : typeof node !== 'object' + ? String(node) : [node.props?.children].flat(Infinity).map(text).join(' ') +const candidate = { + slug: 'native-' + 'a'.repeat(24), discovery_id: 'native-' + 'a'.repeat(24), + name: 'handmade', source: 'native_metadata', installed_version: null, + evidence: [{kind: 'installation_file', value: '/opt/handmade/server'}], default_ports: [], +} + +function fixture() { + let cursor = 0, tree, props, effects = [], states = [], refs = [], effectStates = [] + const calls = [], cachedWrites = [] + let native = {vmid: 101, status: 'not_scanned', candidates: [], stale: false} + let holdScan, scanGate, holdSave, saveGate, scanStatus = 'complete' + const seed = {sidecar: {vmid: 101, apps: []}, suggestions: {extras: [], port_suggestions: []}} + const react = { + useState(initial) { + const index = cursor++ + if (!(index in states)) states[index] = typeof initial === 'function' ? initial() : initial + return [states[index], value => {states[index] = typeof value === 'function' ? value(states[index]) : value}] + }, + useRef(value) {const index = cursor++; return refs[index] ||= {current: value}}, + useMemo(fn) {cursor++; return fn()}, useCallback(fn) {cursor++; return fn}, + useEffect(fn, deps) { + const index = cursor++, prior = effectStates[index] + if (!prior || !deps || deps.some((value, i) => value !== prior.deps?.[i])) { + effects.push(() => {prior?.cleanup?.(); effectStates[index] = {deps, cleanup: fn()}}) + } + }, + } + const api = async (url, options = {}) => { + calls.push({url, ...options}) + if (url.endsWith('/native-discovery')) { + if (options.method === 'POST') { + if (scanGate) await scanGate + native = {...native, status: scanStatus, candidates: [candidate]} + } + return structuredClone(native) + } + if (url.endsWith('/apps/suggestions')) return seed.suggestions + if (url.endsWith('/apps') && options.method === 'POST') { + if (saveGate) await saveGate + return {vmid: Number(url.split('/')[3]), apps: [{id: 'saved', ...JSON.parse(options.body), state: {}}]} + } + if (url.endsWith('/adguard-setup')) return {available: false} + return [] + } + const cache = { + getLxcAppsCached: () => null, fetchLxcApps: async () => seed, + subscribeLxcApps: () => () => {}, setLxcAppsCached: (...args) => cachedWrites.push(args), + } + const exports = {} + vm.runInNewContext(compiled.outputText, {exports, console, setTimeout, clearTimeout, + require: name => { + if (name === 'react') return react + if (name === 'react/jsx-runtime') return {jsx: (type, props) => ({type, props}), jsxs: (type, props) => ({type, props})} + if (name.includes('api-config')) return {fetchApi: api} + if (name.includes('lxc-apps-cache')) return cache + if (name.includes('i18n/provider')) return {useT: () => (key, values) => key + (values ? JSON.stringify(values) : '')} + if (name.includes('category-color')) return {useIsLightTheme: () => false, categoryChipStyle: () => ({})} + if (name.includes('category-label')) return {getCategoryLabel: value => value} + return new Proxy({}, {get: (_, key) => key}) + }, + }) + function render(next = props || {vmid: 101, initialData: seed}) { + props = next; cursor = 0; effects = [] + tree = exports.LxcAppPanel(props) + for (const effect of effects) effect() + return tree + } + const settle = async () => {for (let i = 0; i < 6; i++) await Promise.resolve(); render()} + const button = key => { + const match = flatten(tree).find(n => n.type === 'Button' && text(n).includes('vmLxc.appEditor.' + key)) + assert.ok(match, 'Missing button ' + key) + return match + } + return {render, settle, calls, cachedWrites, button, tree: () => tree, + setNative: value => {native = value}, + setScanStatus: value => {scanStatus = value}, + holdScan: () => {scanGate = new Promise(resolve => {holdScan = resolve})}, resolveScan: () => holdScan(), + holdSave: () => {saveGate = new Promise(resolve => {holdSave = resolve})}, resolveSave: () => holdSave(), + } +} + +async function main() { + const f = fixture() + f.render(); await f.settle() + assert.equal(f.calls.filter(c => c.method === 'POST').length, 0, 'mount and cache reads never scan') + await f.button('searchApplications').props.onClick(); f.render() + assert.equal(f.calls.filter(c => c.url.endsWith('/native-discovery') && c.method === 'POST').length, 1) + assert.ok(text(f.tree()).includes('nativeVersionUnknown'), 'unknown version remains visible') + assert.ok(text(f.tree()).includes('/opt/handmade/server')) + await f.button('registerButton').props.onClick(); f.render() + assert.ok(text(f.tree()).includes('saveButton'), 'candidate opens actual editor') + f.button('cancelButton').props.onClick(); f.render() + assert.equal(f.calls.filter(c => /\/apps$/.test(c.url) && c.method === 'POST').length, 0, 'cancel never registers') + await f.button('registerButton').props.onClick(); f.render() + await f.button('saveButton').props.onClick(); f.render() + const saved = f.calls.find(c => /\/apps$/.test(c.url) && c.method === 'POST') + const payload = JSON.parse(saved.body) + assert.equal(saved.url, '/api/vms/101/apps') + assert.equal(payload.discovery_id, candidate.discovery_id) + assert.ok(!payload.helper_slug, 'native identity never becomes a helper') + assert.equal(payload.installed_via, '') + assert.equal(payload.update_method, 'none') + assert.equal(payload.update_command, '') + assert.equal(payload.repo, '') + assert.equal(payload.upstream_type, '') + + const partial = fixture() + partial.render(); await partial.settle(); partial.setScanStatus('partial') + await partial.button('searchApplications').props.onClick(); partial.render() + assert.ok(text(partial.tree()).includes('nativeScanPartial')) + assert.ok(text(partial.tree()).includes('nativeEvidenceStale')) + const failed = fixture() + failed.render(); await failed.settle(); failed.setScanStatus('error') + await failed.button('searchApplications').props.onClick(); failed.render() + assert.ok(text(failed.tree()).includes('nativeScanFailed')) + assert.equal(failed.calls.filter(c => c.url.endsWith('/apps/suggestions') && c.method === 'POST').length, 0) + + const late = fixture() + late.render(); await late.settle(); late.holdScan() + const scan = late.button('searchApplications').props.onClick() + late.render({vmid: 102}); await late.settle() + late.resolveScan(); await scan; late.render() + assert.ok(!text(late.tree()).includes('/opt/handmade/server'), 'late A scan cannot appear in B') + assert.equal(late.cachedWrites.length, 0) + assert.equal(late.calls.filter(c => c.url.endsWith('/apps/suggestions') && c.method === 'POST').length, 0) + + const saveLate = fixture() + saveLate.render(); await saveLate.settle() + await saveLate.button('searchApplications').props.onClick(); saveLate.render() + await saveLate.button('registerButton').props.onClick(); saveLate.render() + saveLate.holdSave(); const saving = saveLate.button('saveButton').props.onClick() + const priorWrites = saveLate.cachedWrites.length + saveLate.render({vmid: 102}); await saveLate.settle() + saveLate.resolveSave(); await saving; saveLate.render() + assert.equal(saveLate.cachedWrites.length, priorWrites, 'late save response does not update new target') + assert.equal(saveLate.calls.filter(c => /\/vms\/102\/apps$/.test(c.url) && c.method === 'POST').length, 0) + + const oci = fixture() + oci.render({vmid: 101, oci: {instance: true}, initialData: {sidecar: {apps: []}, suggestions: {oci_instance: {template_id: 'image-app'}, extras: []}}}) + await oci.settle() + await oci.button('refreshData').props.onClick() + assert.equal(oci.calls.filter(c => c.url.endsWith('/native-discovery') && c.method === 'POST').length, 0, 'OCI refresh never native-scans') + console.log('PASS native discovery: real panel Find → editor → Cancel/Save, no automatic scan, safe payload, CT switching and OCI refresh (fixtures)') +} +main().catch(error => {console.error(error); process.exitCode = 1}) From bbe811e8736c1578754ed18b12d52bbc7b1eb8da Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:18:17 +0000 Subject: [PATCH 04/53] fix: hide native discovery editor immediately on target change --- AppImage/components/lxc-app-panel.tsx | 2 +- tests/lxc_updates/test_native_app_discovery_ui.cjs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/AppImage/components/lxc-app-panel.tsx b/AppImage/components/lxc-app-panel.tsx index 666009cb..24003d66 100644 --- a/AppImage/components/lxc-app-panel.tsx +++ b/AppImage/components/lxc-app-panel.tsx @@ -1348,7 +1348,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: } // ── Editor ───────────────────────────────────────────────────── - if (editing) { + if (editorOpen && editing) { const draft = editing.draft const method = draft.installed_via || "" const isPackaged = method === "dpkg" || method === "apk" diff --git a/tests/lxc_updates/test_native_app_discovery_ui.cjs b/tests/lxc_updates/test_native_app_discovery_ui.cjs index 51f759b7..3bb55dc4 100644 --- a/tests/lxc_updates/test_native_app_discovery_ui.cjs +++ b/tests/lxc_updates/test_native_app_discovery_ui.cjs @@ -134,6 +134,13 @@ async function main() { assert.ok(text(failed.tree()).includes('nativeScanFailed')) assert.equal(failed.calls.filter(c => c.url.endsWith('/apps/suggestions') && c.method === 'POST').length, 0) + const editorSwitch = fixture() + editorSwitch.render(); await editorSwitch.settle() + await editorSwitch.button('searchApplications').props.onClick(); editorSwitch.render() + await editorSwitch.button('registerButton').props.onClick(); editorSwitch.render() + editorSwitch.render({vmid: 102}) + assert.ok(!text(editorSwitch.tree()).includes('saveButton'), 'old editor must disappear in the very first render of another CT') + const late = fixture() late.render(); await late.settle(); late.holdScan() const scan = late.button('searchApplications').props.onClick() From 6d82cf4f15c5880876b6f5660a0d7806aab34652 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:53:01 +0000 Subject: [PATCH 05/53] fix: exclude native system service candidates --- AppImage/scripts/native_app_discovery.py | 19 ++++++++++++-- .../tests/test_native_app_discovery.py | 25 ++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index a87dfc6e..169fd4a2 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -62,6 +62,12 @@ _SYSTEM = { 'ip6tables', 'firewalld', 'ufw', 'resolvconf', 'wpa-supplicant', 'modemmanager', 'console-setup', 'keyboard-setup', 'plymouth', 'halt', 'reboot', 'shutdown', 'rc-local', 'ntpsec', 'networkd-dispatcher', 'busybox', 'atd', 'uuidd', + # Common packaged daemons are infrastructure, not a user-managed app. + 'nginx', 'apache', 'apache2', 'httpd', 'lighttpd', 'haproxy', 'traefik', + 'redis', 'redis-server', 'memcached', 'mysql', 'mysqld', 'mariadb', 'mariadbd', + 'postgres', 'postgresql', 'mongod', 'mongodb', 'rabbitmq-server', + 'elasticsearch', 'opensearch', 'fail2ban', 'named', 'bind9', 'dnsmasq', + 'avahi-daemon', 'smbd', 'nmbd', 'samba', 'vsftpd', 'proftpd', 'cups', 'tor', } _RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|sh|bash|dash|env|java|ruby|perl)\Z') @@ -205,9 +211,11 @@ def _path(value): def _system(name): raw = name.lower().removesuffix('.service') stem = raw.split('@')[0] - return raw.startswith('user@') or stem in _SYSTEM or stem.startswith(( + return (raw.startswith('user@') or stem in _SYSTEM + or (raw.startswith('php') and raw.endswith('-fpm')) + or stem.startswith(( 'systemd-', 'dbus-', 'getty', 'containerd-', 'apt-', 'dpkg-', 'e2scrub', - 'unattended-', 'ifup', 'ifdown', 'user@', 'btrfs-', 'lvm2-', 'lxc-')) + 'unattended-', 'ifup', 'ifdown', 'user@', 'btrfs-', 'lvm2-', 'lxc-'))) def _custom(path): @@ -240,6 +248,13 @@ def parse_records(text, kind): or install.startswith(('/usr/lib/systemd/', '/lib/systemd/', '/usr/lib/udev/', '/lib/udev/', '/usr/lib/apt/'))): continue + # /etc units are operator-owned. Do not infer an application from an + # arbitrary packaged systemd unit: without a package-db probe it is + # impossible to distinguish vendor infrastructure from a user app. + if (manager == 'systemd' + and source.startswith(('/usr/lib/systemd/system/', '/lib/systemd/system/')) + and not _custom(install)): + continue root = directory if _custom(directory) else str(Path(install).parent) # Generic runtime without an absolute application script is ambiguous. if runtime and not _custom(install): diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index cc1539b1..21962223 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -19,9 +19,9 @@ import native_app_discovery as native import lxc_apps -def service(name='handmade', exe='/opt/handmade/server', directory='/opt/handmade', manager='systemd', script=''): - path = ('/etc/systemd/system/' + name + '.service' if manager == 'systemd' - else '/etc/init.d/' + name) +def service(name='handmade', exe='/opt/handmade/server', directory='/opt/handmade', manager='systemd', script='', source=None): + path = source or ('/etc/systemd/system/' + name + '.service' if manager == 'systemd' + else '/etc/init.d/' + name) return '\t'.join(['S', path, manager, exe, script, directory]) + '\nEND\n' @@ -100,6 +100,25 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_common_system_daemons_and_vendor_units_have_zero_footprint_probes(self): + cases = [ + ('nginx', '/usr/sbin/nginx', 'openrc', None), + ('apache2', '/usr/sbin/apache2', 'systemd', None), + ('redis-server', '/usr/bin/redis-server', 'systemd', None), + ('mariadb', '/usr/sbin/mariadbd', 'systemd', None), + ('postgresql', '/usr/lib/postgresql/16/bin/postgres', 'systemd', None), + ('php8.3-fpm', '/usr/sbin/php-fpm8.3', 'systemd', None), + ('vendor-daemon', '/usr/bin/vendor-daemon', 'systemd', + '/usr/lib/systemd/system/vendor-daemon.service'), + ] + for name, exe, manager, source in cases: + with self.subTest(name=name): + self.calls.clear() + result = native.scan(101, self.runner( + service(name=name, exe=exe, manager=manager, source=source))) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_missing_remote_qemu_stopped_busy_oci_guards_precede_exec(self): scenarios = ['missing', 'stopped', 'busy', 'oci', 'invalid-oci', 'unmanaged'] for scenario in scenarios: From 61fae8f6f75b30840c65f28f660728653e61c701 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:20:40 +0000 Subject: [PATCH 06/53] fix: filter native discovery helper services --- AppImage/scripts/native_app_discovery.py | 43 ++++++++++++++-- .../tests/test_native_app_discovery.py | 49 +++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 169fd4a2..35bb4f07 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -69,7 +69,13 @@ _SYSTEM = { 'elasticsearch', 'opensearch', 'fail2ban', 'named', 'bind9', 'dnsmasq', 'avahi-daemon', 'smbd', 'nmbd', 'samba', 'vsftpd', 'proftpd', 'cups', 'tor', } -_RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|sh|bash|dash|env|java|ruby|perl)\Z') +_RUNTIMES = re.compile(r'(?:python[0-9.]*|node(?:js)?|npm|pnpm|yarn|bun|sh|bash|dash|env|java|ruby|perl)\Z') +_PACKAGE_ROOT_RUNTIMES = {'node', 'nodejs', 'npm', 'pnpm', 'yarn', 'bun'} +_HELPER_ROLES = { + 'backup', 'bootstrap', 'bridge', 'cleanup', 'housekeeping', 'maintenance', + 'migrate', 'migration', 'restore', 'rotate', 'update', 'updater', 'upgrade', + 'watchdog', 'worker', +} class ProbeError(Exception): @@ -218,8 +224,23 @@ def _system(name): 'unattended-', 'ifup', 'ifdown', 'user@', 'btrfs-', 'lvm2-', 'lxc-'))) +def _helper(name): + """Reject service roles that support an application but are not one themselves.""" + raw = name.lower().removesuffix('.service') + if raw.endswith('@'): + return True # A systemd template is not a concrete running application. + return any(part in _HELPER_ROLES for part in re.split(r'[._-]+', raw) if part) + + def _custom(path): - return _path(path) and path.startswith(('/opt/', '/srv/', '/usr/local/')) + if not _path(path): + return False + if path.startswith(('/opt/', '/srv/', '/usr/local/')): + return True + # Native apps commonly keep their runtime below /home//. + # Do not accept a home directory itself: a second path component is required. + parts = path.split('/') + return len(parts) >= 4 and parts[1] == 'home' and bool(parts[2]) and bool(parts[3]) def parse_records(text, kind): @@ -240,10 +261,21 @@ def parse_records(text, kind): if service in seen_services: continue # /etc overrides vendor units; no duplicate installations. seen_services.add(service) - if not _NAME.fullmatch(service) or _system(service) or not _path(exe): + if (not _NAME.fullmatch(service) or _system(service) or _helper(service) + or not _path(exe)): continue - runtime = bool(_RUNTIMES.fullmatch(Path(exe).name)) - install = script if runtime else exe + runtime_name = Path(exe).name + runtime = bool(_RUNTIMES.fullmatch(runtime_name)) + if runtime and not _path(script): + # A Node/npm wrapper may keep its entry point relative to an + # operator-owned working directory. `package.json` is a bounded, + # declarative footprint that proves the application root without + # reading command arguments, environment or service secrets. + if runtime_name not in _PACKAGE_ROOT_RUNTIMES or not _custom(directory): + continue + install = str(Path(directory) / 'package.json') + else: + install = script if runtime else exe if (not _path(install) or _system(Path(install).name) or install.startswith(('/usr/lib/systemd/', '/lib/systemd/', '/usr/lib/udev/', '/lib/udev/', '/usr/lib/apt/'))): @@ -399,6 +431,7 @@ def scan(vmid, run=bounded_run): related[0].setdefault('listening_ports', []).extend(ports) elif not related and (_custom(exe) and _custom(directory) and not _system(Path(exe).name) + and not _helper(Path(exe).name) and not _RUNTIMES.fullmatch(Path(exe).name) and exe.startswith(directory.rstrip('/') + '/') and _NAME.fullmatch(Path(exe).name)): diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index 21962223..661abd95 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -119,6 +119,55 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_helper_services_and_templates_have_zero_footprint_probes(self): + cases = [ + ('netpulse-stable-update', '/bin/bash', '/opt/netpulse', '/opt/netpulse/stable-apply.sh'), + ('rent-tool-backup', '/bin/bash', '/home/app/rent-tool', '/home/app/rent-tool/scripts/backup-db.sh'), + ('keynest-update', '/bin/bash', '/opt/keynest', '/opt/keynest/keynest-update.sh'), + ('cortendesk-bootstrap-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/bootstrap-worker.sh'), + ('cortendesk-source-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/source-worker.sh'), + ('cortendesk-managed-password@', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/password-bridge.sh'), + ] + for name, exe, directory, script in cases: + with self.subTest(name=name): + self.calls.clear() + result = native.scan(101, self.runner(service( + name=name, exe=exe, directory=directory, script=script))) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + + def test_node_and_npm_wrappers_use_only_custom_package_roots(self): + cases = [ + ('keynest', '/usr/bin/node', '/opt/keynest'), + ('rent-tool', '/usr/local/bin/npm', '/home/app/rent-tool'), + ] + for name, exe, directory in cases: + with self.subTest(name=name): + self.calls.clear() + result = native.scan(101, self.runner( + service(name=name, exe=exe, directory=directory), + footprint='FILE\n{"version":"1.2.3"}')) + item, = result['candidates'] + self.assertEqual(item['name'], name) + self.assertEqual(item['installation_path'], directory + '/package.json') + self.assertEqual(item['installed_version'], '1.2.3') + footprint_calls = [call for call in self.calls if native.FOOTPRINT_SCRIPT in call] + self.assertEqual(len(footprint_calls), 1) + self.assertEqual(footprint_calls[0][-2:], [directory + '/package.json', directory]) + + self.calls.clear() + result = native.scan(101, self.runner(service( + name='ambiguous-home-runtime', exe='/usr/bin/node', directory='/home/app'))) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + + def test_helper_process_fallback_has_zero_footprint_probes(self): + self.calls.clear() + result = native.scan(101, self.runner( + services='END\n', processes='P\t123\t/opt/cortendesk/run-worker\t/opt/cortendesk\nEND\n')) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_missing_remote_qemu_stopped_busy_oci_guards_precede_exec(self): scenarios = ['missing', 'stopped', 'busy', 'oci', 'invalid-oci', 'unmanaged'] for scenario in scenarios: From e978bac91a8b1b6db4b18b6ffdbf3720c9a4c320 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:15:07 +0000 Subject: [PATCH 07/53] fix: detect native node release layouts --- AppImage/scripts/native_app_discovery.py | 97 +++++++++++++++++-- .../tests/test_native_app_discovery.py | 74 +++++++++++++- 2 files changed, 158 insertions(+), 13 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 35bb4f07..11f9acf1 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -207,6 +207,27 @@ if [ -f "$2/package.json" ]; then timeout -s KILL 1 head -c 8192 "$2/package.json" fi ''' +MANIFEST_FOOTPRINT_SCRIPT = r''' +command -v head >/dev/null && command -v readlink >/dev/null || exit 1 +# Resolve only the operator-owned working directory and its declared parent. +# A release symlink may change the concrete version directory, but it must not +# escape the bounded custom application root supplied by the host. +workdir=$(timeout -s KILL 1 readlink -f "$1") || exit 1 +anchor=$(timeout -s KILL 1 readlink -f "$2") || exit 1 +parent=$(timeout -s KILL 1 readlink -f "$3") || exit 1 +case "$anchor" in "$parent"/*) ;; *) printf 'OUTSIDE\n'; exit 0;; esac +case "$workdir" in "$anchor"|"$anchor"/*) ;; *) printf 'OUTSIDE\n'; exit 0;; esac +for relative in package.json server/package.json; do + candidate="$workdir/$relative" + [ -f "$candidate" ] && [ -r "$candidate" ] || continue + resolved=$(timeout -s KILL 1 readlink -f "$candidate") || exit 1 + case "$resolved" in "$workdir"/*) ;; *) printf 'OUTSIDE\n'; exit 0;; esac + printf 'MANIFEST\t%s\n' "$relative" + timeout -s KILL 1 head -c 8192 "$resolved" + exit 0 +done +printf 'MISSING\n' +''' def _path(value): @@ -243,6 +264,50 @@ def _custom(path): return len(parts) >= 4 and parts[1] == 'home' and bool(parts[2]) and bool(parts[3]) +def _manifest_bounds(executable, directory): + """Return a narrow, operator-owned boundary for a runtime manifest probe.""" + try: + shared = os.path.commonpath((executable, directory)) + except ValueError: + shared = '' + anchor = shared if _custom(shared) else directory + parts = anchor.split('/') + if anchor.startswith(('/opt/', '/srv/')): + parent = '/' + parts[1] + elif anchor.startswith('/usr/local/'): + parent = '/usr/local' + elif len(parts) >= 4 and parts[1] == 'home' and parts[2]: + parent = f'/home/{parts[2]}' + else: + return None + return anchor, parent + + +def _manifest_footprint(record, run, deadline): + """Read one of two fixed Node manifest locations without following an escape.""" + output = run(['pct', 'exec', str(record['vmid']), '--', 'timeout', '-s', 'KILL', '2', + 'sh', '-c', MANIFEST_FOOTPRINT_SCRIPT, 'discovery', + record['root'], record['manifest_anchor'], record['manifest_parent']], deadline) + header, separator, metadata = output.partition('\n') + if header == 'MISSING': + if metadata: + raise ProbeError('invalid_data') + return None, None + if header == 'OUTSIDE': + if metadata: + raise ProbeError('invalid_data') + raise ProbeError('unsafe_manifest_root') + prefix = 'MANIFEST\t' + relative = header.removeprefix(prefix) + if not header.startswith(prefix) or relative not in ('package.json', 'server/package.json'): + raise ProbeError('invalid_data') + if not separator: + raise ProbeError('invalid_data') + candidate_record = dict(record) + candidate_record['path'] = str(Path(record['root']) / relative) + return candidate_record, 'FILE\n' + metadata + + def parse_records(text, kind): lines = text.splitlines() if not lines or lines[-1] not in ('END', 'LIMIT'): @@ -266,14 +331,18 @@ def parse_records(text, kind): continue runtime_name = Path(exe).name runtime = bool(_RUNTIMES.fullmatch(runtime_name)) + manifest_bounds = None if runtime and not _path(script): # A Node/npm wrapper may keep its entry point relative to an - # operator-owned working directory. `package.json` is a bounded, - # declarative footprint that proves the application root without - # reading command arguments, environment or service secrets. + # operator-owned working directory. Only two fixed manifest + # locations can prove that root; no argv, environment, recursive + # search or guest-provided path is ever used. if runtime_name not in _PACKAGE_ROOT_RUNTIMES or not _custom(directory): continue install = str(Path(directory) / 'package.json') + manifest_bounds = _manifest_bounds(exe, directory) + if manifest_bounds is None: + continue else: install = script if runtime else exe if (not _path(install) or _system(Path(install).name) @@ -291,8 +360,11 @@ def parse_records(text, kind): # Generic runtime without an absolute application script is ambiguous. if runtime and not _custom(install): continue - records.append({'name': service, 'path': install, 'root': root, - 'service': source, 'manager': manager, 'executable': exe}) + record = {'name': service, 'path': install, 'root': root, + 'service': source, 'manager': manager, 'executable': exe} + if manifest_bounds: + record['manifest_anchor'], record['manifest_parent'] = manifest_bounds + records.append(record) elif kind == 'P' and len(parts) == 4 and parts[0] == 'P': _, pid, exe, directory = parts if not pid.isdigit() or not _path(exe) or not _path(directory): @@ -447,10 +519,17 @@ def scan(vmid, run=bounded_run): issues.append('candidate_limit') break try: - footprint = run(['pct', 'exec', str(vmid), '--', 'timeout', '-s', 'KILL', '2', - 'sh', '-c', FOOTPRINT_SCRIPT, - 'discovery', record['path'], record['root']], deadline) - item = candidate(vmid, record, footprint) + probe_record = dict(record) + probe_record['vmid'] = vmid + if 'manifest_anchor' in record: + probe_record, footprint = _manifest_footprint(probe_record, run, deadline) + if probe_record is None: + continue + else: + footprint = run(['pct', 'exec', str(vmid), '--', 'timeout', '-s', 'KILL', '2', + 'sh', '-c', FOOTPRINT_SCRIPT, + 'discovery', record['path'], record['root']], deadline) + item = candidate(vmid, probe_record, footprint) items.append(item) if item['metadata_issue']: issues.append(item['metadata_issue']) diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index 661abd95..4ff2f875 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -44,7 +44,8 @@ class DiscoveryFixtures(unittest.TestCase): native._locks.clear() self.calls = [] - def runner(self, services=None, processes='END\n', footprint='FILE\n', status='status: running', ports='END\n'): + def runner(self, services=None, processes='END\n', footprint='FILE\n', manifest=None, + status='status: running', ports='END\n'): def run(argv, deadline): self.calls.append(argv) self.assertLessEqual(deadline - time.monotonic(), native.TOTAL_SECONDS) @@ -59,6 +60,10 @@ class DiscoveryFixtures(unittest.TestCase): return processes if script == native.PORT_SCRIPT: return ports + if script == native.MANIFEST_FOOTPRINT_SCRIPT: + if manifest is not None: + return manifest + return 'MANIFEST\tpackage.json\n' + footprint.removeprefix('FILE\n') if script == native.FOOTPRINT_SCRIPT: return footprint self.fail('Unexpected probe') @@ -136,7 +141,7 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) - def test_node_and_npm_wrappers_use_only_custom_package_roots(self): + def test_node_and_npm_wrappers_use_only_bounded_manifest_roots(self): cases = [ ('keynest', '/usr/bin/node', '/opt/keynest'), ('rent-tool', '/usr/local/bin/npm', '/home/app/rent-tool'), @@ -151,14 +156,75 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(item['name'], name) self.assertEqual(item['installation_path'], directory + '/package.json') self.assertEqual(item['installed_version'], '1.2.3') - footprint_calls = [call for call in self.calls if native.FOOTPRINT_SCRIPT in call] + footprint_calls = [call for call in self.calls if native.MANIFEST_FOOTPRINT_SCRIPT in call] self.assertEqual(len(footprint_calls), 1) - self.assertEqual(footprint_calls[0][-2:], [directory + '/package.json', directory]) + if directory.startswith('/home/'): + parent = '/home/app' + else: + parent = '/opt' + self.assertEqual(footprint_calls[0][-3:], [directory, directory, parent]) self.calls.clear() result = native.scan(101, self.runner(service( name='ambiguous-home-runtime', exe='/usr/bin/node', directory='/home/app'))) self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.MANIFEST_FOOTPRINT_SCRIPT in call for call in self.calls)) + + def test_node_release_layout_uses_only_server_manifest_under_shared_root(self): + self.calls.clear() + result = native.scan(101, self.runner( + service(name='keynest', exe='/opt/keynest/node/bin/node', directory='/opt/keynest/current'), + manifest='MANIFEST\tserver/package.json\n{"version":"1.5.63"}')) + item, = result['candidates'] + self.assertEqual(item['name'], 'keynest') + self.assertEqual(item['installation_path'], '/opt/keynest/current/server/package.json') + self.assertEqual(item['installed_version'], '1.5.63') + manifest_calls = [call for call in self.calls if native.MANIFEST_FOOTPRINT_SCRIPT in call] + self.assertEqual(len(manifest_calls), 1) + self.assertEqual(manifest_calls[0][-3:], + ['/opt/keynest/current', '/opt/keynest', '/opt']) + + def test_node_release_layout_rejects_manifest_outside_operator_root(self): + self.calls.clear() + result = native.scan(101, self.runner( + service(name='outside-root', exe='/opt/outside/node/bin/node', directory='/opt/outside/current'), + manifest='OUTSIDE\n')) + self.assertEqual(result['candidates'], []) + self.assertIn('unsafe_manifest_root', result['issues']) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + + def test_actual_manifest_probe_accepts_release_and_rejects_symlink_escape(self): + parent = self.root / 'opt' + anchor = parent / 'keynest' + release = anchor / 'releases' / 'v1.5.63' + manifest = release / 'server' / 'package.json' + manifest.parent.mkdir(parents=True) + manifest.write_text('{"version":"1.5.63"}') + current = anchor / 'current' + current.symlink_to(release) + output = native.bounded_run( + ['sh', '-c', native.MANIFEST_FOOTPRINT_SCRIPT, 'fixture', + str(current), str(anchor), str(parent)], time.monotonic() + 3) + self.assertEqual(output, 'MANIFEST\tserver/package.json\n{"version":"1.5.63"}') + + outside = self.root / 'outside' / 'package.json' + outside.parent.mkdir() + outside.write_text('{"version":"not-readable"}') + manifest.unlink() + manifest.symlink_to(outside) + output = native.bounded_run( + ['sh', '-c', native.MANIFEST_FOOTPRINT_SCRIPT, 'fixture', + str(current), str(anchor), str(parent)], time.monotonic() + 3) + self.assertEqual(output, 'OUTSIDE\n') + self.assertNotIn('not-readable', output) + + def test_node_wrapper_with_no_fixed_manifest_is_not_an_error_or_candidate(self): + self.calls.clear() + result = native.scan(101, self.runner( + service(name='missing-manifest', exe='/opt/missing/node/bin/node', directory='/opt/missing/current'), + manifest='MISSING\n')) + self.assertEqual(result['status'], 'complete') + self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) def test_helper_process_fallback_has_zero_footprint_probes(self): From 1f75147d5157eb524e297d25fa75130ba32be30f Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:47:53 +0000 Subject: [PATCH 08/53] fix: recognize Hestia panel and filter watch helpers --- AppImage/scripts/native_app_discovery.py | 28 +++++++++++++++++-- .../tests/test_native_app_discovery.py | 18 ++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 11f9acf1..534c9125 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -74,8 +74,11 @@ _PACKAGE_ROOT_RUNTIMES = {'node', 'nodejs', 'npm', 'pnpm', 'yarn', 'bun'} _HELPER_ROLES = { 'backup', 'bootstrap', 'bridge', 'cleanup', 'housekeeping', 'maintenance', 'migrate', 'migration', 'restore', 'rotate', 'update', 'updater', 'upgrade', - 'watchdog', 'worker', + 'watch', 'watchdog', 'worker', } +_HESTIA_NGINX = '/usr/local/hestia/nginx/sbin/hestia-nginx' +_HESTIA_ROOT = '/usr/local/hestia' +_HESTIA_PANEL_PORT = 8083 class ProbeError(Exception): @@ -253,6 +256,17 @@ def _helper(name): return any(part in _HELPER_ROLES for part in re.split(r'[._-]+', raw) if part) +def _hestia_panel_process(executable, ports): + """Recognize only Hestia's own bound control-panel process. + + Hestia's generated init unit does not expose an ExecStart that the generic + service parser may treat as an application. Its panel process and port are + stable, local runtime metadata, so this stays narrower than accepting + arbitrary vendor services or a generic web server. + """ + return executable == _HESTIA_NGINX and _HESTIA_PANEL_PORT in ports + + def _custom(path): if not _path(path): return False @@ -367,7 +381,10 @@ def parse_records(text, kind): records.append(record) elif kind == 'P' and len(parts) == 4 and parts[0] == 'P': _, pid, exe, directory = parts - if not pid.isdigit() or not _path(exe) or not _path(directory): + # A process rooted at / is normally too ambiguous for generic + # discovery, but it is valid process metadata for the exact Hestia + # panel signature below. It never becomes a generic candidate. + if not pid.isdigit() or not _path(exe) or (directory != '/' and not _path(directory)): continue records.append({'pid': pid, 'executable': exe, 'root': directory}) elif kind == 'L' and len(parts) == 3 and parts[0] == 'L': @@ -407,7 +424,8 @@ def candidate(vmid, record, footprint): evidence.append({'kind': record['manager'], 'value': record['service']}) else: evidence.extend([{'kind': 'process_executable', 'value': record['process']}, - {'kind': 'working_directory', 'value': record['root']}]) + {'kind': 'working_directory', + 'value': record.get('process_root', record['root'])}]) ports = sorted(set(record.get('listening_ports') or [])) evidence.extend({'kind': 'tcp_listener', 'value': str(port)} for port in ports) return {'slug': identity, 'discovery_id': identity, 'source': 'native_metadata', @@ -501,6 +519,10 @@ def scan(vmid, run=bounded_run): ports = [entry['port'] for entry in listeners if entry['pid'] == process['pid']] if len(related) == 1: related[0].setdefault('listening_ports', []).extend(ports) + elif not related and _hestia_panel_process(exe, ports): + records.append({'name': 'Hestia', 'path': exe, 'root': _HESTIA_ROOT, + 'process': exe, 'process_root': directory, 'executable': exe, + 'listening_ports': ports}) elif not related and (_custom(exe) and _custom(directory) and not _system(Path(exe).name) and not _helper(Path(exe).name) diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index 4ff2f875..e9073f6e 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -132,6 +132,7 @@ class DiscoveryFixtures(unittest.TestCase): ('cortendesk-bootstrap-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/bootstrap-worker.sh'), ('cortendesk-source-worker', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/source-worker.sh'), ('cortendesk-managed-password@', '/bin/bash', '/opt/cortendesk', '/opt/cortendesk/password-bridge.sh'), + ('miminails-watch', '/usr/local/bin/miminails-watch', '/usr/local/bin', ''), ] for name, exe, directory, script in cases: with self.subTest(name=name): @@ -141,6 +142,23 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_hestia_panel_is_recognized_only_from_its_exact_runtime_and_port(self): + hestia = '/usr/local/hestia/nginx/sbin/hestia-nginx' + process = f'P\t391\t{hestia}\t/\nEND\n' + result = native.scan(101, self.runner( + services='END\n', processes=process, ports='L\t391\t8083\nEND\n')) + item, = result['candidates'] + self.assertEqual(item['name'], 'Hestia') + self.assertEqual(item['installation_path'], hestia) + self.assertEqual(item['listening_ports'], [8083]) + self.assertIn({'kind': 'working_directory', 'value': '/'}, item['evidence']) + + self.calls.clear() + result = native.scan(101, self.runner( + services='END\n', processes=process, ports='L\t391\t8443\nEND\n')) + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_node_and_npm_wrappers_use_only_bounded_manifest_roots(self): cases = [ ('keynest', '/usr/bin/node', '/opt/keynest'), From 20acf0852a4567ac937e2bdf95291091fd904656 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Fri, 16 Oct 2026 07:15:43 +0000 Subject: [PATCH 09/53] fix: find Hestia behind bounded process metadata --- AppImage/scripts/native_app_discovery.py | 50 ++++++++++++++++++- .../tests/test_native_app_discovery.py | 14 ++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 534c9125..a807ead1 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -202,6 +202,30 @@ else exit 1 fi ''' +HESTIA_PANEL_SCRIPT = r''' +# The generic process collector intentionally stops after a bounded number of +# records and ignores shared listener ownership. Hestia's master/worker pair +# can therefore be outside that generic result. This narrow fallback never +# accepts a name alone: it requires Hestia's exact executable and its panel +# listener on the fixed local port. +[ -x /usr/local/hestia/nginx/sbin/hestia-nginx ] || { printf 'END\n'; exit 0; } +command -v pgrep >/dev/null && command -v readlink >/dev/null && command -v ss >/dev/null && command -v head >/dev/null || exit 1 +n=0 +for pid in $(pgrep -x hestia-nginx 2>/dev/null | head -n 9); do + case "$pid" in *[!0-9]*|'') continue;; esac + n=$((n+1)); [ "$n" -le 8 ] || { printf 'LIMIT\n'; exit 0; } + exe=$(timeout -s KILL 1 readlink "/proc/$pid/exe") || continue + [ "$exe" = /usr/local/hestia/nginx/sbin/hestia-nginx ] || continue + cwd=$(timeout -s KILL 1 readlink "/proc/$pid/cwd") || continue + if timeout -s KILL 1 ss -H -lntp | awk -v pid="$pid" ' + {port=$4; sub(/^.*:/,"",port) + if (port == 8083 && index($0, "pid=" pid ",")) found=1} + END {exit(found ? 0 : 1)}'; then + printf 'H\t%s\t%s\t%s\t8083\n' "$pid" "$exe" "$cwd" + fi +done +printf 'END\n' +''' FOOTPRINT_SCRIPT = r''' [ -f "$1" ] && [ -r "$1" ] || exit 1 printf 'FILE\n' @@ -393,6 +417,15 @@ def parse_records(text, kind): records.append({'pid': pid, 'port': int(port)}) else: raise ProbeError('invalid_data') + elif kind == 'H' and len(parts) == 5 and parts[0] == 'H': + _, pid, exe, directory, port = parts + if (pid.isdigit() and exe == _HESTIA_NGINX + and (directory == '/' or _path(directory)) + and port == str(_HESTIA_PANEL_PORT)): + records.append({'pid': pid, 'executable': exe, 'root': directory, + 'port': _HESTIA_PANEL_PORT}) + else: + raise ProbeError('invalid_data') else: raise ProbeError('invalid_data') return records, lines[-1] == 'LIMIT', len(lines) - 1 @@ -487,7 +520,7 @@ def _guard(vmid, deadline, run): def scan(vmid, run=bounded_run): deadline = time.monotonic() + TOTAL_SECONDS _guard(vmid, deadline, run) - records, issues, consumed, processes, listeners = [], [], 0, [], [] + records, issues, consumed, processes, listeners, hestia_panels = [], [], 0, [], [], [] for kind, script in (('S', SERVICE_SCRIPT), ('P', PROCESS_SCRIPT), ('L', PORT_SCRIPT)): try: if consumed >= MAX_RECORDS: @@ -512,6 +545,16 @@ def scan(vmid, run=bounded_run): issues.append('metadata_limit') except ProbeError as exc: issues.append(str(exc)) + try: + # This is outside the generic record budget: it returns only an exact, + # fixed Hestia signature, not additional arbitrary process metadata. + hestia_panels, limited, _ = parse_records(run( + ['pct', 'exec', str(vmid), '--', 'timeout', '-s', 'KILL', '2', + 'sh', '-c', HESTIA_PANEL_SCRIPT, 'discovery'], deadline), 'H') + if limited: + issues.append('metadata_limit') + except ProbeError as exc: + issues.append(str(exc)) for process in processes: exe, directory = process['executable'], process['root'] related = [record for record in records if record['executable'] == exe and ( @@ -531,6 +574,11 @@ def scan(vmid, run=bounded_run): and _NAME.fullmatch(Path(exe).name)): records.append({'name': Path(exe).name, 'path': exe, 'root': directory, 'process': exe, 'executable': exe, 'listening_ports': ports}) + for panel in hestia_panels: + records.append({'name': 'Hestia', 'path': panel['executable'], 'root': _HESTIA_ROOT, + 'process': panel['executable'], 'process_root': panel['root'], + 'executable': panel['executable'], + 'listening_ports': [panel['port']]}) items, seen = [], set() for record in records: key = (record['path'], record['root']) diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index e9073f6e..f47c33c9 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -45,7 +45,7 @@ class DiscoveryFixtures(unittest.TestCase): self.calls = [] def runner(self, services=None, processes='END\n', footprint='FILE\n', manifest=None, - status='status: running', ports='END\n'): + status='status: running', ports='END\n', hestia_panels='END\n'): def run(argv, deadline): self.calls.append(argv) self.assertLessEqual(deadline - time.monotonic(), native.TOTAL_SECONDS) @@ -60,6 +60,8 @@ class DiscoveryFixtures(unittest.TestCase): return processes if script == native.PORT_SCRIPT: return ports + if script == native.HESTIA_PANEL_SCRIPT: + return hestia_panels if script == native.MANIFEST_FOOTPRINT_SCRIPT: if manifest is not None: return manifest @@ -144,21 +146,25 @@ class DiscoveryFixtures(unittest.TestCase): def test_hestia_panel_is_recognized_only_from_its_exact_runtime_and_port(self): hestia = '/usr/local/hestia/nginx/sbin/hestia-nginx' - process = f'P\t391\t{hestia}\t/\nEND\n' + panel = f'H\t391\t{hestia}\t/\t8083\nEND\n' result = native.scan(101, self.runner( - services='END\n', processes=process, ports='L\t391\t8083\nEND\n')) + services='END\n', processes='LIMIT\n', ports='END\n', hestia_panels=panel)) item, = result['candidates'] self.assertEqual(item['name'], 'Hestia') self.assertEqual(item['installation_path'], hestia) self.assertEqual(item['listening_ports'], [8083]) self.assertIn({'kind': 'working_directory', 'value': '/'}, item['evidence']) + self.assertIn('metadata_limit', result['issues']) self.calls.clear() result = native.scan(101, self.runner( - services='END\n', processes=process, ports='L\t391\t8443\nEND\n')) + services='END\n', processes='LIMIT\n', ports='END\n')) self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + with self.assertRaises(native.ProbeError): + native.parse_records(f'H\t391\t{hestia}\t/\t8443\nEND\n', 'H') + def test_node_and_npm_wrappers_use_only_bounded_manifest_roots(self): cases = [ ('keynest', '/usr/bin/node', '/opt/keynest'), From 503302cea35c7b3b8b24fb546e9e36afffa33b81 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Fri, 16 Oct 2026 08:25:17 +0000 Subject: [PATCH 10/53] fix: prefill verified Hestia panel endpoint --- AppImage/scripts/native_app_discovery.py | 12 +++++++++++- .../scripts/tests/test_native_app_discovery.py | 9 +++++++++ tests/lxc_updates/test_native_app_discovery_ui.cjs | 14 ++++++++++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index a807ead1..5ade84bd 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -460,11 +460,21 @@ def candidate(vmid, record, footprint): {'kind': 'working_directory', 'value': record.get('process_root', record['root'])}]) ports = sorted(set(record.get('listening_ports') or [])) + # Hestia's control panel is a narrow, verified exception to generic port + # inference: its own executable serves the panel on fixed HTTPS port 8083. + # Keep this declaration on the exact runtime signature so an unrelated + # application merely listening on 8083 never receives an HTTPS guess. + hestia_panel = (record['name'] == 'Hestia' + and record['path'] == _HESTIA_NGINX + and record['root'] == _HESTIA_ROOT + and _HESTIA_PANEL_PORT in ports) evidence.extend({'kind': 'tcp_listener', 'value': str(port)} for port in ports) return {'slug': identity, 'discovery_id': identity, 'source': 'native_metadata', 'name': record['name'], 'installed_version': version, 'evidence': evidence, - 'installation_path': record['path'], 'default_ports': [], + 'installation_path': record['path'], + 'default_ports': [_HESTIA_PANEL_PORT] if hestia_panel else [], 'listening_ports': ports, + **({'scheme': 'https'} if hestia_panel else {}), 'metadata_issue': metadata_issue, 'tracking_suggestion': ({'installed_via': 'manual', 'installed_version': version, 'detected_version': version, 'detector_verified': True} diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index f47c33c9..d1432b65 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -153,9 +153,18 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(item['name'], 'Hestia') self.assertEqual(item['installation_path'], hestia) self.assertEqual(item['listening_ports'], [8083]) + self.assertEqual(item['default_ports'], [8083]) + self.assertEqual(item['scheme'], 'https') self.assertIn({'kind': 'working_directory', 'value': '/'}, item['evidence']) self.assertIn('metadata_limit', result['issues']) + unrelated = native.candidate(101, { + 'name': 'other-panel', 'path': '/opt/other-panel/server', 'root': '/opt/other-panel', + 'process': '/opt/other-panel/server', 'listening_ports': [8083], + }, 'FILE\n') + self.assertEqual(unrelated['default_ports'], []) + self.assertNotIn('scheme', unrelated) + self.calls.clear() result = native.scan(101, self.runner( services='END\n', processes='LIMIT\n', ports='END\n')) diff --git a/tests/lxc_updates/test_native_app_discovery_ui.cjs b/tests/lxc_updates/test_native_app_discovery_ui.cjs index 3bb55dc4..06a77f2e 100644 --- a/tests/lxc_updates/test_native_app_discovery_ui.cjs +++ b/tests/lxc_updates/test_native_app_discovery_ui.cjs @@ -23,7 +23,7 @@ const candidate = { evidence: [{kind: 'installation_file', value: '/opt/handmade/server'}], default_ports: [], } -function fixture() { +function fixture({nativeCandidate = candidate} = {}) { let cursor = 0, tree, props, effects = [], states = [], refs = [], effectStates = [] const calls = [], cachedWrites = [] let native = {vmid: 101, status: 'not_scanned', candidates: [], stale: false} @@ -49,7 +49,7 @@ function fixture() { if (url.endsWith('/native-discovery')) { if (options.method === 'POST') { if (scanGate) await scanGate - native = {...native, status: scanStatus, candidates: [candidate]} + native = {...native, status: scanStatus, candidates: [nativeCandidate]} } return structuredClone(native) } @@ -123,6 +123,16 @@ async function main() { assert.equal(payload.repo, '') assert.equal(payload.upstream_type, '') + const hestia = fixture({nativeCandidate: { + ...candidate, name: 'Hestia', default_ports: [8083], scheme: 'https', + }}) + hestia.render(); await hestia.settle() + await hestia.button('searchApplications').props.onClick(); hestia.render() + await hestia.button('registerButton').props.onClick(); hestia.render() + await hestia.button('saveButton').props.onClick(); hestia.render() + const hestiaPayload = JSON.parse(hestia.calls.find(c => /\/apps$/.test(c.url) && c.method === 'POST').body) + assert.deepEqual(hestiaPayload.ports, [{port: 8083, scheme: 'https', web_path: ''}], + 'a verified Hestia panel pre-fills only its fixed HTTPS endpoint') const partial = fixture() partial.render(); await partial.settle(); partial.setScanStatus('partial') await partial.button('searchApplications').props.onClick(); partial.render() From c393a56b92d236080d8d9323db0076b2bb3f25e1 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:31:51 +0000 Subject: [PATCH 11/53] fix: preserve native discovery results after delayed cache reads --- AppImage/components/lxc-app-panel.tsx | 7 ++++++- .../test_native_app_discovery_ui.cjs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/AppImage/components/lxc-app-panel.tsx b/AppImage/components/lxc-app-panel.tsx index 24003d66..22154ef6 100644 --- a/AppImage/components/lxc-app-panel.tsx +++ b/AppImage/components/lxc-app-panel.tsx @@ -475,6 +475,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: const [sidecar, setSidecar] = useState(seed?.sidecar ?? null) const [suggestions, setSuggestions] = useState(seed?.suggestions ?? null) const [nativeDiscovery, setNativeDiscovery] = useState(null) + const nativeRequest = useRef(0) const targetRef = useRef(vmid) targetRef.current = vmid const targetGeneration = useRef(0) @@ -485,6 +486,7 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: } useEffect(() => { const generation = targetGeneration.current + const request = ++nativeRequest.current setNativeDiscovery(null) setEditing(null) setDetectionNotice(null) @@ -492,7 +494,8 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: setSaving(false) if (managed) return fetchApi(`/api/vms/${vmid}/apps/native-discovery`).then(result => { - if (targetRef.current === vmid && targetGeneration.current === generation) setNativeDiscovery(result) + if (targetRef.current === vmid && targetGeneration.current === generation + && nativeRequest.current === request) setNativeDiscovery(result) }).catch(() => { /* cached metadata is optional; never initiate a scan */ }) return () => { targetGeneration.current += 1 } }, [vmid, managed]) @@ -787,7 +790,9 @@ export function LxcAppPanel({ vmid, ctIp, onChange, managed, initialData, oci }: const searchInstalledApplications = async () => { const generation = targetGeneration.current + const request = ++nativeRequest.current const isCurrent = () => targetRef.current === vmid && targetGeneration.current === generation + && nativeRequest.current === request const before = new Set([...visibleDetected, ...visibleWorkloads].map((item) => item.slug)) setSearchingApplications(true) setDetectionNotice(null) diff --git a/tests/lxc_updates/test_native_app_discovery_ui.cjs b/tests/lxc_updates/test_native_app_discovery_ui.cjs index 06a77f2e..2ffa802e 100644 --- a/tests/lxc_updates/test_native_app_discovery_ui.cjs +++ b/tests/lxc_updates/test_native_app_discovery_ui.cjs @@ -28,6 +28,7 @@ function fixture({nativeCandidate = candidate} = {}) { const calls = [], cachedWrites = [] let native = {vmid: 101, status: 'not_scanned', candidates: [], stale: false} let holdScan, scanGate, holdSave, saveGate, scanStatus = 'complete' + let cacheGate, resolveCache, cachePending = false const seed = {sidecar: {vmid: 101, apps: []}, suggestions: {extras: [], port_suggestions: []}} const react = { useState(initial) { @@ -47,6 +48,12 @@ function fixture({nativeCandidate = candidate} = {}) { const api = async (url, options = {}) => { calls.push({url, ...options}) if (url.endsWith('/native-discovery')) { + if (options.method !== 'POST' && cachePending) { + cachePending = false + const oldSnapshot = structuredClone(native) + await cacheGate + return oldSnapshot + } if (options.method === 'POST') { if (scanGate) await scanGate native = {...native, status: scanStatus, candidates: [nativeCandidate]} @@ -92,6 +99,8 @@ function fixture({nativeCandidate = candidate} = {}) { } return {render, settle, calls, cachedWrites, button, tree: () => tree, setNative: value => {native = value}, + holdInitialCache: () => {cachePending = true; cacheGate = new Promise(resolve => {resolveCache = resolve})}, + resolveInitialCache: () => resolveCache(), setScanStatus: value => {scanStatus = value}, holdScan: () => {scanGate = new Promise(resolve => {holdScan = resolve})}, resolveScan: () => holdScan(), holdSave: () => {saveGate = new Promise(resolve => {holdSave = resolve})}, resolveSave: () => holdSave(), @@ -133,6 +142,15 @@ async function main() { const hestiaPayload = JSON.parse(hestia.calls.find(c => /\/apps$/.test(c.url) && c.method === 'POST').body) assert.deepEqual(hestiaPayload.ports, [{port: 8083, scheme: 'https', web_path: ''}], 'a verified Hestia panel pre-fills only its fixed HTTPS endpoint') + const delayedCache = fixture() + delayedCache.holdInitialCache() + delayedCache.render(); await delayedCache.settle() + await delayedCache.button('searchApplications').props.onClick(); delayedCache.render() + assert.ok(text(delayedCache.tree()).includes('/opt/handmade/server')) + delayedCache.resolveInitialCache(); await delayedCache.settle() + assert.ok(text(delayedCache.tree()).includes('/opt/handmade/server'), + 'a delayed initial cache read must not erase a completed explicit search') + const partial = fixture() partial.render(); await partial.settle(); partial.setScanStatus('partial') await partial.button('searchApplications').props.onClick(); partial.render() From 22098042ba637e283f50344292510d4780f3b724 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:53:30 +0000 Subject: [PATCH 12/53] fix: exclude Alpine service supervision from native discovery --- AppImage/scripts/native_app_discovery.py | 2 ++ .../scripts/tests/test_native_app_discovery.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 5ade84bd..29a56b7b 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -46,6 +46,8 @@ _NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.+@-]{0,63}\Z') _VERSION = re.compile(r'v?\d+(?:\.\d+){1,4}(?:[-+._][A-Za-z0-9.-]+)?\Z') _SYSTEM = { 'ssh', 'sshd', 'cron', 'crond', 'postfix', 'rsyslog', 'syslog', 'syslog-ng', + # Alpine/OpenRC system logging and service supervision are not apps. + 'klogd', 'syslogd', 'openrc-user', 'runit', 'runsvdir', 'runsv', 'supervise-daemon', 'dbus', 'dbus-broker', 'systemd', 'udev', 'networking', 'network', 'networkmanager', 'dhcpcd', 'dhclient', 'chrony', 'chronyd', 'ntp', 'ntpd', 'openntpd', 'agetty', 'getty', 'login', 'polkit', 'polkitd', 'accounts-daemon', 'acpid', 'anacron', diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index d1432b65..5e90b4cf 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -126,6 +126,24 @@ class DiscoveryFixtures(unittest.TestCase): self.assertEqual(result['candidates'], []) self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_alpine_openrc_infrastructure_has_zero_footprint_probes(self): + # Actual Alpine 3.24.1 CT217 init metadata: klogd, user and runsvdir. + # The runsvdir definition can exist without /usr/bin/runsvdir installed; + # rejecting it must precede probing, not turn the scan into partial. + for name, executable in ( + ('klogd', '/sbin/klogd'), + ('user', '/usr/libexec/rc/bin/openrc-user'), + ('runsvdir', '/usr/bin/runsvdir'), + ('renamed-system-service', '/usr/libexec/rc/bin/openrc-user'), + ): + with self.subTest(name=name): + self.calls.clear() + result = native.scan(101, self.runner( + service(name=name, exe=executable, directory='', manager='openrc'))) + self.assertEqual(result['status'], 'complete') + self.assertEqual(result['candidates'], []) + self.assertFalse(any(native.FOOTPRINT_SCRIPT in call for call in self.calls)) + def test_helper_services_and_templates_have_zero_footprint_probes(self): cases = [ ('netpulse-stable-update', '/bin/bash', '/opt/netpulse', '/opt/netpulse/stable-apply.sh'), From 7aec721eb5b8c2bc6616a3a136efa763478f6e21 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:23:12 +0000 Subject: [PATCH 13/53] fix: invalidate native scans across lifecycle changes and serialize updates --- AppImage/scripts/native_app_discovery.py | 75 ++++++++-- .../tests/test_native_app_discovery.py | 139 +++++++++++++++++- 2 files changed, 201 insertions(+), 13 deletions(-) diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index 29a56b7b..ac9daa9e 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -17,6 +17,7 @@ """Separate from catalog detection: callers must explicitly request a scan.""" import copy +from contextlib import contextmanager import fcntl import hashlib import json @@ -25,6 +26,7 @@ from pathlib import Path import re import selectors import signal +import stat import subprocess import threading import time @@ -40,6 +42,7 @@ OCI_ROOT = Path('/usr/local/share/proxmenux/oci/instances') UPDATE_LOCK_ROOT = Path('/run/lock') _cache = {} _locks = {} +_generations = {} _mutex = threading.Lock() _PATH = re.compile(r'/[A-Za-z0-9_.+/@-]{1,510}\Z') _NAME = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.+@-]{0,63}\Z') @@ -509,7 +512,13 @@ def _guard(vmid, deadline, run): raise ProbeError('guest_busy') update_lock = UPDATE_LOCK_ROOT / f'proxmenux-lxc-update-{vmid}.lock' try: - with update_lock.open('rb') as handle: + fd = os.open(update_lock, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + with os.fdopen(fd, 'rb') as handle: + info = os.fstat(handle.fileno()) + if not stat.S_ISREG(info.st_mode): + raise ProbeError('lock_unavailable') + if info.st_size: + raise ProbeError('guest_busy') try: fcntl.flock(handle, fcntl.LOCK_SH | fcntl.LOCK_NB) except BlockingIOError: @@ -529,9 +538,45 @@ def _guard(vmid, deadline, run): raise ProbeError('guest_stopped' if status == 'status: stopped' else 'status_unknown') +@contextmanager +def _scan_reservation(vmid): + """Exclude cooperating updates/control tasks for this bounded scan only. + + Keep an empty lock inode after first use, as the update runner does. + Never truncate a pending control-task reservation or unlink its inode. + """ + path = UPDATE_LOCK_ROOT / f'proxmenux-lxc-update-{vmid}.lock' + fd = None + try: + fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600) + if not stat.S_ISREG(os.fstat(fd).st_mode): + raise ProbeError('lock_unavailable') + try: + fcntl.flock(fd, fcntl.LOCK_SH | fcntl.LOCK_NB) + except BlockingIOError: + raise ProbeError('guest_busy') from None + if os.fstat(fd).st_size: + raise ProbeError('guest_busy') + yield + except OSError: + raise ProbeError('lock_unavailable') from None + finally: + if fd is not None: + os.close(fd) + + def scan(vmid, run=bounded_run): deadline = time.monotonic() + TOTAL_SECONDS _guard(vmid, deadline, run) + with _scan_reservation(vmid): + result = _scan_metadata(vmid, deadline, run) + # External PVE lifecycle operations do not share our flock. Fail closed + # if the guest stopped, changed ownership or became locked meanwhile. + _guard(vmid, deadline, run) + return result + + +def _scan_metadata(vmid, deadline, run): records, issues, consumed, processes, listeners, hestia_panels = [], [], 0, [], [], [] for kind, script in (('S', SERVICE_SCRIPT), ('P', PROCESS_SCRIPT), ('L', PORT_SCRIPT)): try: @@ -637,6 +682,7 @@ def cached(vmid): def mark_stale(vmid): """Lifecycle events retire freshness, never collect new guest metadata.""" with _mutex: + _generations[int(vmid)] = _generations.get(int(vmid), 0) + 1 if int(vmid) in _cache: _cache[int(vmid)]['stale'] = True @@ -648,23 +694,28 @@ def discover(vmid): if not lock.acquire(blocking=False): return {**cached(vmid), 'status': 'busy', 'stale': True} try: + with _mutex: + generation = _generations.get(vmid, 0) try: result = scan(vmid) except ProbeError as exc: result = {**cached(vmid), 'status': 'error', 'issues': [str(exc)], 'stale': True} result.setdefault('scanned_at', time.time()) - with _mutex: - _cache[vmid] = copy.deepcopy(result) - return result - previous = cached(vmid) - if result['status'] != 'complete' and previous['candidates']: - # Partial output cannot prove that previously seen apps disappeared. - ids = {item['discovery_id'] for item in result['candidates']} - result['candidates'].extend(item for item in previous['candidates'] - if item['discovery_id'] not in ids) - result['candidates'] = result['candidates'][:MAX_CANDIDATES] - result['stale'] = True + # Comparing the generation and publishing must be atomic with lifecycle + # invalidation, including the first scan when no cache exists yet. with _mutex: + previous = copy.deepcopy(_cache.get(vmid)) + if _generations.get(vmid, 0) != generation: + result = {**(previous or {'vmid': vmid, 'candidates': [], + 'scanned_at': time.time()}), + 'status': 'error', 'issues': ['guest_changed'], 'stale': True} + elif result['status'] != 'complete' and previous and previous['candidates']: + # Partial output cannot prove that previously seen apps disappeared. + ids = {item['discovery_id'] for item in result['candidates']} + result['candidates'].extend(item for item in previous['candidates'] + if item['discovery_id'] not in ids) + result['candidates'] = result['candidates'][:MAX_CANDIDATES] + result['stale'] = True _cache[vmid] = copy.deepcopy(result) return result finally: diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index 5e90b4cf..397bb3fc 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -42,6 +42,7 @@ class DiscoveryFixtures(unittest.TestCase): self.addCleanup(context.stop) native._cache.clear() native._locks.clear() + native._generations.clear() self.calls = [] def runner(self, services=None, processes='END\n', footprint='FILE\n', manifest=None, @@ -375,6 +376,142 @@ class DiscoveryFixtures(unittest.TestCase): with patch.object(native, 'scan', return_value={**complete, 'candidates': []}): self.assertEqual(native.discover(101)['candidates'], []) + def test_lifecycle_during_first_or_cached_scan_cannot_publish_fresh_result(self): + complete = native.scan(101, self.runner()) + for prior in (None, complete): + with self.subTest(cached=prior is not None): + native._cache.clear() + if prior: + native._cache[101] = copy.deepcopy(prior) + entered, release = threading.Event(), threading.Event() + results = [] + def delayed(vmid): + entered.set() + if not release.wait(3): + raise AssertionError('scan release timed out') + return copy.deepcopy(complete) + with patch.object(native, 'scan', side_effect=delayed): + worker = threading.Thread(target=lambda: results.append(native.discover(101))) + worker.start() + try: + self.assertTrue(entered.wait(3)) + native.mark_stale(101) + finally: + release.set() + worker.join(3) + self.assertFalse(worker.is_alive()) + self.assertEqual(len(results), 1) + self.assertTrue(results[0]['stale']) + self.assertIn('guest_changed', results[0]['issues']) + self.assertEqual(results[0]['candidates'], prior['candidates'] if prior else []) + self.assertTrue(native.cached(101)['stale']) + with patch.object(native, 'scan', return_value=copy.deepcopy(complete)): + self.assertFalse(native.discover(101)['stale']) + with patch.object(native, 'scan', side_effect=lambda vmid: ( + native.mark_stale(102), copy.deepcopy(complete))[1]): + self.assertFalse(native.discover(101)['stale']) + + def test_scan_excludes_later_updater_and_releases_lock_after_success_or_failure(self): + path = self.root / 'proxmenux-lxc-update-101.lock' + for fail in (False, True): + with self.subTest(fail=fail): + runner = self.runner() + probes = [] + def checked(argv, deadline): + if argv[1] == 'exec': + attempt = subprocess.run(['flock', '-n', str(path), 'true']) + self.assertEqual(attempt.returncode, 1) + probes.append(argv) + if fail: + raise native.ProbeError('timeout') + return runner(argv, deadline) + native.scan(101, checked) + self.assertTrue(probes) + self.assertEqual(subprocess.run(['flock', '-n', str(path), 'true']).returncode, 0) + self.assertEqual(path.read_bytes(), b'') + + def test_pending_control_reservation_blocks_scan_without_touching_it(self): + path = self.root / 'proxmenux-lxc-update-101.lock' + original = b'{"task":"pending-control"}' + path.write_bytes(original) + with self.assertRaisesRegex(native.ProbeError, 'guest_busy'): + native.scan(101, self.runner()) + self.assertFalse(any(call[1] == 'exec' for call in self.calls)) + self.assertEqual(path.read_bytes(), original) + + def test_guest_stop_during_scan_never_returns_complete(self): + runner = self.runner() + statuses = iter(['status: running', 'status: stopped']) + def stopped(argv, deadline): + return next(statuses) if argv[1] == 'status' else runner(argv, deadline) + with self.assertRaisesRegex(native.ProbeError, 'guest_stopped'): + native.scan(101, stopped) + self.assertTrue(all(call[1] == 'exec' for call in self.calls)) + + def test_scan_reservation_closes_race_after_guard_and_releases_after_exception(self): + import lxc_check_job + path = self.root / 'proxmenux-lxc-update-101.lock' + runner = self.runner() + handles = [] + def starts_update(argv, deadline): + if argv[1] == 'status': + handles.append(lxc_check_job.acquire(101)) + return runner(argv, deadline) + with patch.object(lxc_check_job, 'LOCK_DIR', self.root): + try: + with self.assertRaisesRegex(native.ProbeError, 'guest_busy'): + native.scan(101, starts_update) + self.assertFalse(any(call[1] == 'exec' for call in self.calls)) + finally: + for handle in handles: + handle.close() + def crash(argv, deadline): + if argv[1] == 'exec': + with self.assertRaises(BlockingIOError): + lxc_check_job.acquire(101) + # Another CT must remain available. + lxc_check_job.acquire(102).close() + raise RuntimeError('unexpected probe failure') + return runner(argv, deadline) + with self.assertRaisesRegex(RuntimeError, 'unexpected'): + native.scan(101, crash) + lxc_check_job.acquire(101).close() + self.assertEqual(path.read_bytes(), b'') + + def test_failed_scan_releases_discovery_lock_and_keeps_prior_evidence(self): + complete = native.scan(101, self.runner()) + native._cache[101] = copy.deepcopy(complete) + with patch.object(native, 'scan', side_effect=RuntimeError('unexpected')): + with self.assertRaises(RuntimeError): + native.discover(101) + self.assertFalse(native._locks[101].locked()) + self.assertEqual(native.cached(101)['candidates'], complete['candidates']) + + def test_invalid_lock_inode_and_unreadable_config_fail_before_any_probe(self): + import os + path = self.root / 'proxmenux-lxc-update-101.lock' + target = self.root / 'unrelated' + target.write_bytes(b'keep') + for kind in ('symlink', 'fifo', 'directory'): + with self.subTest(kind=kind): + if kind == 'symlink': + path.symlink_to(target) + elif kind == 'fifo': + os.mkfifo(path) + else: + path.mkdir() + try: + with self.assertRaisesRegex(native.ProbeError, 'lock_unavailable'): + native.scan(101, self.runner()) + self.assertEqual(self.calls, []) + self.assertEqual(target.read_bytes(), b'keep') + finally: + path.rmdir() if kind == 'directory' else path.unlink() + with patch.object(Path, 'open', side_effect=PermissionError): + with self.assertRaisesRegex(native.ProbeError, 'config_unavailable'): + native.scan(101, self.runner()) + self.assertEqual(self.calls, []) + def test_same_ct_busy_and_different_ct_cache_isolation(self): lock = threading.Lock() lock.acquire() @@ -546,7 +683,7 @@ class Routes(unittest.TestCase): '_VM_APP_SUGGESTIONS_TTL': 300, '_vm_cache_get': lambda cache, vmid, ttl: cache.get(vmid), '_vm_cache_put': lambda cache, vmid, value: cache.update({vmid: value}), - '_pvesh_cache': {}, '_vm_disk_cache': {}, '_vm_cache_invalidate': Mock(), + '_pvesh_cache': {}, '_vm_disk_cache': {}, '_vm_schedule_cache': {}, '_vm_cache_invalidate': Mock(), '_invalidate_lxc_ip': Mock(), '_schedule_started_guest_refresh': Mock(), '_publish_guest_modal_cache_revision': Mock()} tree = ast.parse((SCRIPTS / 'flask_server.py').read_text()) From 427c3dc2b9fa2bcadaf0930a5429cf1c94a8087a Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:08:53 +0200 Subject: [PATCH 14/53] docs: review networking documentation in all seven translations --- .github/workflows/test-i18n.yml | 10 ++ .../de/docs/network/backup-restore.json | 110 ++++++++++++++ .../de/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../de/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/de/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/de/docs/network/index.json | 107 ++++++++++++++ web/messages/de/docs/network/monitoring.json | 136 ++++++++++++++++++ .../de/docs/network/persistent-names.json | 130 +++++++++++++++++ web/messages/en/docs/network/monitoring.json | 2 +- .../es/docs/network/backup-restore.json | 8 +- .../es/docs/network/bridge-analysis.json | 18 +-- .../es/docs/network/config-analysis.json | 14 +- web/messages/es/docs/network/diagnostics.json | 16 +-- web/messages/es/docs/network/index.json | 16 +-- web/messages/es/docs/network/monitoring.json | 28 ++-- .../es/docs/network/persistent-names.json | 18 +-- .../fr/docs/network/backup-restore.json | 110 ++++++++++++++ .../fr/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../fr/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/fr/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/fr/docs/network/index.json | 107 ++++++++++++++ web/messages/fr/docs/network/monitoring.json | 136 ++++++++++++++++++ .../fr/docs/network/persistent-names.json | 130 +++++++++++++++++ .../it/docs/network/backup-restore.json | 110 ++++++++++++++ .../it/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../it/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/it/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/it/docs/network/index.json | 107 ++++++++++++++ web/messages/it/docs/network/monitoring.json | 136 ++++++++++++++++++ .../it/docs/network/persistent-names.json | 130 +++++++++++++++++ .../pt/docs/network/backup-restore.json | 110 ++++++++++++++ .../pt/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../pt/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/pt/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/pt/docs/network/index.json | 107 ++++++++++++++ web/messages/pt/docs/network/monitoring.json | 136 ++++++++++++++++++ .../pt/docs/network/persistent-names.json | 130 +++++++++++++++++ .../sk/docs/network/backup-restore.json | 110 ++++++++++++++ .../sk/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../sk/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/sk/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/sk/docs/network/index.json | 107 ++++++++++++++ web/messages/sk/docs/network/monitoring.json | 136 ++++++++++++++++++ .../sk/docs/network/persistent-names.json | 130 +++++++++++++++++ .../sv/docs/network/backup-restore.json | 110 ++++++++++++++ .../sv/docs/network/bridge-analysis.json | 132 +++++++++++++++++ .../sv/docs/network/config-analysis.json | 122 ++++++++++++++++ web/messages/sv/docs/network/diagnostics.json | 94 ++++++++++++ web/messages/sv/docs/network/index.json | 107 ++++++++++++++ web/messages/sv/docs/network/monitoring.json | 136 ++++++++++++++++++ .../sv/docs/network/persistent-names.json | 130 +++++++++++++++++ 51 files changed, 5056 insertions(+), 60 deletions(-) create mode 100644 web/messages/de/docs/network/backup-restore.json create mode 100644 web/messages/de/docs/network/bridge-analysis.json create mode 100644 web/messages/de/docs/network/config-analysis.json create mode 100644 web/messages/de/docs/network/diagnostics.json create mode 100644 web/messages/de/docs/network/index.json create mode 100644 web/messages/de/docs/network/monitoring.json create mode 100644 web/messages/de/docs/network/persistent-names.json create mode 100644 web/messages/fr/docs/network/backup-restore.json create mode 100644 web/messages/fr/docs/network/bridge-analysis.json create mode 100644 web/messages/fr/docs/network/config-analysis.json create mode 100644 web/messages/fr/docs/network/diagnostics.json create mode 100644 web/messages/fr/docs/network/index.json create mode 100644 web/messages/fr/docs/network/monitoring.json create mode 100644 web/messages/fr/docs/network/persistent-names.json create mode 100644 web/messages/it/docs/network/backup-restore.json create mode 100644 web/messages/it/docs/network/bridge-analysis.json create mode 100644 web/messages/it/docs/network/config-analysis.json create mode 100644 web/messages/it/docs/network/diagnostics.json create mode 100644 web/messages/it/docs/network/index.json create mode 100644 web/messages/it/docs/network/monitoring.json create mode 100644 web/messages/it/docs/network/persistent-names.json create mode 100644 web/messages/pt/docs/network/backup-restore.json create mode 100644 web/messages/pt/docs/network/bridge-analysis.json create mode 100644 web/messages/pt/docs/network/config-analysis.json create mode 100644 web/messages/pt/docs/network/diagnostics.json create mode 100644 web/messages/pt/docs/network/index.json create mode 100644 web/messages/pt/docs/network/monitoring.json create mode 100644 web/messages/pt/docs/network/persistent-names.json create mode 100644 web/messages/sk/docs/network/backup-restore.json create mode 100644 web/messages/sk/docs/network/bridge-analysis.json create mode 100644 web/messages/sk/docs/network/config-analysis.json create mode 100644 web/messages/sk/docs/network/diagnostics.json create mode 100644 web/messages/sk/docs/network/index.json create mode 100644 web/messages/sk/docs/network/monitoring.json create mode 100644 web/messages/sk/docs/network/persistent-names.json create mode 100644 web/messages/sv/docs/network/backup-restore.json create mode 100644 web/messages/sv/docs/network/bridge-analysis.json create mode 100644 web/messages/sv/docs/network/config-analysis.json create mode 100644 web/messages/sv/docs/network/diagnostics.json create mode 100644 web/messages/sv/docs/network/index.json create mode 100644 web/messages/sv/docs/network/monitoring.json create mode 100644 web/messages/sv/docs/network/persistent-names.json diff --git a/.github/workflows/test-i18n.yml b/.github/workflows/test-i18n.yml index 2b3e4685..7f4dfb51 100644 --- a/.github/workflows/test-i18n.yml +++ b/.github/workflows/test-i18n.yml @@ -9,15 +9,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' push: branches: [main, develop] paths: @@ -27,15 +32,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' workflow_dispatch: permissions: diff --git a/web/messages/de/docs/network/backup-restore.json b/web/messages/de/docs/network/backup-restore.json new file mode 100644 index 00000000..fc81f50a --- /dev/null +++ b/web/messages/de/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Schnittstellen sichern und neu starten | ProxMenux-Dokumentation", + "description": "Manuelle Schnappschüsse erstellen und unter /var/backups/proxmenux/ gespeicherte Backups von /etc/network/interfaces durchsuchen und wiederherstellen. Mit bestätigtem Neustart des Netzwerkdienstes und Schnellansicht der aktuellen Konfiguration.", + "ogTitle": "Schnittstellen sichern und neu starten | ProxMenux-Dokumentation", + "ogDescription": "Snapshot und Wiederherstellung der Proxmox-Netzwerkkonfiguration; Neustart des Netzwerkdienstes mit Bestätigung." + }, + "header": { + "title": "Schnittstellen sichern und neu starten", + "description": "Vier Werkzeuge für /etc/network/interfaces: einen manuellen Schnappschuss erstellen, die aktuelle Konfiguration anzeigen, ein früheres Backup mit optionaler Vorschau wiederherstellen und den Netzwerkdienst nach ausdrücklicher Zustimmung neu starten. Sie verwenden dasselbe Backup-Verzeichnis wie die geführten Reparaturen.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Verwaltet das gleiche Backup-Verzeichnis, das von den geführten Reparaturflüssen verwendet wird (/var/backups/proxmenux/). Sie können vor jeder manuellen Änderung eine zusätzliche Momentaufnahme machen, alle vorhandenen Backups durchsuchen und zu einem von ihnen zurückkehren - immer mit einem neuen Pre-Restore-Backup, das automatisch als zweites Sicherheitsnetz übernommen wird." + }, + "shared": { + "heading": "Das gemeinsame Backup-Verzeichnis", + "intro": "Jedes Backup im Netzwerkmenü landet an der gleichen Stelle:", + "outro": "Dateinamen werden mit einem Zeitstempel versehen, nach Datum sortiert und nie überschrieben. Das Verzeichnis wird bei der ersten Verwendung erstellt." + }, + "show": { + "heading": "Netzwerkkonfigurationsdatei anzeigen", + "body": "Gibt /etc/network/interfaces unverändert im Terminal aus. Schreibgeschützt. Nützlich zur Kontrolle vor einem Backup oder nach einer Wiederherstellung. Es gibt kein separates Werkzeug zum Vergleich mit dem vorherigen Backup; die aktuelle Datei direkt zu prüfen ist daher am einfachsten." + }, + "create": { + "heading": "Netzwerk-Backup erstellen", + "body": "Kopiert den aktuellen /etc/network/interfaces mit einem neuen Zeitstempel in das Backup-Verzeichnis. Das ist es — keine Analyse, keine Aufforderungen, nur eine Momentaufnahme.", + "whenTitle": "Wann Sie ein manuelles Backup durchführen sollten", + "whenBody": "Vor der manuellen Bearbeitung von /etc/network/interfaces, vor der Installation eines Pakets, das den Netzwerkstack ändern kann (z. B. NetworkManager, Open vSwitch), oder vor Hardwareänderungen. Geführte Reparaturen erstellen bereits automatisch einen Schnappschuss — diese Option ist für manuelle Eingriffe." + }, + "restore": { + "heading": "Netzwerksicherung wiederherstellen", + "intro": "Listet alle Backups in /var/backups/proxmenux/ vom neuesten zum ältesten auf und führt durch eine abgesicherte Wiederherstellung:", + "steps": [ + { + "title": "1. Wählen Sie ein Backup", + "body": "Ein Menü listet jedes Backup nach seinem Zeitstempel auf. Wenn keine Backups vorhanden sind, wird der Flow mit einer klaren Nachricht beendet.", + "tone": "blue" + }, + { + "title": "2. Optionale Vorschau", + "body": "Bietet an, das ausgewählte Backup in einer scrollbaren Ansicht zu öffnen, bevor Sie es festlegen. Standardmäßig ja – überspringen Sie es nicht auf einem entfernten Host.", + "tone": "blue" + }, + { + "title": "3. Backup vor der Wiederherstellung", + "body": "Erstellt vor dem Überschreiben von /etc/network/interfaces ein weiteres Backup des aktuellen Zustands. Eine Wiederherstellung ist selbst ein destruktiver Vorgang. Mit dem neuen Schnappschuss können Sie zurückkehren, falls das gewählte Backup falsch war.", + "tone": "amber" + }, + { + "title": "4. Anwenden der Wiederherstellung", + "body": "Kopiert das ausgewählte Backup über /etc/network/interfaces. Die Dateiänderung ist auf der Festplatte; der Live-Kernel-Zustand spiegelt immer noch die vorherige Konfiguration wider.", + "tone": "amber" + }, + { + "title": "5. Optionaler Neustart", + "body": "Fragt, ob systemctl restart networking jetzt ausgeführt werden soll. Lehnen Sie ab, um die Änderung auf den nächsten Neustart zu verschieben.", + "tone": "emerald" + } + ], + "autoBackupTitle": "Jede Wiederherstellung erstellt automatisch ein eigenes Backup", + "autoBackupBody": "Die Auswahl von Netzwerksicherung wiederherstellen erstellt immer einen neuen Schnappschuss der aktuellen Konfiguration, bevor sie überschrieben wird. Falls Sie das falsche Backup wiederherstellen, enthält die neueste Datei in /var/backups/proxmenux/ den vorherigen Zustand." + }, + "restart": { + "heading": "Starten Sie den Netzwerkdienst neu", + "body": "Führt nach einer Ja/Nein-Bestätigung systemctl restart networking aus. Meldet das Ergebnis mit msg_ok / msg_error. Die meisten Abläufe in diesem Menü bieten am Ende selbst einen Neustart an. Diese Option dient zum Neustart nach manueller Bearbeitung oder wenn Sie den zuvor angebotenen Neustart abgelehnt haben.", + "warnTitle": "Kurze Trennung garantiert; dauerhafte Trennung möglich", + "warnBody": "Ein Neustart von networking deaktiviert alle in /etc/network/interfaces deklarierten Schnittstellen und wendet ihre Konfiguration erneut an. SSH-Sitzungen über diese Schnittstellen brechen ab. Ist die neue Konfiguration ungültig, kehrt das Netzwerk nicht zurück. Nur ausführen, wenn:", + "warnItems": [ + "Sie Konsolen-/IPMI-/iKVM-Zugriff bereithalten, oder", + "Sie sich direkt neben dem Rechner befinden, oder", + "Sie haben gerade erfolgreich zu einer bekannten guten Konfiguration zurückgerollt und wollen sie anwenden." + ] + }, + "manualRollback": { + "heading": "Manuelles Rollback von einer Konsole", + "intro": "Wenn Sie das Menü nicht erreichen können (kein SSH, Dialogabsturz, ...), aber eine Konsole erreichen können, besteht das Rollback aus zwei Befehlen:", + "outro": "Das ist dieselbe Operation wie im Menü, nur von Hand eingegeben. Startet networking weiterhin nicht, prüfen Sie journalctl -u networking -b auf den zugrunde liegenden Syntax- oder Treiberfehler." + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "noneTitle": "„Keine Backups gefunden“ bei der Wiederherstellung", + "noneBody": "Das Verzeichnis /var/backups/proxmenux/ existiert nicht oder enthält keine interfaces_backup_*-Dateien. Führen Sie Netzwerk-Backup erstellen mindestens einmal aus oder starten Sie eine geführte Reparatur, die automatisch ein Backup erstellt. Prüfen Sie mit ls /var/backups/proxmenux/.", + "unreachTitle": "Der Netzwerkneustart gelingt, aber der Host ist weiterhin nicht erreichbar", + "unreachBody": "Entweder ist die neue Konfiguration falsch oder die beim Neustart angewendete Konfiguration weist Ihre Verwaltungs-IP nicht zu. Setzen Sie sie über die Konsole zurück (siehe oben). Nutzen Sie danach Brückenanalyse und Konfigurationsanalyse, um den Fehler vor einem erneuten Anwenden zu verstehen.", + "emptyTitle": "Die Wiederherstellungsvorschau zeigt eine leere Datei", + "emptyBody": "Die Backup-Datei ist leer (0 Bytes). Das passiert, wenn ein Kopiervorgang bei einem früheren Durchlauf unbemerkt fehlgeschlagen ist. Wählen Sie ein älteres Backup oder führen Sie Netzwerk-Backup erstellen jetzt erneut aus, wenn die aktuelle Konfiguration funktioniert." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Brückenanalyse und geführte Reparatur", + "href": "/docs/network/bridge-analysis", + "tail": " — der häufigste Grund, warum hier ein Backup erstellt wird." + }, + { + "label": "Konfigurationsanalyse und geführte Bereinigung", + "href": "/docs/network/config-analysis", + "tailRich": "— schreibt auch automatisch an /var/backups/proxmenux/." + }, + { + "label": "Diagnose", + "href": "/docs/network/diagnostics", + "tail": "- Überprüfen Sie den Live-Zustand nach einer Wiederherstellung." + } + ] + } +} diff --git a/web/messages/de/docs/network/bridge-analysis.json b/web/messages/de/docs/network/bridge-analysis.json new file mode 100644 index 00000000..00d03038 --- /dev/null +++ b/web/messages/de/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Brückenanalyse & geführte Reparatur | ProxMenux Dokumentation", + "description": "Erkennt vmbrX-Bridges mit fehlenden oder ungültigen physischen Ports (typisch nach PCI-Reenumeration), meldet diese mit vorgeschlagenen Shell-Befehlen und bietet eine 5-schrittgesteuerte Reparatur mit obligatorischem Backup.", + "ogTitle": "Brückenanalyse & geführte Reparatur | ProxMenux Dokumentation", + "ogDescription": "Proxmox-Bridge-Ports nach Hardwareänderungen prüfen und reparieren, mit geführtem Ablauf und sicherer Rückkehr zur vorherigen Konfiguration." + }, + "header": { + "title": "Brückenanalyse und geführte Reparatur", + "description": "Prüft jede in /etc/network/interfaces deklarierte vmbrX-Bridge und ob ihre physischen Ports tatsächlich existieren. Fehlende Ports können schrittweise repariert werden. Die Analyse ist schreibgeschützt; die Reparatur erfordert Zustimmung, zeigt eine Vorschau und erstellt ein Backup.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Liest jede Bridge in /etc/network/interfaces, prüft ihre Zeile bridge-ports und ob jeder deklarierte Port, etwa enp3s0, auf dem Host existiert. Fehlt ein Port, schlägt das Werkzeug einen Ersatz vor und wendet die Änderung nach ausdrücklicher Zustimmung mit dem Ablauf sichern, Vorschau anzeigen, anwenden, überprüfen an." + }, + "when": { + "heading": "Wenn Sie das brauchen", + "intro": "Linux weist vorhersehbare Schnittstellennamen aus PCI-Topologie und Slot-Reihenfolge zu. Mehrere Ereignisse mischen sie und lassen /etc/network/interfaces auf Namen verweisen, die nicht mehr existieren:", + "items": [ + "Hinzufügen oder Entfernen einer PCIe-Karte (ein NIC, ein GPU, ein HBA, ein NVMe-Träger).", + "Verschieben einer vorhandenen Karte in einen anderen PCIe-Slot.", + "BIOS / UEFI Updates, die PCI-Geräte erneut aufzählen.", + "Migration der Boot-Disk auf andere Hardware." + ], + "outro": "Nach dem nächsten Start versucht die Bridge, einen nicht mehr vorhandenen Port einzubinden. Sie lässt sich nicht aktivieren und der Host verliert seine Netzwerkverbindung. Persistente Schnittstellennamen verhindern eine Wiederholung. Falls Sie bereits ausgesperrt sind, beschreibt diese Seite den Wiederherstellungsweg." + }, + "bigPicture": { + "heading": "Überblick", + "diagram1": { + "arrowLabel": "Stufe 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analysieren (schreibgeschützt)", + "bridgeDetail": "ip link show enp3s0\n→ nicht vorhanden", + "targetLabel": "Bericht + Vorschlag", + "targetDetail": "❌ enp3s0: NICHT GEFUNDEN\nErsetzen mit: eno1\n(noch keine Änderungen)" + } + }, + "diagram2": { + "arrowLabel": "mit Backup anwenden", + "nodes": { + "sourceLabel": "Geführte Reparatur", + "sourceDetail": "1. Backup\n2. Aktuelle Konfiguration anzeigen\n3. Änderungsvorschau\n4. Anwenden\n5. Überprüfen", + "targetLabel": "/etc/network/interfaces (neu)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Schritt 1: Analyse (schreibgeschützt)", + "intro": "Nach Auswahl von Brückenkonfiguration analysieren bricht das Werkzeug sofort ab, falls der Host nicht den klassischen Debian-/Proxmox-Stack nutzt. Andernfalls prüft es jede Bridge und meldet:", + "items": [ + "Brückenname, aktueller Status (UP / DOWN), zugewiesene IP.", + "Jeder deklarierte Port und ob er derzeit existiert (ip link show <port>).", + "Für jeden ungültigen Port: ein vorgeschlagener Ersatz (die erste verfügbare physische Schnittstelle) sowie der genaue Befehl sed, um ihn manuell anzuwenden.", + "Brücken ohne bridge-ports und verwaiste auto-Einträge (kein passender iface-Block)." + ], + "readonlyTitle": "Garantie einer schreibgeschützten Analyse", + "readonlyBody": "Bis zu diesem Punkt hat das Skript keinen einzigen Änderungsbefehl ausgeführt. Sie können die Analyse offen lassen, die vorgeschlagenen sed-Befehle kopieren und sie manuell anwenden, wenn Sie möchten - oder die nächste Aufforderung akzeptieren, um in die geführte Reparatur einzusteigen." + }, + "step2": { + "heading": "Schritt 2: geführte Reparatur (5 Schritte)", + "intro": "Wird nur gestartet, wenn Sie der Aufforderung am Ende der Analyse zustimmen. Jeder Schritt zeigt die geplante Aktion und bittet vor dem Fortfahren um Bestätigung.", + "steps": [ + { + "title": "1. Sicherheitssicherung", + "body": "Kopiert /etc/network/interfaces in /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Der genaue Sicherungspfad wird vor der Kopie und danach mit dem Rollback-Befehl angezeigt.", + "tone": "blue" + }, + { + "title": "2. Überprüfung der aktuellen Konfiguration", + "body": "Öffnet das Live /etc/network/interfaces in einem scrollbaren Dialog, so dass Sie genau sehen können, was geändert werden soll.", + "tone": "blue" + }, + { + "title": "3. Vorgeschlagene Änderungen anzeigen", + "body": "Listet genau die betroffenen Bridges und geplanten Portersetzungen auf. Falls inzwischen kein Reparaturbedarf mehr besteht, weil der Port zwischenzeitlich wieder aufgetaucht ist, endet der Ablauf mit „Keine Änderungen erforderlich.“", + "tone": "blue" + }, + { + "title": "4. Änderungen anwenden", + "body": "Für jede betroffene Brücke läuft sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" gegen /etc/network/interfaces. Wenn für eine Brücke kein gültiger Ersatz verfügbar war, wird die Substitution übersprungen und in Schritt 5 gemeldet.", + "tone": "amber" + }, + { + "title": "5. Überprüfung", + "body": "Liest die Datei erneut und bestätigt, dass der neue Port jeder Bridge existiert. Gibt den Befehl zum Zurücksetzen aus (cp <backup> /etc/network/interfaces). Bietet abschließend Netzwerk neu starten an — stimmen Sie nur zu, wenn ein Konsolenzugang als Ersatz bereitsteht.", + "tone": "emerald" + } + ], + "restartTitle": "Netzwerkneustart ≠ risikoloses Rückgängigmachen", + "restartBody": "Die Reparatur wird auf den Datenträger geschrieben, unabhängig davon, ob Sie den Dienst neu starten. systemctl restart networking wendet die Änderung jetzt an und kann Ihre SSH-Sitzung unterbrechen, wenn der neue Port falsch ist. Lehnen Sie ab, wird die Änderung dennoch beim nächsten Systemstart wirksam. Prüfen Sie daher die neue Konfiguration oder setzen Sie sie mit dem angezeigten Befehl zurück." + }, + "edits": { + "heading": "Was wird bearbeitet (genau)", + "body": "Nur die Zeile bridge-ports jeder betroffenen Bridge. Andere Direktiven (address, netmask, gateway, bridge-stp, …) bleiben unverändert. Das Skript erstellt oder entfernt in diesem Ablauf keine iface-Blöcke — dafür ist Konfigurationsanalyse & Bereinigung zuständig." + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "unsupportedTitle": "Die Analyse bricht mit „Nicht unterstützter Netzwerkstack“ ab", + "unsupportedBody": "Der Host läuft netplan, systemd-networkd oder NetworkManager. Das Tool unterstützt nur /etc/network/interfaces. Schalten Sie den Host zuerst auf den klassischen Stack oder bearbeiten Sie die Konfiguration mit dem nativen Tooling des Managers (z.B. netplan apply).", + "noSuggestTitle": "Kein Vorschlag wird für einen ungültigen Port gedruckt", + "noSuggestBody": "Der Host hat null freie physische Schnittstellen (alles befindet sich entweder bereits in einer anderen Brücke oder existiert nicht). Die Analyse greift auf bridge-ports none zurück, so dass die Brücke zumindest ohne Port auftauchen kann. Fügen Sie eine NIC hinzu oder migrieren Sie Ports zwischen Brücken manuell, bevor Sie erneut ausführen.", + "stillDownTitle": "Die Reparatur ist abgeschlossen, aber die Brücke ist noch DOWN", + "stillDownBody": "sed hat die Datei aktualisiert, aber systemctl restart networking wurde abgelehnt. Führen Sie den Befehl nach Prüfung der neuen Konfiguration manuell aus oder starten Sie das System neu. Wurde der Neustart angenommen und bleibt die Bridge DOWN, prüfen Sie mit ip link show, ob der neue Port existiert, und suchen Sie mit journalctl -u networking nach der Ursache: nicht eingestecktes Kabel, nicht ausgehandelte Verbindung oder ein Port, der schon zu einer anderen Bridge gehört.", + "lostSshTitle": "Ich habe den SSH-Zugang direkt nach dem Neustart des Netzwerks verloren", + "lostSshIntro": "Verwenden Sie Konsole / IPMI / iKVM, um den Host zu erreichen. Wiederherstellen des Backups:", + "lostSshOutro": "Führen Sie dann die Analyse erneut von der Konsole aus aus, um herauszufinden, warum der Vorschlag nicht funktioniert hat (normalerweise: Der Ersatzanschluss ist nicht wirklich angeschlossen)." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Persistente Schnittstellennamen", + "href": "/docs/network/persistent-names", + "tail": " — bindet Namen an MAC-Adressen, damit dieses Szenario nicht erneut auftritt." + }, + { + "label": "Konfigurationsanalyse und geführte Bereinigung", + "href": "/docs/network/config-analysis", + "tailRich": "— für verwaiste iface-Blöcke (die andere Hälfte des gleichen Problems)." + }, + { + "label": "Schnittstellen sichern und neu starten", + "href": "/docs/network/backup-restore", + "tail": "- manuelle Snapshots und der Wiederherstellungsbrowser." + } + ] + } +} diff --git a/web/messages/de/docs/network/config-analysis.json b/web/messages/de/docs/network/config-analysis.json new file mode 100644 index 00000000..138b6c24 --- /dev/null +++ b/web/messages/de/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Konfigurationsanalyse und geführte Bereinigung | ProxMenux-Dokumentation", + "description": "Erkennt iface-Blöcke in /etc/network/interfaces, die auf physische NICs verweisen, die nicht mehr auf dem Host vorhanden sind. Meldet sie und bietet eine 5-schrittgesteuerte Entfernung mit obligatorischem Backup und einer Blockvorschau.", + "ogTitle": "Konfigurationsanalyse und geführte Bereinigung | ProxMenux-Dokumentation", + "ogDescription": "Finden und entfernen Sie verwaiste Schnittstellenblöcke, die durch Hardwareänderungen zurückgelassen wurden, mit vollständiger Vorschau und Rollback." + }, + "header": { + "title": "Konfigurationsanalyse und geführte Bereinigung", + "description": "Untersucht jeden iface-Block in /etc/network/interfaces und prüft, ob die zugehörige physische Netzwerkkarte noch vorhanden ist. Meldet nach Hardwareänderungen verbliebene verwaiste Blöcke und bietet eine geführte Entfernung mit Backup, Vorschau und Bestätigung für jeden Block.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Identifiziert iface-Deklarationen, deren unterstützende NIC nicht mehr auf dem Host vorhanden ist (typisch nach dem Ersetzen oder Entfernen einer Netzwerkkarte). Anschließend wird eine geführte Bereinigung angeboten, bei der nur die verwaisten Blöcke entfernt werden, mit einer Sicherung, einer Vorschau pro Abschnitt und einer expliziten endgültigen Bestätigung." + }, + "differs": { + "heading": "Wie es sich von der Brückenanalyse unterscheidet", + "headerAspect": "Aspekt", + "headerBridge": "Brückenanalyse", + "headerConfig": "Konfigurationsanalyse", + "rows": [ + { + "aspect": "Untersucht", + "bridge": "Brücken (vmbrX) und ihre bridge-ports-Linie", + "config": "Jeder iface-Block (ausgenommen Loopback, Bridges, Bonds)" + }, + { + "aspect": "Erkennt", + "bridge": "Brücken, die auf einen nicht vorhandenen Port verweisen", + "config": "Standalone-Schnittstellenblöcke für NICs, die nicht existieren" + }, + { + "aspect": "Reparaturmaßnahmen", + "bridge": "Ersetzt den Portnamen in bridge-ports", + "config": "Entfernen Sie den gesamten iface-Block" + }, + { + "aspect": "Ausgenommen sind", + "bridge": "Nichts – jede Brücke wird analysiert", + "config": "Bridges (vmbrX) und Bonds (bondX) bleiben immer erhalten, da sie virtuell sind und beim Start vorübergehend fehlen können" + } + ], + "outro": "In der Praxis führen Sie oft beide Werkzeuge in dieser Reihenfolge aus: zuerst Konfigurationsanalyse, um verwaiste Blöcke zu entfernen, dann Brückenanalyse, um Bridges, die auf die entfernte Schnittstelle verwiesen, neu zuzuordnen." + }, + "step1": { + "heading": "Schritt 1: Analyse (schreibgeschützt)", + "intro": "Bricht sofort ab, wenn der Host nicht den klassischen Netzwerkstack nutzt. Andernfalls werden alle konfigurierten Schnittstellen außer Loopback mit ihrem Status aufgelistet:", + "virtTitle": "Virtuelle Schnittstellen sind geschützt", + "virtBody": "Bridges (vmbrX) und Bonds (bondX) sind virtuelle Konstrukte. Sie können zum Zeitpunkt der Prüfung durchaus fehlen, etwa eine Bridge ohne Ports. Daher schlägt die Analyse nur die Entfernung physischer Netzwerkkarten vor, niemals die dieser virtuellen Schnittstellen." + }, + "step2": { + "heading": "Schritt 2: geführte Bereinigung (5 Schritte)", + "intro": "Wird nur gestartet, wenn Sie der Aufforderung am Ende der Analyse zustimmen. Jeder Schritt muss bestätigt werden; bei Abbruch wird ohne Schreiben beendet.", + "steps": [ + { + "title": "1. Sicherheitssicherung", + "body": "Kopiert /etc/network/interfaces nach /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Der Pfad wird vorher und nachher angezeigt.", + "tone": "blue" + }, + { + "title": "2. Bestätigen Sie die Entfernungsliste", + "body": "Listet genau die zu entfernenden physischen Schnittstellenblöcke auf. Sind die Netzwerkkarten zwischen Analyse und Bereinigung wieder aufgetaucht, etwa nach erneutem Einsetzen einer Karte, endet der Ablauf mit „Keine Bereinigung erforderlich.“", + "tone": "blue" + }, + { + "title": "3. Vorschau der genauen Block(s)", + "body": "Öffnet eine scrollbare Ansicht jedes iface-Blocks, der gelöscht werden würde, exakt wie in der Datei. Hier können Sie etwa einen wichtigen up-Hook erkennen, den Sie behalten möchten.", + "tone": "blue" + }, + { + "title": "4. Entfernung anwenden", + "body": "Für jede verwaiste Schnittstelle läuft sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. Das löscht von der passenden iface-Zeile bis zur nächsten leeren Zeile.", + "tone": "amber" + }, + { + "title": "5. Überprüfung", + "body": "Liest die Datei erneut, listet auf, was entfernt wurde, und überprüft die verbleibenden Schnittstellen erneut. Gibt den Rollback-Befehl aus. Führt nicht einen automatischen Neustart des Netzwerks durch. Das Entfernen eines nicht verwendeten Blocks kann sicher beim nächsten Neustart angewendet werden und vermeidet eine Beeinträchtigung des Live-Status.", + "tone": "emerald" + } + ], + "noRestartTitle": "Startet das Netzwerk nicht automatisch neu", + "noRestartBody": "Im Gegensatz zur Brückenreparatur bietet der Bereinigungsfluss keinen Neustart des Dienstes. Das Entfernen eines unbenutzten Schnittstellenblocks hat keine unmittelbaren Auswirkungen auf die laufende Konfiguration, so dass ein Neustart unnötig ist und ein unnötiges Verbindungsrisiko darstellt. Die Änderung wird beim nächsten manuellen systemctl restart networking oder beim nächsten Neustart wirksam." + }, + "caveats": { + "heading": "Wichtige Vorbehalte", + "boundaryTitle": "Die Blockgrenze ist die erste leere Linie", + "boundaryBody": "Das sed-Muster löscht von iface <name> bis zur nächsten leeren Zeile. Fehlt in /etc/network/interfaces eine Leerzeile zwischen Blöcken, was nach manueller Bearbeitung möglich ist, kann auch der nächste Block gelöscht werden. Deshalb ist Schritt 3 obligatorisch: Prüfen Sie die Vorschau vor der Bestätigung.", + "tandemTitle": "Ein Waisen-Iface, das von einer Brücke verwendet wird, wird hier erkannt, nicht in der Brückenanalyse", + "tandemBody": "Wenn vmbr0 bridge-ports enp3s0 deklariert und enp3s0 einen eigenen Block iface enp3s0 inet manual hat, bleibt nach der Entfernung von enp3s0 eine ungültige Referenz in vmbr0. Führen Sie danach die Brückenanalyse aus, um den Bridge-Port neu zuzuordnen oder zu entfernen. Beide Abläufe sind für die gemeinsame Verwendung ausgelegt." + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "notFoundTitle": "Die Analyse zeigt mein neues NIC als \"NICHT GEFUNDEN\"", + "notFoundBody": "Der Kernel sieht das Gerät unter einem anderen Namen als in der Datei. Führen Sie ip link show aus, um den tatsächlichen Namen zu erhalten. Dies ist genau der Fall, wenn Persistente Schnittstellennamen das Problem verhindern würden.", + "tooMuchTitle": "Die Bereinigung hat zu viel entfernt (einen Block, den ich behalten wollte)", + "tooMuchBody": "Wiederherstellen aus dem in Schritt 5 gedruckten Backup:", + "tooMuchOutro": "Bearbeiten Sie die Datei von Hand, um eine klare leere Linie zwischen den Blöcken hinzuzufügen, bevor Sie die Bereinigung erneut ausführen, oder entfernen Sie den problematischen Block manuell.", + "bridgeBreakTitle": "Eine Brücke funktioniert nach dem Aufräumen nicht mehr", + "bridgeBreakBody": "Die Bridge war auf einen Port angewiesen, dessen iface-Block gerade entfernt wurde. Führen Sie die Brückenanalyse aus; der Vorschlag ordnet der Bridge eine vorhandene physische Schnittstelle zu." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Brückenanalyse und geführte Reparatur", + "href": "/docs/network/bridge-analysis", + "tail": " — der natürliche nächste Schritt nach einer Bereinigung." + }, + { + "label": "Persistente Schnittstellennamen", + "href": "/docs/network/persistent-names", + "tail": " — verhindert, dass sich nach jeder Hardwareänderung verwaiste Blöcke ansammeln." + }, + { + "label": "Schnittstellen sichern und neu starten", + "href": "/docs/network/backup-restore", + "tail": "- manuelle Snapshots und der Wiederherstellungsbrowser." + } + ] + } +} diff --git a/web/messages/de/docs/network/diagnostics.json b/web/messages/de/docs/network/diagnostics.json new file mode 100644 index 00000000..3aca50e3 --- /dev/null +++ b/web/messages/de/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnose | ProxMenux Dokumentation", + "description": "Drei einmalige, schreibgeschützte Diagnoseprüfungen: Routingtabelle anzeigen, Konnektivität testen und erweiterte Diagnose. Reine Prüfung — schreibt nie in /etc/network/interfaces und führt nie ändernde Befehle aus, mit einer ausdrücklichen Ausnahme: Wird NetworkManager erkannt, kann er nach Ihrer Zustimmung vollständig entfernt werden.", + "ogTitle": "Diagnose | ProxMenux Dokumentation", + "ogDescription": "Schreibgeschützte Netzwerkdiagnose für den Proxmox-Host: Routing, Erreichbarkeit und erweiterte Statistiken." + }, + "header": { + "title": "Diagnose", + "description": "Drei einmalige, schreibgeschützte Prüfungen des aktuellen Netzwerkzustands: Routingtabelle, Konnektivitätstest und erweiterte Statistiken. Keine schreibt in /etc/network/interfaces — jederzeit sicher nutzbar, auch über SSH. Für interaktive Echtzeitüberwachung siehe Überwachungswerkzeuge für Echtzeitdaten.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Berichtet über Routing, Erreichbarkeit und zusammengefasste Statistiken, ohne etwas zu ändern. Damit können Sie den ordnungsgemäßen Netzwerkbetrieb bestätigen oder doppelte IP-Adressen erkennen. Für die kontinuierliche Verkehrsbeobachtung siehe Überwachungswerkzeuge für Echtzeitdaten; zur Anzeige der unverarbeiteten Konfigurationsdatei siehe Schnittstellen sichern und neu starten." + }, + "routing": { + "heading": "Routing-Tabelle anzeigen", + "body": "Listet alle aktuell vom Kernel verwendeten Routen auf (ip route show) und hebt das Standard-Gateway hervor. Damit lässt sich prüfen, ob Internetverkehr die erwartete Schnittstelle verwendet und keine veralteten Routen Vorrang haben." + }, + "connectivity": { + "heading": "Konnektivität testen", + "intro": "Sendet zwei ICMP-Sonden nacheinander an drei Ziele und meldet jede einzelne. Führt dann eine nslookup google.com aus, um die DNS-Auflösung unabhängig von ICMP zu überprüfen.", + "headerTest": "Test", + "headerTarget": "Ziel", + "headerConfirms": "Was es bestätigt", + "rows": [ + { + "test": "Google DNS", + "target": "8.8.8.8", + "confirms": "Externe Erreichbarkeit über bekannte Anycast IP" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Unabhängige zweite Meinung, wenn Google ICMP blockiert" + }, + { + "test": "Gateway", + "target": "(Standardroute)", + "confirms": "L2/L3-Verbindung zum lokalen Router" + }, + { + "test": "DNS-Auflösung", + "target": "google.com", + "confirms": "Der DNS-Server ist erreichbar und antwortet" + } + ], + "readingTitle": "Lesen des Ergebnisses", + "readingBody": "Wenn der Gateway-Test besteht, aber die öffentlichen DNS-Ziele fehlschlagen, haben Sie lokale Konnektivität, aber kein Internet - überprüfen Sie den Upstream des Gateways. Wenn die DNS-Auflösung fehlschlägt, Pings zu 8.8.8.8 jedoch erfolgreich sind, ist das Problem der Resolver, nicht das Netzwerk: Überprüfen Sie /etc/resolv.conf." + }, + "advanced": { + "heading": "Erweiterte Diagnose", + "intro": "Fasst netzwerkweite Statistiken zusammen und prüft auf häufige Probleme. Meldet die Anzahl aktiver Verbindungen, lauschende Ports und die Gesamtzahl der Schnittstellen und weist ausdrücklich auf zwei problematische Konstellationen hin:", + "items": [ + "NetworkManager läuft auf einem Proxmox-Host. NetworkManager und Proxmox’ ifupdown stehen im Konflikt: Beide versuchen, Schnittstellen zu verwalten, sodass Bridges ständig ihren Zustand wechseln oder nicht aktiviert werden. Wird NetworkManager erkannt, bietet das Werkzeug an, ihn interaktiv zu stoppen, zu deaktivieren und vollständig zu entfernen (dies ist die einzige Diagnose, die das System ändern kann, und nur nach ausdrücklicher Zustimmung).", + "Doppelte IP-Adressen auf verschiedenen Schnittstellen. Zwei Schnittstellen mit derselben IPv4-Adresse verursachen sporadische Verbindungsprobleme, die innerhalb der VM äußerst schwer zu untersuchen sind. Erkannt über ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "Die NetworkManager-Eingabeaufforderung ist hier die einzige Änderungsaktion", + "nmBody": "Alle anderen Prüfungen in der erweiterten Diagnose sind schreibgeschützt. Die Entfernung von NetworkManager erfordert eine Ja/Nein-Bestätigung und führt systemctl stop / disable + apt-get purge -y network-manager aus. Wenn Sie ablehnen, ändert sich nichts." + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "gwTitle": "Konnektivität testen meldet einen Gatewayfehler, aber der Host hat Internetzugriff", + "gwBody": "Einige Router reagieren nicht auf ICMP von ihrer internen Schnittstelle aus, selbst wenn sie den Datenverkehr gerne weiterleiten. Testen Sie mit traceroute 8.8.8.8 von einer Konsole aus: Wenn der zweite Hop Ihr ISP ist und der öffentliche DNS-Test bestanden ist, ist das Gateway trotz des fehlgeschlagenen Pings in Ordnung.", + "dupTitle": "Die erweiterte Diagnose meldet weiterhin doppelte IP-Adressen, obwohl ich eine entfernt habe", + "dupBody": "Der Check betrachtet den Live-Kernel-Status, nicht die Config-Datei. Führen Sie ip -4 addr show aus, um zu bestätigen; wenn das Duplikat noch vorhanden ist, hält es immer noch eine Schnittstelle. Verwenden Sie ip addr del <IP>/<mask> dev <iface>, um es aus dem Live-Zustand zu entfernen, und bearbeiten Sie dann /etc/network/interfaces, wenn es auch persistent ist." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Überwachungswerkzeuge für Echtzeitdaten", + "href": "/docs/network/monitoring", + "tail": "— interaktive iftop / iptraf-ng / iperf3 Launcher." + }, + { + "label": "Brückenanalyse und geführte Reparatur", + "href": "/docs/network/bridge-analysis", + "tail": " — wenn einem vmbrX der physische Port fehlt." + }, + { + "label": "Konfigurationsanalyse und geführte Bereinigung", + "href": "/docs/network/config-analysis", + "tail": " — wenn eine alte Netzwerkkarte noch deklariert, aber nicht mehr vorhanden ist." + }, + { + "label": "Schnittstellen sichern und neu starten", + "href": "/docs/network/backup-restore", + "tail": " — um vor jeder Änderung einen Schnappschuss der Konfiguration zu erstellen." + } + ] + } +} diff --git a/web/messages/de/docs/network/index.json b/web/messages/de/docs/network/index.json new file mode 100644 index 00000000..f9900748 --- /dev/null +++ b/web/messages/de/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Proxmox-Netzwerkverwaltung — Bridges, Bonds, Diagnose, Reparatur | ProxMenux", + "description": "Schreibgeschützte Diagnose, Analysen mit anschließenden Vorschlägen und geführte Reparaturen für den Debian-/Proxmox-Netzwerkstack (/etc/network/interfaces). Prüfen Sie Bridges und Bonds, starten Sie Live-Überwachungswerkzeuge, vergeben Sie dauerhafte Schnittstellennamen, erstellen Sie Backups und starten Sie das Netzwerk sicher neu — mit obligatorischen Backups und schrittweiser Vorschau.", + "ogTitle": "Proxmox-Netzwerkverwaltung — Bridges, Bonds, Diagnose, Reparatur", + "ogDescription": "Diagnose, Analyse und geführte Reparaturen für den Proxmox-Netzwerkstack mit obligatorischen Backups und Schritt-für-Schritt-Vorschau.", + "twitterTitle": "Proxmox-Netzwerkverwaltung | ProxMenux", + "twitterDescription": "Brücken, Bonds, Diagnosen und geführte Reparaturen für den Proxmox-Netzwerkstack mit obligatorischen Backups." + }, + "header": { + "title": "Netzwerkmanagement", + "description": "Schreibgeschützte Diagnose, Analyseberichte mit anschließenden Vorschlägen und geführte Reparaturen für den klassischen Debian-/Proxmox-Netzwerkstack. Jeder destruktive Vorgang erstellt zuerst ein Backup und zeigt die genauen Änderungen vor der Anwendung an.", + "section": "Netzwerk" + }, + "intro": { + "title": "Wozu dieses Menü dient", + "body": "Prüfen, diagnostizieren und reparieren Sie bei Bedarf die Netzwerkkonfiguration des Proxmox-Hosts, ohne den Fernzugriff zu verlieren. Die Werkzeuge folgen einem Prinzip: zuerst lesen, dann Vorschläge machen und Änderungen nur mit ausdrücklicher Zustimmung und einer Sicherheitskopie anwenden. Es gibt keine Schaltfläche „Alles automatisch reparieren“." + }, + "openingMenu": { + "heading": "Öffnen des Menüs", + "intro": "Wählen Sie im Hauptmenü von ProxMenux Netzwerk. Folgendes wird angezeigt:", + "imageAlt": "Menü Netzwerkmanagement mit Diagnose, Überwachung, Analyse, Reparatur, persistenten Namen und Backup-Optionen" + }, + "safety": { + "heading": "Das Sicherheitsmodell", + "body": "Das Bearbeiten der Netzwerkkonfiguration auf einem entfernten Proxmox-Host ist eine der einfachsten Möglichkeiten, sich auszusperren. ProxMenux behandelt jede Aktion entsprechend. Die Werkzeuge fallen in drei Verhaltensstufen - wählen Sie die Karte, die Ihrer Absicht entspricht:" + }, + "tiers": { + "readOnly": { + "title": "Schreibgeschützt", + "body": "Reine Inspektion. Kann das System unter keinen Umständen ändern.", + "items": [ + "Routingtabelle, Konnektivitätstests, fortgeschrittene Statistiken", + "Verkehrsüberwachung in Echtzeit (iftop, iptraf-ng)", + "Bandbreitentest (iperf3)" + ] + }, + "analyze": { + "title": "Analysieren, dann vorschlagen", + "body": "Erkennt Probleme, druckt einen Bericht mit vorgeschlagenen Shell-Befehlen und stoppt. Sie entscheiden, ob Sie anschließend in die geführte Reparatur eintreten möchten.", + "items": [ + "Brückenkonfigurationsanalyse", + "Netzwerkkonfigurationsanalyse" + ] + }, + "apply": { + "title": "Mit Backup anwenden", + "body": "Ändert das System. Erstellt immer zuerst ein Backup der betroffenen Datei mit Zeitstempel und zeigt vor dem Schreiben eine Vorschau an.", + "items": [ + "Persistente Schnittstellennamen (.link-Dateien)", + "Manuelles Backup, Wiederherstellung und Neustart des Dienstes" + ] + } + }, + "classicTitle": "Nur klassischer Stack", + "classicBody": "Jede Analyse- und Reparaturfunktion prüft den aktiven Netzwerkmanager, bevor sie etwas ändert. Wenn der Host netplan, systemd-networkd oder NetworkManager verwendet, bricht das Werkzeug sofort mit einer klaren Meldung ab — das Menü unterstützt nur den klassischen Debian-/Proxmox-Stack unter /etc/network/interfaces. Das ist beabsichtigt: Eine netplan-Datei nach Regeln für ifupdown zu bearbeiten, würde die Konfiguration unbemerkt beschädigen.", + "backups": { + "heading": "Wo Backups gespeichert werden", + "intro": "Jede geführte Reparatur, Wiederherstellung oder manuelle Sicherung schreibt eine zeitgestempelte Kopie von /etc/network/interfaces auf /var/backups/proxmenux/:", + "rollbackIntro": "So rollen Sie manuell von einer Konsole zurück:" + }, + "readOnlySection": { + "heading": "Schreibgeschützte Prüfung", + "body": "Der Ausgangspunkt, wenn etwas nicht stimmt. Reine Prüfung — schreibt nie in /etc/network/interfaces und führt nie einen ändernden Befehl aus (mit einer ausdrücklichen Ausnahme: Wird NetworkManager erkannt, kann er nach Ihrer Zustimmung vollständig entfernt werden). Jederzeit sicher über SSH nutzbar.", + "options": [ + { + "title": "Diagnose", + "description": "Drei einmalige, schreibgeschützte Prüfungen: Routingtabelle anzeigen, Konnektivität testen und erweiterte Diagnose. Reine Prüfung — schreibt nie in das System." + }, + { + "title": "Überwachungswerkzeuge für Echtzeitdaten", + "description": "Drei interaktive Launcher: iftop (Echtzeitbandbreite pro Hostpaar), iptraf-ng (Multi-Mode-Traffic-Monitor) und iperf3 (Bandbreitentest, Server / Client-Modus)." + } + ] + }, + "analyzeSection": { + "heading": "Analysieren, dann vorschlagen", + "body": "Wenn eine Prüfung oder ein tatsächlicher Ausfall auf ein Konfigurationsproblem hinweist, kommen diese Werkzeuge zum Einsatz. Jedes untersucht den relevanten Teil von /etc/network/interfaces, gibt einen detaillierten Bericht mit dem genauen Shell-Befehl zur Behebung jedes Befunds aus und stoppt. Wenn Sie anschließend die optionale geführte Reparatur annehmen, wird vor jeder Änderung eine Sicherung erstellt und die Änderung vor dem Schreiben angezeigt.", + "options": [ + { + "title": "Brückenanalyse und geführte Reparatur", + "description": "Erkennt vmbrX-Bridges mit fehlenden oder ungültigen Ports, etwa nach einer neuen PCI-Gerätenummerierung. Zeigt zuerst einen Bericht an und repariert nur, wenn Sie dem geführten Ablauf mit 5 Schritten zustimmen." + }, + { + "title": "Konfigurationsanalyse und geführte Bereinigung", + "description": "Findet in /etc/network/interfaces deklarierte physische Schnittstellen, die nicht mehr existieren (Waisenkonfigurationen, die durch Hardwareänderungen zurückgelassen wurden). Meldet sie und bietet eine geführte Entfernung an." + } + ] + }, + "applySection": { + "heading": "Mit Backup anwenden", + "body": "Diese Werkzeuge schreiben bestimmungsgemäß auf den Datenträger. Jedes erstellt vorher ein Backup der betroffenen Datei mit Zeitstempel. Destruktive Optionen wie Wiederherstellen und Neustarten erfordern eine ausdrückliche Ja/Nein-Bestätigung. Persistente Schnittstellennamen werden erst beim nächsten Neustart wirksam, sodass die Einrichtung auch auf einem entfernten Host sicher geplant werden kann.", + "options": [ + { + "title": "Persistente Schnittstellennamen", + "description": "Bindet Schnittstellennamen (eno1, enp3s0, …) über systemd-.link-Dateien an MAC-Adressen. Die Namen bleiben bei PCI-Steckplatzwechseln, Kernel-Upgrades und beim Hinzufügen oder Entfernen anderer Netzwerkkarten erhalten." + }, + { + "title": "Schnittstellen sichern und neu starten", + "description": "Manueller Snapshot von /etc/network/interfaces, Durchsuchen und Wiederherstellen früherer Backups, Anzeigen der Live-Konfiguration und Neustart des Netzwerkdienstes bei Bedarf." + } + ] + }, + "consoleTitle": "Konsolenzugriff bereithalten", + "consoleSubTitle": "Vor jeder Reparatur auf einem entfernten Host", + "consoleBody": "Wenn Sie über SSH verbunden sind und nur einen Zugangsweg zum Host haben, halten Sie vor Netzwerkänderungen einen Ersatzweg bereit: eine physische Konsole, IPMI oder iKVM oder einen anderen Rechner im selben LAN. Die geführten Reparaturen sind sicher und bieten immer einen Befehl zum Zurücksetzen. Eine falsch konfigurierte Bridge oder eine unterbrochene Verbindung kann Sie dennoch aussperren, bis Sie die Konsole erreichen." +} diff --git a/web/messages/de/docs/network/monitoring.json b/web/messages/de/docs/network/monitoring.json new file mode 100644 index 00000000..c0be503f --- /dev/null +++ b/web/messages/de/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Überwachungswerkzeuge für Echtzeitdaten | ProxMenux-Dokumentation", + "description": "Drei interaktive Netzwerküberwachungsstarter: iftop (Echtzeitbandbreite pro Hostpaar), iptraf-ng (Multi-Mode-Traffic-Monitor) und iperf3 (Bandbreitentest, Server / Client-Modus). Jeder wird bei der ersten Verwendung installiert.", + "ogTitle": "Überwachungswerkzeuge für Echtzeitdaten | ProxMenux-Dokumentation", + "ogDescription": "Interaktive Netzwerküberwachung und Bandbreitentests für den Proxmox-Host: iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Überwachungswerkzeuge für Echtzeitdaten", + "description": "Drei interaktive Starter zur Echtzeit-Netzwerkbeobachtung und Bandbreitenmessung: iftop, iptraf-ng und iperf3. Jedes Werkzeug wird beim ersten Gebrauch automatisch über apt installiert, läuft im Vordergrund und wird mit der angegebenen Taste beendet. Sie ändern die Netzwerkkonfiguration des Hosts nicht; iperf3 erzeugt Testverkehr.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Drei interaktive Monitoring-Tools, jedes hinter seinem eigenen Menüeintrag. Beim ersten Start eines Pakets wird das Paket lautlos über apt-get installiert. Nachfolgende Starts beginnen sofort. Keines dieser Tools ändert die Host-Netzwerkkonfiguration." + }, + "when": { + "heading": "Welches Werkzeug Sie wann verwenden", + "headerQuestion": "Fragestellung", + "headerUse": "Verwenden", + "rows": [ + { + "question": "Wer lastet die Verbindung gerade jetzt aus?", + "use": "iftop" + }, + { + "question": "Welche Protokollaufschlüsselung / Paketgrößen / TCP-Flows fließen?", + "use": "iptraf-ng" + }, + { + "question": "Wie viel Bandbreite ist tatsächlich zwischen zwei Hosts verfügbar?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "Netzwerknutzung in Echtzeit (iftop)", + "body": "Bandbreite pro Hostpaar (Quelle ↔ Ziel) in Echtzeit — wie top für Netzwerkverkehr. Zeigt die größten Datenströme oben an, mit gleitenden Durchschnitten über 2/10/40 Sekunden. Das beste Werkzeug für die Frage „Warum ist mein Uplink ausgelastet?“.", + "exit": "Beenden: Drücken Sie q. ProxMenux zeigt vor dem Start einen Erinnerungsdialog.", + "keysTitle": "Nützliche Tasten in iftop", + "keysBody": "n DNS-Auflösung umschalten, p Portnummern anzeigen, P Anzeige pausieren, t Zeilenrichtung umschalten (gesendet / empfangen / beides), 1/2/3 nach dem Durchschnitt über 2 s / 10 s / 40 s sortieren. Vollständige Liste unter man iftop." + }, + "iptraf": { + "heading": "Netzwerküberwachungstool (iptraf-ng)", + "intro": "Ein menügesteuerter Verkehrsmonitor mit mehreren Modi. iftop beantwortet „wer“, iptraf-ng beantwortet „was“: Byte-/Paketzähler pro Protokoll, Verfolgung des TCP-Verbindungszustands, Paketgrößenhistogramme und LAN-Aktivität pro Station.", + "menuIntro": "Beim Start erhalten Sie ein Menü mit fünf Optionen:", + "headerMode": "Modus", + "headerUseFor": "Verwenden Sie es für", + "rows": [ + { + "mode": "IP-Verkehrsmonitor", + "useFor": "Live TCP / UDP/ICMP / Other-IP-Flow-Liste mit Byte-Zählern und Verbindungszustand" + }, + { + "mode": "Allgemeine Schnittstellenstatistiken", + "useFor": "Aggregierte IPv4/IPv6/TCP/UDP/ICMP/Nicht-IP-Paketzahl pro NIC" + }, + { + "mode": "Detaillierte Schnittstellenstatistiken", + "useFor": "Gleiche wie oben, aber für eine Schnittstelle, mit Paketgröße und -rate Details" + }, + { + "mode": "Statistische Aufgliederungen", + "useFor": "Histogramm der Paketgrößenverteilung nach TCP / UDP Port" + }, + { + "mode": "LAN-Stationenmonitor", + "useFor": "Per-MAC-Verkehrsstatistiken für die lokale Broadcast-Domain" + } + ], + "exit": "Beenden: Drücken Sie in einer beliebigen Ansicht x oder im Hauptmenü Q. ProxMenux zeigt vor dem Start einen Erinnerungsdialog.", + "logTitle": "Erfasste Daten in einer Datei protokollieren", + "logBody": "Jeder Modus bietet an, erfasste Statistiken unter /var/log/iptraf-ng/ zu protokollieren. Nützlich zur Aufzeichnung einer Verkehrsspitze: Lassen Sie das Werkzeug laufen, protokollieren Sie in eine Datei und prüfen Sie diese später, statt die Live-Anzeige mitlesen zu müssen." + }, + "iperf3": { + "heading": "Bandbreitentest (iperf3)", + "intro1": "Misst den tatsächlichen TCP-Durchsatz zwischen zwei Hosts. Anders als iftop / iptraf-ng, die vorhandenen Verkehr beobachten, erzeugt iperf3 künstlichen Verkehr für einen Belastungstest der Verbindung. Unverzichtbar für Fragen wie „Erreicht mein 10-GbE-Link wirklich 10 GbE?“ oder „Liegt der Engpass an der Netzwerkkarte, am Switch oder am Speicher?“.", + "intro2": "iperf3 ist ein Werkzeug für zwei Hosts: Eine Seite läuft als Server und lauscht auf TCP-Port 5201, die andere als Client, der sich verbindet, Daten sendet und die Rate ausgibt. Der ProxMenux-Starter fragt nach dem gewünschten Modus:", + "headerMode": "Modus", + "headerBehaviour": "Verhalten", + "headerCli": "Entsprechender CLI-Befehl", + "rows": [ + { + "mode": "Server", + "behaviour": "Lauscht auf TCP-Port 5201 und gibt für jeden eingehenden Test Ergebnisse aus. Beenden mit Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Client", + "behaviour": "Fragt nach der Server-IP / Hostname, verbindet, führt einen Standard-10-Sekunden-Test aus und druckt den Bericht aus.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Typischer Workflow zum Testen einer 10 GbE-Verbindung zwischen zwei Proxmox-Hosts:", + "workflow": [ + "Öffnen Sie auf Host A das Netzwerkmenü → Bandbreitentest (iperf3) → wählen Sie Server.", + "Öffnen Sie auf Host B den gleichen Menüeintrag → Wählen Sie Client → Geben Sie die IP von Host A ein.", + "Warten Sie 10 Sekunden. Vergleichen Sie die gemeldete Rate mit dem theoretischen Maximum des Links." + ], + "sample": "Beispiel für die Client-Ausgabe:", + "flagsTitle": "Nützliche manuelle Flags (aus einer Shell ausgeführt)", + "flagsBody": "-t 60 längerer Test (60 s statt 10 s), -P 4 4 parallele Datenströme (lastet schneller aus), -R umgekehrte Richtung (Server → Client), -u -b 100M UDP-Test mit 100 Mbit/s (für Jitter- und Paketverlustmessungen), -p 5202 anderer Port (mehrere parallele Tests). Vollständige Liste unter man iperf3.", + "firewallTitle": "Öffnen Sie den Firewall-Port auf dem Server", + "firewallBody": "Der Server hört standardmäßig auf TCP 5201. Wenn Sie den Server in einem Proxmox-Host mit einer strengen Firewall (Datacenter oder Host-Level) betreiben, erlauben Sie für die Dauer des Tests eingehende TCP 5201 von der IP des Clients und schließen Sie ihn erneut. Gleiches gilt für nftables / iptables auf bloßen Hosts." + }, + "install": { + "heading": "Erster Start installiert das Paket", + "body": "Alle drei Starter prüfen, ob das Programm vorhanden ist, und führen bei Bedarf apt-get update -qq && apt-get install -y <pkg> aus. Die Installation erfolgt ohne Ausgabe: Das Menü kann beim ersten Mal 10–30 Sekunden lang eingefroren wirken. Spätere Starts erfolgen sofort." + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "hangTitle": "Die Installation hängt beim ersten Start endlos", + "hangBody": "Der Host hat kein Internet oder die Apt-Repos sind nicht erreichbar. Brechen Sie mit Ctrl+C ab, führen Sie apt-get update manuell aus, um den tatsächlichen Fehler anzuzeigen (DNS, Repo-Signatur, Proxy …), und kehren Sie dann zum Menü zurück.", + "refusedTitle": "iperf3-Client: „unable to connect to server: Connection refused“", + "refusedBody": "Entweder läuft der Server nicht oder seine Firewall blockiert TCP 5201. Prüfen Sie auf dem Server mit ss -tlnp | grep 5201, ob iperf3 lauscht. Lauscht es, aber der Client kann sich weiterhin nicht verbinden, prüfen Sie die Firewalls auf dem Weg zwischen beiden Hosts.", + "slowTitle": "iperf3 meldet deutlich weniger als die erwartete Verbindungsgeschwindigkeit", + "slowBody": "Häufige Ursachen nach Wahrscheinlichkeit: (1) Die CPU eines Hosts begrenzt den Durchsatz — versuchen Sie iperf3 -c <target> -P 4 zur Nutzung mehrerer Kerne; (2) der Weg enthält eine langsamere Verbindung, etwa einen Gigabit-Switch zwischen zwei 10-GbE-Karten; (3) unterschiedliche MTU — prüfen Sie ip link show an beiden Enden; (4) deaktiviertes NIC-Offloading — siehe Community-Skript Disable NIC Offloading bei einer Intel-e1000e-Karte.", + "noTrafficTitle": "iftop / iptraf-ng zeigt keinen Traffic für einen beschäftigten Host an", + "noTrafficBody": "Die Standarderfassung erfolgt auf der ersten erkannten Schnittstelle. Geben Sie die richtige explizit aus der Shell an: iftop -i vmbr0 oder iptraf-ng -i vmbr0. Der Menüstarter verwendet den Standard; für nicht standardmäßige Schnittstellen, laufen Sie von einer Konsole aus." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Diagnose", + "href": "/docs/network/diagnostics", + "tail": " — die einmaligen, schreibgeschützten Prüfungen (Routing, Konnektivität, erweiterte Statistiken)." + }, + { + "label": "Brückenanalyse und geführte Reparatur", + "href": "/docs/network/bridge-analysis", + "tail": "Wenn die Überwachung zeigt, dass eine Schnittstelle oder Brücke nicht das tut, was sie sollte." + } + ] + } +} diff --git a/web/messages/de/docs/network/persistent-names.json b/web/messages/de/docs/network/persistent-names.json new file mode 100644 index 00000000..4c79b075 --- /dev/null +++ b/web/messages/de/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Persistente Schnittstellennamen | ProxMenux-Dokumentation", + "description": "Binden Sie Proxmox-Schnittstellennamen (eno1, enp3s0, …) über systemd-.link-Dateien an MAC-Adressen, damit sie bei neuer PCI-Nummerierung, Kernel-Upgrades und beim Hinzufügen oder Entfernen anderer Netzwerkkarten erhalten bleiben.", + "ogTitle": "Persistente Schnittstellennamen | ProxMenux-Dokumentation", + "ogDescription": "Verhindern Sie, dass sich Proxmox-Schnittstellennamen bei Hardwareänderungen ändern. Verwendet systemd-.link-Dateien mit Zuordnung über die MAC-Adresse." + }, + "header": { + "title": "Persistente Schnittstellennamen", + "description": "Erstellt pro physischer Netzwerkkarte eine systemd-.link-Datei, die den vom Kernel vergebenen Namen an die MAC-Adresse der Karte bindet. Nach der Anwendung beim nächsten Neustart bleiben die Schnittstellennamen beim Hinzufügen, Entfernen oder Umstecken von PCIe-Karten stabil.", + "section": "Netzwerk" + }, + "intro": { + "title": "Was das macht", + "body": "Schreibt für jede physische Netzwerkkarte des Hosts eine kleine Datei unter /etc/systemd/network/10-<iface>.link mit der Regel: „Das Gerät mit dieser MAC-Adresse muss immer diesen Namen tragen.“ systemd-udevd wendet die Regel bei jedem Start an, bevor ifupdown die Datei /etc/network/interfaces liest." + }, + "problem": { + "heading": "Das Problem, das dies löst", + "intro": "Linux leitet Standard-Schnittstellennamen aus der PCI-Topologie ab — eno1 = onboard, enp3s0 = die Karte in PCI-Bus 3, Steckplatz 0 usw. Das Namensschema ist deterministisch, solange die Hardwareanordnung gleich bleibt. Ändert sich die Anordnung, verschieben sich die Namen:", + "items": [ + "Fügen Sie einen GPU vor einem vorhandenen NIC hinzu → die NIC-Busnummer kann sich ändern → Namensänderungen.", + "Bewegen Sie eine Karte in einen anderen PCIe-Slot → Namensänderungen.", + "BIOS / UEFI-Update, das Geräte erneut auflistet → Namen können sich ändern.", + "Ersetzen Sie eine fehlerhafte Karte durch das gleiche Modell in einem anderen Slot → Namensänderungen." + ], + "outro": "Nach einer solchen Änderung verweist /etc/network/interfaces weiterhin auf den alten Namen. Die Bridge lässt sich nicht aktivieren und der Host verliert die Netzwerkverbindung. Persistente Namen verhindern, dass dieses Szenario erneut auftritt." + }, + "howWorks": { + "heading": "Wie es funktioniert", + "arrowLabel": "je NIC", + "nodes": { + "detectLabel": "Physische NIC erkennen", + "detectDetail": "ls /sys/class/net/\nvmbr / bond / docker / veth /\nwireguard … ausschließen", + "readLabel": "Jede MAC-Adresse lesen", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": ".link-Datei schreiben", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Jede generierte Datei ist absichtlich minimal:", + "minimalOutro": "Beim Start erkennt systemd-udevd das Gerät anhand der MAC-Adresse und weist den gewünschten Namen zu, bevor andere Komponenten wie ifupdown oder die Standardbenennung des Kernels darauf zugreifen. Das Dateipräfix 10- sorgt dafür, dass diese Regeln früh und vor der Standarddatei 99-default.link geladen werden." + }, + "scope": { + "heading": "Was geschrieben wird, was übersprungen wird", + "headerType": "Typ", + "headerBehaviour": "Verhalten", + "headerWhy": "Warum", + "rows": [ + { + "type": "Onboard-/PCIe-Netzwerkkarte", + "behaviour": ".link Datei geschrieben", + "why": "Unterstützt durch ein echtes PCI-Gerät - der Fall, für den das Tool geeignet ist" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": ".link Datei geschrieben", + "why": "Hat einen echten MAC und profitiert von Namensstabilität" + }, + { + "type": "Brücken (vmbrX)", + "behaviour": "Übersprungen", + "why": "Virtuell; Name kommt von /etc/network/interfaces" + }, + { + "type": "Bonds (bondX)", + "behaviour": "Übersprungen", + "why": "Virtuell; Bondname wird von ifupdown festgelegt" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Übersprungen", + "why": "Erstellt auf Anfrage von Docker / LXC - keine persistente Hardware" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Übersprungen", + "why": "Erstellt auf Anfrage von Proxmox per VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Übersprungen", + "why": "Software-Schnittstellen, die von eigenen Daemons verwaltet werden" + } + ] + }, + "safety": { + "heading": "Sicherheitsnetz: Frühere .link-Dateien werden gesichert", + "intro": "Wenn /etc/systemd/network/ bereits .link-Dateien enthält (aus einem früheren Lauf oder anderen Tools), werden diese in ein Backup-Verzeichnis mit Zeitstempel kopiert, bevor die neuen generiert werden:", + "outro": "Zum Zurückrollen: Kopieren Sie die Dateien aus dem Backup-Verzeichnis zurück und starten Sie neu.", + "rebootTitle": "Effekt beim nächsten Neustart, nicht sofort", + "rebootBody": "Änderungen an .link-Dateien gelten nur beim Booten, wenn udev Geräte erneut auflistet. Das Tool meldet \"Änderungen gelten nach dem Neustart\" aus diesem Grund. Das Umbenennen einer Schnittstelle ist riskant und wird nicht versucht: Ein aktiver vmbr0 mit Mitgliedern müsste atomar neu konfiguriert werden, weshalb die Operation auf den nächsten sauberen Boot verschoben wird." + }, + "afterReboot": { + "heading": "Nach dem Reboot", + "intro": "Sobald die Namen angeheftet sind, ist der Workflow für zukünftige Hardwareänderungen einfach:", + "items": [ + "Ausschalten, Hardware ändern (Karte hinzufügen, in einen anderen Steckplatz umstecken, …), starten.", + "Jedes NIC behält seinen vorherigen Namen, weil sein MAC mit einer .link-Datei übereinstimmt.", + "Wenn ein NIC ersetzt wird (anderer MAC), erhält er einen Standard-Kernelnamen (enp<bus>s<slot>); Führen Sie dieses Menü erneut aus, um einen .link-Eintrag für den MAC der neuen Karte hinzuzufügen." + ] + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "emptyTitle": "\"Keine physischen Schnittstellen gefunden\" nach dem Ausführen des Tools", + "emptyBody": "Der Kernel erkennt auf dem Host keine auf PCI oder phy80211 basierenden Schnittstellen. Prüfen Sie mit ls -l /sys/class/net/ — jeder Eintrag sollte entweder einen device-Symlink (PCI) oder einen phy80211-Eintrag (WLAN) haben. Fehlt bei einer echten Netzwerkkarte beides, ist der Treiber nicht geladen.", + "noChangeTitle": "Nach dem Neustart änderten sich die Namen nicht wie erwartet", + "noChangeBody": "Überprüfen Sie, ob die Datei vorhanden und gut geformt ist: cat /etc/systemd/network/10-<iface>.link. Dann überprüfen Sie udev logs: journalctl -u systemd-udevd -b | grep -i link. Eine häufige Ursache ist ein veralteter net.ifnames=0-Kernelparameter, der die vorhersagbare Benennung vollständig deaktiviert - entfernen Sie ihn aus /etc/default/grub, führen Sie update-grub aus, Neustart.", + "undoTitle": "Ich möchte persistente Benennung rückgängig machen", + "undoBody": "Löschen Sie entweder die .link-Dateien (rm /etc/systemd/network/10-*.link) oder stellen Sie sie aus dem beim vorherigen Durchlauf erstellten Backup-Verzeichnis wieder her. Starten Sie neu, um die Änderung anzuwenden." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "label": "Brückenanalyse und geführte Reparatur", + "href": "/docs/network/bridge-analysis", + "tailRich": " — der Wiederherstellungsweg, wenn sich die Namen bereits geändert haben." + }, + { + "label": "Konfigurationsanalyse und geführte Bereinigung", + "href": "/docs/network/config-analysis", + "tail": "- um verwaiste Blöcke zu entfernen, die durch Namensverschiebungen zurückgelassen wurden." + }, + { + "label": "Diagnose", + "href": "/docs/network/diagnostics", + "tailRich": " — bestätigen Sie die neuen Namen nach dem Neustart mit Routingtabelle anzeigen." + } + ] + } +} diff --git a/web/messages/en/docs/network/monitoring.json b/web/messages/en/docs/network/monitoring.json index c6e85f08..ef749366 100644 --- a/web/messages/en/docs/network/monitoring.json +++ b/web/messages/en/docs/network/monitoring.json @@ -7,7 +7,7 @@ }, "header": { "title": "Live monitoring tools", - "description": "Three interactive launchers for real-time network observation and bandwidth measurement: iftop, iptraf-ng and iperf3. Each tool is auto-installed from apt on first use, runs in the foreground, and is exited with the documented keystroke. Read-only against the host configuration — they only observe traffic.", + "description": "Three interactive launchers for real-time network observation and bandwidth measurement: iftop, iptraf-ng and iperf3. Each tool is auto-installed from apt on first use, runs in the foreground, and is exited with the documented keystroke. They do not change the host network configuration; iperf3 generates test traffic.", "section": "Network" }, "intro": { diff --git a/web/messages/es/docs/network/backup-restore.json b/web/messages/es/docs/network/backup-restore.json index 4e6945a4..75ead38d 100644 --- a/web/messages/es/docs/network/backup-restore.json +++ b/web/messages/es/docs/network/backup-restore.json @@ -1,8 +1,8 @@ { "meta": { - "title": "Backup y reinicio de interfaces | ProxMenux Documentation", + "title": "Backup y reinicio de interfaces | Documentación de ProxMenux", "description": "Snapshot manual, navegación y restauración de backups de /etc/network/interfaces almacenados en /var/backups/proxmenux/. Incluye un reinicio del servicio de red con confirmación y una vista rápida de la configuración en vivo.", - "ogTitle": "Backup y reinicio de interfaces | ProxMenux Documentation", + "ogTitle": "Backup y reinicio de interfaces | Documentación de ProxMenux", "ogDescription": "Snapshot y restauración de la configuración de red de Proxmox; reinicia el servicio de red con confirmación." }, "header": { @@ -27,7 +27,7 @@ "heading": "Crear backup de red", "body": "Copia el /etc/network/interfaces actual al directorio de backups con un timestamp nuevo. Ya está — sin análisis, sin prompts, solo un snapshot.", "whenTitle": "Cuándo hacer un backup manual", - "whenBody": "Antes de editar /etc/network/interfaces a mano, antes de instalar un paquete que pueda tocar la stack de red (p. ej. NetworkManager, Open vSwitch) o antes de cualquier cambio de hardware. Las reparaciones guiadas ya hacen snapshot automáticamente — esta opción es para los momentos manuales." + "whenBody": "Antes de editar /etc/network/interfaces a mano, antes de instalar un paquete que pueda modificar la pila de red (p. ej. NetworkManager, Open vSwitch) o antes de cualquier cambio de hardware. Las reparaciones guiadas ya crean una copia automáticamente; esta opción es para las operaciones manuales." }, "restore": { "heading": "Restaurar backup de red", @@ -96,7 +96,7 @@ "tail": " — la razón más habitual por la que un backup acaba aquí." }, { - "label": "Análisis de config y limpieza guiada", + "label": "Análisis de configuración y limpieza guiada", "href": "/docs/network/config-analysis", "tailRich": " — también escribe en /var/backups/proxmenux/ automáticamente." }, diff --git a/web/messages/es/docs/network/bridge-analysis.json b/web/messages/es/docs/network/bridge-analysis.json index 6fba5f64..af1f211f 100644 --- a/web/messages/es/docs/network/bridge-analysis.json +++ b/web/messages/es/docs/network/bridge-analysis.json @@ -1,8 +1,8 @@ { "meta": { - "title": "Análisis de bridges y reparación guiada | ProxMenux Documentation", + "title": "Análisis de bridges y reparación guiada | Documentación de ProxMenux", "description": "Detecta bridges vmbrX con puertos físicos ausentes o inválidos (típico tras una re-enumeración PCI), los reporta con comandos de shell propuestos y ofrece una reparación guiada de 5 pasos con backup obligatorio.", - "ogTitle": "Análisis de bridges y reparación guiada | ProxMenux Documentation", + "ogTitle": "Análisis de bridges y reparación guiada | Documentación de ProxMenux", "ogDescription": "Auditar y reparar los puertos de bridge de Proxmox tras cambios de hardware, con flujo guiado y rollback seguro." }, "header": { @@ -35,7 +35,7 @@ "bridgeLabel": "Analizar (solo lectura)", "bridgeDetail": "ip link show enp3s0\n→ no existe", "targetLabel": "Informe + sugerencia", - "targetDetail": "❌ enp3s0: NOT FOUND\nReemplazar por: eno1\n(aún sin cambios)" + "targetDetail": "❌ enp3s0: NO ENCONTRADA\nReemplazar por: eno1\n(aún sin cambios)" } }, "diagram2": { @@ -50,7 +50,7 @@ }, "step1": { "heading": "Paso 1: análisis (solo lectura)", - "intro": "Seleccionar Analizar configuración de bridges aborta inmediatamente si el host no usa la stack clásica de Debian/Proxmox. Si no, recorre cada bridge y reporta:", + "intro": "Al seleccionar Analizar configuración de bridges, la herramienta se detiene inmediatamente si el host no usa la pila clásica de Debian/Proxmox. En caso contrario, recorre todos los bridges e informa de:", "items": [ "Nombre del bridge, estado actual (UP / DOWN), IP asignada.", "Cada puerto declarado y si existe actualmente (ip link show <port>).", @@ -76,7 +76,7 @@ }, { "title": "3. Previsualizar los cambios propuestos", - "body": "Lista exactamente qué bridges se van a tocar y qué sustituciones de puerto se van a hacer. Si el análisis decide que en realidad no hay que arreglar nada (race condition: el puerto volvió), el flujo sale limpio con \"No changes needed.\"", + "body": "Lista exactamente qué bridges se modificarán y qué sustituciones de puertos se harán. Si el análisis determina que no hay nada que reparar porque el puerto ha reaparecido entre las comprobaciones, el proceso termina con «No es necesario realizar cambios».", "tone": "blue" }, { @@ -90,7 +90,7 @@ "tone": "emerald" } ], - "restartTitle": "Reiniciar networking ≠ un undo gratis", + "restartTitle": "Reiniciar la red ≠ deshacer los cambios sin riesgo", "restartBody": "La reparación se escribe en disco independientemente de si reinicias el servicio. systemctl restart networking aplica el cambio ahora, lo que puede cortar tu sesión SSH si el nuevo puerto es incorrecto. Si rechazas el reinicio, el cambio sigue surtiendo efecto en el siguiente reinicio — confirma antes la nueva config o haz rollback con el comando impreso." }, "edits": { @@ -99,8 +99,8 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "unsupportedTitle": "El análisis aborta con \"Unsupported Network Stack\"", - "unsupportedBody": "El host usa netplan, systemd-networkd o NetworkManager. La herramienta solo soporta /etc/network/interfaces. Cambia el host a la stack clásica primero, o edita la configuración con las herramientas nativas del gestor (p. ej. netplan apply).", + "unsupportedTitle": "El análisis se detiene con «Pila de red no compatible»", + "unsupportedBody": "El host usa netplan, systemd-networkd o NetworkManager. La herramienta solo admite /etc/network/interfaces. Cambia primero el host a la pila clásica o edita la configuración con las herramientas nativas del gestor (p. ej. netplan apply).", "noSuggestTitle": "No se imprime sugerencia para un puerto inválido", "noSuggestBody": "El host no tiene ninguna interfaz física libre (todas están ya en otro bridge o no existen). El análisis cae a sugerir bridge-ports none para que el bridge al menos pueda levantar sin puerto. Añade una NIC o migra puertos entre bridges manualmente antes de volver a ejecutar.", "stillDownTitle": "La reparación termina pero el bridge sigue DOWN", @@ -118,7 +118,7 @@ "tail": " — fija los nombres a direcciones MAC para que esto deje de pasar." }, { - "label": "Análisis de config y limpieza guiada", + "label": "Análisis de configuración y limpieza guiada", "href": "/docs/network/config-analysis", "tailRich": " — para bloques iface huérfanos (la otra mitad del mismo problema)." }, diff --git a/web/messages/es/docs/network/config-analysis.json b/web/messages/es/docs/network/config-analysis.json index af835473..637b08f6 100644 --- a/web/messages/es/docs/network/config-analysis.json +++ b/web/messages/es/docs/network/config-analysis.json @@ -1,12 +1,12 @@ { "meta": { - "title": "Análisis de config y limpieza guiada | ProxMenux Documentation", + "title": "Análisis de configuración y limpieza guiada | Documentación de ProxMenux", "description": "Detecta bloques iface en /etc/network/interfaces que referencian NICs físicas que ya no están en el host. Los reporta y ofrece una eliminación guiada de 5 pasos con backup obligatorio y previsualización por bloque.", - "ogTitle": "Análisis de config y limpieza guiada | ProxMenux Documentation", + "ogTitle": "Análisis de configuración y limpieza guiada | Documentación de ProxMenux", "ogDescription": "Encuentra y elimina bloques de interfaz huérfanos dejados por cambios de hardware, con previsualización completa y rollback." }, "header": { - "title": "Análisis de config y limpieza guiada", + "title": "Análisis de configuración y limpieza guiada", "description": "Recorre cada bloque iface en /etc/network/interfaces y verifica que la NIC física subyacente sigue existiendo. Reporta los bloques huérfanos dejados por cambios de hardware y ofrece una eliminación guiada con backup, previsualización y confirmación por bloque.", "section": "Red" }, @@ -18,7 +18,7 @@ "heading": "En qué se diferencia del análisis de bridges", "headerAspect": "Aspecto", "headerBridge": "Análisis de bridges", - "headerConfig": "Análisis de config", + "headerConfig": "Análisis de configuración", "rows": [ { "aspect": "Mira", @@ -45,7 +45,7 @@ }, "step1": { "heading": "Paso 1: análisis (solo lectura)", - "intro": "Aborta inmediatamente si el host no usa la stack clásica. Si no, lista cada interfaz configurada que no sea loopback y reporta su estado:", + "intro": "Se detiene inmediatamente si el host no usa la pila clásica. En caso contrario, lista todas las interfaces configuradas salvo loopback e indica su estado:", "virtTitle": "Las interfaces virtuales están protegidas", "virtBody": "Los bridges (vmbrX) y los bonds (bondX) son construcciones virtuales. Pueden no existir legítimamente en el momento de la inspección (p. ej. un bridge aún sin puertos), por lo que el análisis nunca propone eliminarlos — solo NICs físicas." }, @@ -60,7 +60,7 @@ }, { "title": "2. Confirmar la lista a eliminar", - "body": "Lista exactamente qué bloques de interfaz física se van a eliminar. Si, entre el análisis y la limpieza, las NICs han reaparecido (p. ej. has reasentado una tarjeta), el flujo sale con \"No cleanup needed.\"", + "body": "Lista exactamente qué bloques de interfaz física se eliminarán. Si las NIC han reaparecido entre el análisis y la limpieza, por ejemplo al volver a insertar una tarjeta, el proceso termina con «No es necesaria ninguna limpieza».", "tone": "blue" }, { @@ -91,7 +91,7 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "notFoundTitle": "El análisis muestra mi NIC nueva como \"NOT FOUND\"", + "notFoundTitle": "El análisis muestra mi NIC nueva como «NO ENCONTRADA»", "notFoundBody": "El kernel ve el dispositivo con un nombre distinto al que hay en el fichero. Ejecuta ip link show para obtener el nombre real. Este es exactamente el caso en el que Nombres persistentes de interfaz evitaría el problema de aquí en adelante.", "tooMuchTitle": "La limpieza eliminó demasiado (un bloque que quería conservar)", "tooMuchBody": "Restaura desde el backup impreso en el paso 5:", diff --git a/web/messages/es/docs/network/diagnostics.json b/web/messages/es/docs/network/diagnostics.json index 2e01d19e..7ea0b105 100644 --- a/web/messages/es/docs/network/diagnostics.json +++ b/web/messages/es/docs/network/diagnostics.json @@ -1,8 +1,8 @@ { "meta": { - "title": "Diagnóstico | ProxMenux Documentation", - "description": "Tres comprobaciones de diagnóstico puntuales de solo lectura: Mostrar tabla de rutas, Test de conectividad y Diagnóstico avanzado. Pura inspección — nunca escribe en /etc/network/interfaces y nunca ejecuta un comando modificador (con una excepción explícita y opt-in para purgar NetworkManager cuando se detecta).", - "ogTitle": "Diagnóstico | ProxMenux Documentation", + "title": "Diagnóstico | Documentación de ProxMenux", + "description": "Tres comprobaciones de diagnóstico puntuales de solo lectura: Mostrar tabla de rutas, Test de conectividad y Diagnóstico avanzado. Pura inspección: nunca escribe en /etc/network/interfaces ni ejecuta comandos modificadores, con una excepción explícita que requiere tu aceptación para purgar NetworkManager si se detecta.", + "ogTitle": "Diagnóstico | Documentación de ProxMenux", "ogDescription": "Diagnóstico de red de solo lectura para el host Proxmox: rutas, alcanzabilidad, estadísticas avanzadas." }, "header": { @@ -21,12 +21,12 @@ "connectivity": { "heading": "Test de conectividad", "intro": "Envía dos sondas ICMP a tres destinos en secuencia y reporta cada uno. Luego ejecuta un nslookup google.com para verificar la resolución DNS independientemente de ICMP.", - "headerTest": "Test", + "headerTest": "Prueba", "headerTarget": "Destino", "headerConfirms": "Qué confirma", "rows": [ { - "test": "Google DNS", + "test": "DNS de Google", "target": "8.8.8.8", "confirms": "Alcanzabilidad externa por IP anycast conocida" }, @@ -36,7 +36,7 @@ "confirms": "Segunda opinión independiente si Google bloquea ICMP" }, { - "test": "Gateway", + "test": "Pasarela", "target": "(ruta por defecto)", "confirms": "Conectividad L2 / L3 con el router local" }, @@ -53,7 +53,7 @@ "heading": "Diagnóstico avanzado", "intro": "Agrega estadísticas de red a nivel global y ejecuta una batería de comprobaciones de problemas comunes. Reporta el número de conexiones activas, puertos en escucha, total de interfaces y señala dos antipatrones explícitamente:", "items": [ - "NetworkManager corriendo en un host Proxmox. NetworkManager y el ifupdown de Proxmox entran en conflicto — los dos intentan gestionar interfaces, lo que provoca bridges que flapean o se niegan a levantar. Si se detecta, la herramienta ofrece un prompt interactivo para detenerlo, deshabilitarlo y purgarlo (es el único diagnóstico que puede modificar el sistema, y solo tras consentimiento explícito).", + "NetworkManager ejecutándose en un host Proxmox. NetworkManager y el ifupdown de Proxmox entran en conflicto: ambos intentan gestionar las interfaces, lo que provoca bridges con caídas intermitentes o que no se activan. Si se detecta, la herramienta ofrece una confirmación interactiva para detenerlo, deshabilitarlo y purgarlo (es el único diagnóstico que puede modificar el sistema, y solo tras consentimiento explícito).", "Direcciones IP duplicadas en distintas interfaces. Dos interfaces con la misma IPv4 causan conectividad intermitente extremadamente difícil de depurar desde dentro de la VM. Se detecta vía ip -4 addr show | sort | uniq -d." ], "nmTitle": "El prompt de NetworkManager es la única acción modificadora aquí", @@ -80,7 +80,7 @@ "tail": " — cuando un vmbrX se queda sin su puerto físico." }, { - "label": "Análisis de config y limpieza guiada", + "label": "Análisis de configuración y limpieza guiada", "href": "/docs/network/config-analysis", "tail": " — cuando una NIC antigua sigue declarada pero ya no está." }, diff --git a/web/messages/es/docs/network/index.json b/web/messages/es/docs/network/index.json index 2d7e778e..66c61ea1 100644 --- a/web/messages/es/docs/network/index.json +++ b/web/messages/es/docs/network/index.json @@ -1,15 +1,15 @@ { "meta": { "title": "Gestión de red en Proxmox — bridges, bonds, diagnóstico, reparación | ProxMenux", - "description": "Diagnóstico de solo lectura, flujos de analizar-y-sugerir y reparaciones guiadas para la stack de red Debian / Proxmox (/etc/network/interfaces). Inspecciona bridges y bonds, ejecuta herramientas de monitorización en vivo, persiste nombres de interfaz, hace backup y reinicia la red de forma segura — con backups obligatorios y previsualizaciones paso a paso.", + "description": "Diagnóstico de solo lectura, análisis con propuestas y reparaciones guiadas para la pila de red Debian / Proxmox (/etc/network/interfaces). Inspecciona bridges y bonds, ejecuta herramientas de monitorización en vivo, fija nombres de interfaz, crea copias de seguridad y reinicia la red de forma segura, con copias obligatorias y vistas previas paso a paso.", "ogTitle": "Gestión de red en Proxmox — bridges, bonds, diagnóstico, reparación", - "ogDescription": "Diagnóstico, análisis y reparaciones guiadas para la stack de red de Proxmox con backups obligatorios y previsualizaciones paso a paso.", + "ogDescription": "Diagnóstico, análisis y reparaciones guiadas para la pila de red de Proxmox con copias de seguridad obligatorias y vistas previas paso a paso.", "twitterTitle": "Gestión de red en Proxmox | ProxMenux", - "twitterDescription": "Bridges, bonds, diagnóstico y reparaciones guiadas para la stack de red de Proxmox con backups obligatorios." + "twitterDescription": "Bridges, bonds, diagnóstico y reparaciones guiadas para la pila de red de Proxmox con copias de seguridad obligatorias." }, "header": { "title": "Gestión de red", - "description": "Diagnóstico de solo lectura, informes de analizar-y-sugerir y reparaciones guiadas para la stack clásica de red Debian/Proxmox. Cada flujo destructivo hace primero un backup y previsualiza los cambios exactos antes de aplicarlos.", + "description": "Diagnóstico de solo lectura, informes de análisis con propuestas y reparaciones guiadas para la pila de red clásica de Debian/Proxmox. Cada operación destructiva crea primero una copia de seguridad y muestra los cambios exactos antes de aplicarlos.", "section": "Red" }, "intro": { @@ -52,8 +52,8 @@ ] } }, - "classicTitle": "Solo stack clásica", - "classicBody": "Cada función de análisis y reparación comprueba el gestor de red activo antes de tocar nada. Si el host usa netplan, systemd-networkd o NetworkManager, la herramienta aborta inmediatamente con un mensaje claro — el menú solo soporta la stack clásica de Debian/Proxmox en /etc/network/interfaces. Es intencional: editar un fichero de netplan con reglas escritas para ifupdown corrompería silenciosamente la configuración.", + "classicTitle": "Solo la pila de red clásica", + "classicBody": "Cada función de análisis y reparación comprueba el gestor de red activo antes de tocar nada. Si el host usa netplan, systemd-networkd o NetworkManager, la herramienta se detiene inmediatamente con un mensaje claro: el menú solo admite la pila clásica de Debian/Proxmox en /etc/network/interfaces. Es intencional: editar un fichero de netplan con reglas escritas para ifupdown corrompería silenciosamente la configuración.", "backups": { "heading": "Dónde van los backups", "intro": "Cada reparación guiada, restauración o backup manual escribe una copia con timestamp de /etc/network/interfaces en /var/backups/proxmenux/:", @@ -61,7 +61,7 @@ }, "readOnlySection": { "heading": "Inspección de solo lectura", - "body": "El punto de partida cuando algo huele raro. Pura inspección — nunca escribe en /etc/network/interfaces y nunca ejecuta un comando modificador (con una única excepción explícita y opt-in para purgar NetworkManager cuando se detecta). Seguro de usar por SSH en cualquier momento.", + "body": "El punto de partida cuando algo parece ir mal. Pura inspección: nunca escribe en /etc/network/interfaces ni ejecuta comandos que modifiquen el sistema (con una única excepción explícita, que requiere tu aceptación, para purgar NetworkManager cuando se detecta). Se puede usar por SSH en cualquier momento de forma segura.", "options": [ { "title": "Diagnóstico", @@ -82,7 +82,7 @@ "description": "Detecta bridges vmbrX con puertos ausentes o inválidos (típico tras una re-enumeración PCI). Muestra primero un informe; solo repara cuando aceptas el flujo guiado de 5 pasos." }, { - "title": "Análisis de config y limpieza guiada", + "title": "Análisis de configuración y limpieza guiada", "description": "Encuentra interfaces físicas declaradas en /etc/network/interfaces que ya no existen (configs huérfanas que dejan cambios de hardware). Las reporta y ofrece una eliminación guiada." } ] diff --git a/web/messages/es/docs/network/monitoring.json b/web/messages/es/docs/network/monitoring.json index 2db64fb1..d2d6c671 100644 --- a/web/messages/es/docs/network/monitoring.json +++ b/web/messages/es/docs/network/monitoring.json @@ -1,13 +1,13 @@ { "meta": { - "title": "Herramientas de monitorización en vivo | ProxMenux Documentation", + "title": "Herramientas de monitorización en vivo | Documentación de ProxMenux", "description": "Tres lanzadores interactivos de monitorización de red: iftop (ancho de banda en tiempo real por par de hosts), iptraf-ng (monitor de tráfico multimodo) e iperf3 (test de ancho de banda, modo servidor / cliente). Cada uno se instala la primera vez que se usa.", - "ogTitle": "Herramientas de monitorización en vivo | ProxMenux Documentation", + "ogTitle": "Herramientas de monitorización en vivo | Documentación de ProxMenux", "ogDescription": "Monitorización interactiva de red y test de ancho de banda para el host Proxmox: iftop, iptraf-ng, iperf3." }, "header": { "title": "Herramientas de monitorización en vivo", - "description": "Tres lanzadores interactivos para observación de red en tiempo real y medición de ancho de banda: iftop, iptraf-ng e iperf3. Cada herramienta se autoinstala desde apt la primera vez que se usa, corre en primer plano y se sale con la tecla documentada. De solo lectura sobre la configuración del host — solo observan el tráfico.", + "description": "Tres lanzadores interactivos para observar la red en tiempo real y medir el ancho de banda: iftop, iptraf-ng e iperf3. Cada herramienta se instala automáticamente desde apt la primera vez que se usa, se ejecuta en primer plano y se cierra con la tecla indicada. No cambian la configuración de red del host; iperf3 genera tráfico de prueba.", "section": "Red" }, "intro": { @@ -48,28 +48,28 @@ "headerUseFor": "Úsalo para", "rows": [ { - "mode": "IP traffic monitor", + "mode": "Monitor de tráfico IP", "useFor": "Lista en vivo de flujos TCP / UDP / ICMP / otro-IP con contadores de bytes y estado de conexión" }, { - "mode": "General interface stats", + "mode": "Estadísticas generales de interfaces", "useFor": "Conteos agregados de paquetes IPv4 / IPv6 / TCP / UDP / ICMP / no-IP por NIC" }, { - "mode": "Detailed interface stats", + "mode": "Estadísticas de interfaz detalladas", "useFor": "Igual que arriba pero para una interfaz, con detalle de tamaño y tasa de paquetes" }, { - "mode": "Statistical breakdowns", + "mode": "Desglose estadístico", "useFor": "Histograma de distribución de tamaño de paquete, por puerto TCP / UDP" }, { - "mode": "LAN station monitor", + "mode": "Monitor de estaciones LAN", "useFor": "Estadísticas de tráfico por MAC del dominio de broadcast local" } ], "exit": "Salir: pulsa x desde cualquier vista (o Q desde el menú principal). ProxMenux muestra un diálogo recordatorio antes de lanzarlo.", - "logTitle": "El logging captura a fichero", + "logTitle": "Guardar las capturas en un fichero", "logBody": "Cada modo ofrece registrar las estadísticas capturadas en /var/log/iptraf-ng/. Útil si necesitas un registro de un pico de tráfico — déjalo corriendo, vuelca al fichero, revisa el fichero después en lugar de intentar leer la pantalla en vivo." }, "iperf3": { @@ -81,24 +81,24 @@ "headerCli": "CLI equivalente", "rows": [ { - "mode": "Server", + "mode": "Servidor", "behaviour": "Escucha en TCP 5201 e imprime resultados de cada test entrante. Se detiene con Ctrl+C.", "cli": "iperf3 -s" }, { - "mode": "Client", + "mode": "Cliente", "behaviour": "Pide la IP / hostname del servidor, conecta, ejecuta un test por defecto de 10 segundos e imprime el informe.", "cli": "iperf3 -c " } ], "workflowIntro": "Flujo típico para probar un enlace 10 GbE entre dos hosts Proxmox:", "workflow": [ - "En el host A, abre el menú Red → Test de ancho de banda (iperf3) → elige Server.", - "En el host B, abre la misma entrada de menú → elige Client → introduce la IP del host A.", + "En el host A, abre el menú Red → Test de ancho de banda (iperf3) → elige Servidor.", + "En el host B, abre la misma entrada de menú → elige Cliente → introduce la IP del host A.", "Espera 10 segundos. Compara la tasa reportada con el máximo teórico del enlace." ], "sample": "Salida de ejemplo del cliente:", - "flagsTitle": "Flags útiles a mano (desde una shell)", + "flagsTitle": "Opciones útiles para ejecutar a mano desde una shell", "flagsBody": "-t 60 test más largo (60s en lugar de los 10s por defecto), -P 4 4 streams en paralelo (satura más rápido), -R dirección inversa (servidor → cliente), -u -b 100M test UDP a 100 Mbit/s (para mediciones de jitter / pérdida de paquetes), -p 5202 usa un puerto distinto (varios tests en paralelo). man iperf3 para el set completo.", "firewallTitle": "Abre el puerto del firewall en el servidor", "firewallBody": "El servidor escucha en TCP 5201 por defecto. Si corres el servidor dentro de un host Proxmox con un firewall estricto (a nivel de datacenter o de host), permite entrada TCP 5201 desde la IP del cliente durante el test y luego ciérralo. Lo mismo aplica a nftables / iptables en hosts bare metal." diff --git a/web/messages/es/docs/network/persistent-names.json b/web/messages/es/docs/network/persistent-names.json index fb4e9b9f..c6c4de2a 100644 --- a/web/messages/es/docs/network/persistent-names.json +++ b/web/messages/es/docs/network/persistent-names.json @@ -1,8 +1,8 @@ { "meta": { - "title": "Nombres persistentes de interfaz | ProxMenux Documentation", + "title": "Nombres persistentes de interfaz | Documentación de ProxMenux", "description": "Fija los nombres de interfaz de Proxmox (eno1, enp3s0, …) a direcciones MAC mediante ficheros .link de systemd para que los nombres sobrevivan a la re-enumeración PCI, actualizaciones de kernel y al añadir / quitar otras NICs.", - "ogTitle": "Nombres persistentes de interfaz | ProxMenux Documentation", + "ogTitle": "Nombres persistentes de interfaz | Documentación de ProxMenux", "ogDescription": "Evita que los nombres de interfaz de Proxmox cambien al modificar el hardware. Usa ficheros .link de systemd basados en la dirección MAC." }, "header": { @@ -16,7 +16,7 @@ }, "problem": { "heading": "El problema que resuelve", - "intro": "Linux deriva los nombres de interfaz por defecto a partir de la topología PCI — eno1 = onboard, enp3s0 = la tarjeta en el bus PCI 3, slot 0, etc. El esquema de naming es determinista dado el mismo layout de hardware. Cambia el layout y los nombres se mueven:", + "intro": "Linux obtiene los nombres de interfaz predeterminados a partir de la topología PCI: eno1 = integrada en placa, enp3s0 = la tarjeta en el bus PCI 3, ranura 0, etc. El esquema de nombres es determinista con la misma disposición de hardware. Si cambia la disposición, cambian los nombres:", "items": [ "Añade una GPU delante de una NIC existente → el número de bus de la NIC puede cambiar → el nombre cambia.", "Mueve una tarjeta a otro slot PCIe → el nombre cambia.", @@ -46,7 +46,7 @@ "headerWhy": "Por qué", "rows": [ { - "type": "NIC onboard / PCIe", + "type": "NIC integrada / PCIe", "behaviour": "Se escribe fichero .link", "why": "Respaldada por un dispositivo PCI real — el caso para el que está la herramienta" }, @@ -56,7 +56,7 @@ "why": "Tiene una MAC real y se beneficia de la estabilidad de nombre" }, { - "type": "Bridges (vmbrX)", + "type": "Puentes (vmbrX)", "behaviour": "Se omite", "why": "Virtual; el nombre viene de /etc/network/interfaces" }, @@ -87,7 +87,7 @@ "intro": "Si /etc/systemd/network/ ya contiene ficheros .link (de una ejecución anterior u otra herramienta), se copian a un directorio de backup con timestamp antes de generar los nuevos:", "outro": "Para hacer rollback: copia los ficheros desde el directorio de backup y reinicia.", "rebootTitle": "Tiene efecto en el siguiente reinicio, no inmediatamente", - "rebootBody": "Los cambios en ficheros .link solo se aplican en el arranque, cuando udev re-enumera los dispositivos. La herramienta reporta \"Changes will apply after reboot\" por este motivo. Renombrar una interfaz en vivo es arriesgado y no se intenta: un vmbr0 activo con miembros tendría que reconfigurarse atómicamente, por eso la operación se difiere al siguiente arranque limpio." + "rebootBody": "Los cambios en ficheros .link solo se aplican en el arranque, cuando udev vuelve a enumerar los dispositivos. Por eso la herramienta indica «Los cambios se aplicarán tras reiniciar». No intenta renombrar una interfaz en vivo por el riesgo que supone: un vmbr0 activo con interfaces miembro tendría que reconfigurarse de forma atómica, así que la operación se aplaza al siguiente arranque limpio." }, "afterReboot": { "heading": "Tras el reinicio", @@ -100,11 +100,11 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "emptyTitle": "\"No physical interfaces found\" tras ejecutar la herramienta", + "emptyTitle": "«No se han encontrado interfaces físicas» tras ejecutar la herramienta", "emptyBody": "El host no tiene interfaces respaldadas por PCI / phy80211 visibles para el kernel. Confirma con ls -l /sys/class/net/ — cada entrada debería tener un symlink device (PCI) o una entrada phy80211 (Wi-Fi). Si faltan ambos para lo que debería ser una NIC real, el driver no está cargado.", "noChangeTitle": "Tras el reinicio, los nombres no han cambiado como esperaba", "noChangeBody": "Comprueba que el fichero está presente y bien formado: cat /etc/systemd/network/10-<iface>.link. Después mira los logs de udev: journalctl -u systemd-udevd -b | grep -i link. Una causa habitual es un parámetro de kernel net.ifnames=0 residual que deshabilita el naming predictivo por completo — quítalo de /etc/default/grub, ejecuta update-grub y reinicia.", - "undoTitle": "Quiero deshacer el naming persistente", + "undoTitle": "Quiero deshacer los nombres persistentes", "undoBody": "O bien borra los ficheros .link (rm /etc/systemd/network/10-*.link) o restaura desde el directorio de backup generado en la ejecución anterior. Reinicia para aplicar." }, "related": { @@ -116,7 +116,7 @@ "tailRich": " — el camino de recuperación cuando los nombres ya se han movido." }, { - "label": "Análisis de config y limpieza guiada", + "label": "Análisis de configuración y limpieza guiada", "href": "/docs/network/config-analysis", "tail": " — para eliminar bloques huérfanos dejados por los cambios de nombre." }, diff --git a/web/messages/fr/docs/network/backup-restore.json b/web/messages/fr/docs/network/backup-restore.json new file mode 100644 index 00000000..5273c0c9 --- /dev/null +++ b/web/messages/fr/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Sauvegarde et redémarrage des interfaces | Documentation ProxMenux", + "description": "Capture manuelle, navigation et restauration des sauvegardes /etc/network/interfaces stockées sous /var/backups/proxmenux/. Comprend un redémarrage du service réseau avec confirmation et une vue rapide de la configuration en direct.", + "ogTitle": "Sauvegarde et redémarrage des interfaces | Documentation ProxMenux", + "ogDescription": "Créez un instantané de la configuration réseau Proxmox et restaurez-la ; redémarrez le service réseau avec confirmation." + }, + "header": { + "title": "Sauvegarde et redémarrage des interfaces", + "description": "Quatre utilitaires qui tournent autour de /etc/network/interfaces: prennent un instantané manuel, visualisent la configuration en direct, restaurent une sauvegarde précédente avec prévisualisation optionnelle et redémarrent le service de réseautage avec un consentement explicite. Même répertoire de sauvegarde utilisé par les flux de réparation guidés.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Gère le même répertoire de sauvegarde utilisé par les flux de réparation guidés (/var/backups/proxmenux/). Permet de prendre un instantané supplémentaire avant tout changement manuel, de parcourir toutes les sauvegardes existantes, et de revenir à l'une d'entre elles — toujours avec une nouvelle sauvegarde pré-restauration prise automatiquement comme deuxième filet de sécurité." + }, + "shared": { + "heading": "Le répertoire de sauvegarde partagé", + "intro": "Chaque sauvegarde prise n'importe où dans le menu Réseau atterrit au même endroit :", + "outro": "Les noms de fichiers sont horodatés, triés par date et jamais écrasés. Le répertoire est créé lors de la première utilisation." + }, + "show": { + "heading": "Afficher le fichier de configuration réseau", + "body": "Affiche le contenu exact de /etc/network/interfaces dans le terminal. En lecture seule. Utile pour vérifier la configuration avant une sauvegarde ou après une restauration. Il n’existe pas d’outil distinct pour comparer avec la sauvegarde précédente ; examiner le fichier actuel est donc le moyen le plus simple de confirmer son contenu." + }, + "create": { + "heading": "Créer une sauvegarde réseau", + "body": "Copie le /etc/network/interfaces actuel dans le répertoire de sauvegarde avec un nouvel horodatage. C'est tout — pas d'analyse, pas d'invite, juste un instantané.", + "whenTitle": "Quand prendre une sauvegarde manuelle", + "whenBody": "Avant de modifier /etc/network/interfaces à la main, d’installer un paquet pouvant toucher la pile réseau (par exemple NetworkManager ou Open vSwitch), ou de changer le matériel. Les réparations guidées créent déjà automatiquement un instantané — cette option sert aux opérations manuelles." + }, + "restore": { + "heading": "Restaurer la sauvegarde réseau", + "intro": "Liste les sauvegardes de /var/backups/proxmenux/, de la plus récente à la plus ancienne, et guide une restauration avec des garde-fous :", + "steps": [ + { + "title": "1. Choisir une sauvegarde", + "body": "Un menu liste chaque sauvegarde par son horodatage. S'il n'y a pas de sauvegardes, le flux sort avec un message clair.", + "tone": "blue" + }, + { + "title": "2. Aperçu facultatif", + "body": "Offre d'ouvrir la sauvegarde sélectionnée dans une vue défilante avant de s'engager. Oui par défaut — ne le sautez pas sur un hôte distant.", + "tone": "blue" + }, + { + "title": "3. Sauvegarde avant restauration", + "body": "Avant d'écraser /etc/network/interfaces, prend une autre sauvegarde de l'état actuel. La restauration d'une sauvegarde est elle-même une action destructrice, de sorte que le nouvel instantané pré-restauration vous permet de revenir si la sauvegarde choisie s'avère être la mauvaise.", + "tone": "amber" + }, + { + "title": "4. Appliquer la restauration", + "body": "Copie la sauvegarde choisie sur /etc/network/interfaces. Le changement de fichier est sur disque; l'état du noyau en direct reflète toujours la configuration précédente.", + "tone": "amber" + }, + { + "title": "5. Redémarrage facultatif", + "body": "Demande s’il faut exécuter systemctl restart networking maintenant. Refusez pour reporter l’application au prochain redémarrage.", + "tone": "emerald" + } + ], + "autoBackupTitle": "Une restauration prend sa propre sauvegarde automatiquement", + "autoBackupBody": "Sélectionner Restaurer la sauvegarde réseau crée toujours un nouvel instantané de la configuration actuelle avant de l’écraser. Si vous restaurez la mauvaise sauvegarde, le fichier le plus récent dans /var/backups/proxmenux/ contient l’état précédent." + }, + "restart": { + "heading": "Redémarrer le service réseau", + "body": "Exécute systemctl restart networking après confirmation oui/non. Affiche le résultat avec msg_ok / msg_error. La plupart des procédures de ce menu proposent déjà un redémarrage à la fin ; cette option sert après une modification manuelle ou si vous avez refusé le redémarrage proposé auparavant.", + "warnTitle": "Brève déconnexion garantie; possibilité de déconnexion permanente", + "warnBody": "Redémarrer networking désactive puis reconfigure chaque interface déclarée dans /etc/network/interfaces. Les sessions SSH passant par ces interfaces sont interrompues. Si la nouvelle configuration est invalide, le réseau ne revient pas. N’exécutez cette opération que si :", + "warnItems": [ + "Vous avez la console / IPMI / accès iKVM prêt, ou", + "Vous êtes physiquement à côté de la machine, ou", + "Vous venez de restaurer avec succès une configuration dont le bon fonctionnement est connu et souhaitez l’appliquer." + ] + }, + "manualRollback": { + "heading": "Retour manuel depuis une console", + "intro": "Si vous ne pouvez pas accéder au menu (pas de SSH, plantage de dialog, …) mais disposez d’une console, deux commandes suffisent pour revenir en arrière :", + "outro": "C’est la même opération que celle du menu, simplement saisie à la main. Si networking ne démarre toujours pas, consultez journalctl -u networking -b pour identifier l’erreur de syntaxe ou de pilote." + }, + "troubleshoot": { + "heading": "Dépannage", + "noneTitle": "« Aucune sauvegarde trouvée » lors de la restauration", + "noneBody": "Le répertoire /var/backups/proxmenux/ n’existe pas ou ne contient aucun fichier interfaces_backup_*. Exécutez Créer une sauvegarde réseau au moins une fois ou lancez une réparation guidée, qui crée automatiquement une sauvegarde. Vérifiez avec ls /var/backups/proxmenux/.", + "unreachTitle": "Le redémarrage du réseau réussit, mais l’hôte reste inaccessible", + "unreachBody": "La nouvelle configuration est incorrecte ou n’attribue pas votre adresse IP de gestion. Revenez en arrière depuis la console (voir ci-dessus). Utilisez ensuite l’analyse des ponts et l’analyse de configuration pour comprendre le problème avant de réappliquer la configuration.", + "emptyTitle": "L’aperçu de restauration affiche un fichier vide", + "emptyBody": "Le fichier de sauvegarde est vide (0 octet). Cela arrive si une copie a échoué silencieusement lors d’une exécution précédente. Choisissez une sauvegarde plus ancienne ou relancez Créer une sauvegarde réseau maintenant si la configuration actuelle fonctionne." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Analyse des ponts et réparation guidée", + "href": "/docs/network/bridge-analysis", + "tail": "— la raison la plus courante pour laquelle une sauvegarde se termine ici." + }, + { + "label": "Analyse de configuration et nettoyage guidé", + "href": "/docs/network/config-analysis", + "tailRich": "— écrit également à /var/backups/proxmenux/ automatiquement." + }, + { + "label": "Diagnostic", + "href": "/docs/network/diagnostics", + "tail": " — pour vérifier l’état actuel après une restauration." + } + ] + } +} diff --git a/web/messages/fr/docs/network/bridge-analysis.json b/web/messages/fr/docs/network/bridge-analysis.json new file mode 100644 index 00000000..7a5a5903 --- /dev/null +++ b/web/messages/fr/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Analyse des ponts et réparation guidée | Documentation ProxMenux", + "description": "Détecte les ponts vmbrX dont les ports physiques sont absents ou invalides, généralement après une réénumération PCI. Les signale avec des commandes shell proposées et offre une réparation guidée en 5 étapes avec sauvegarde obligatoire.", + "ogTitle": "Analyse des ponts et réparation guidée | Documentation ProxMenux", + "ogDescription": "Vérifiez et réparez les ports des ponts Proxmox après des changements matériels, avec une procédure guidée permettant un retour en arrière sûr." + }, + "header": { + "title": "Analyse des ponts et réparation guidée", + "description": "Examine chaque pont vmbrX déclaré dans /etc/network/interfaces, vérifie l’existence de ses ports physiques et propose une réparation étape par étape s’ils sont absents. L’analyse est en lecture seule ; la réparation nécessite un accord, présente un aperçu et crée une sauvegarde.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Lit chaque pont dans /etc/network/interfaces, examine sa ligne bridge-ports et vérifie l’existence de chaque port déclaré, par exemple enp3s0, sur l’hôte. Si un port manque, propose un remplacement et applique la modification avec un consentement explicite selon la procédure sauvegarder, prévisualiser, appliquer, vérifier." + }, + "when": { + "heading": "Quand vous en avez besoin", + "intro": "Linux attribue des noms d'interface prévisibles à partir de la topologie PCI et de l'ordre des fentes. Plusieurs événements les mélangent et laissent /etc/network/interfaces se référant à des noms qui n'existent plus :", + "items": [ + "Ajouter ou retirer une carte PCIe (une carte NIC, un GPU, un HBA, un support NVMe).", + "Déplacement d'une carte existante vers une autre fente PCIe.", + "Mises à jour du BIOS / UEFI qui réénumèrent les périphériques PCI.", + "Migrer le disque de démarrage vers différents matériels." + ], + "outro": "Après le prochain démarrage, le pont tente de se fixer à un port qui n'existe plus, ne parvient pas à monter, et l'hôte perd son réseau. Les noms d'interface persistants empêchent cela de se reproduire — mais si vous êtes déjà verrouillé, cette page est le chemin de récupération." + }, + "bigPicture": { + "heading": "La situation générale", + "diagram1": { + "arrowLabel": "étape 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analyser (lecture seule)", + "bridgeDetail": "ip link show enp3s0\n→ n’existe pas", + "targetLabel": "Rapport + suggestion", + "targetDetail": "❌ enp3s0 : INTROUVABLE\nRemplacer par : eno1\n(pas encore de modification)" + } + }, + "diagram2": { + "arrowLabel": "appliquer avec sauvegarde", + "nodes": { + "sourceLabel": "Réparation guidée", + "sourceDetail": "1. Sauvegarder\n2. Afficher l’état actuel\n3. Prévisualiser les modifications\n4. Appliquer\n5. Vérifier", + "targetLabel": "/etc/network/interfaces (nouveau)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Étape 1: analyse (lecture seule)", + "intro": "Sélectionner Analyser la configuration des ponts arrête immédiatement l’outil si l’hôte n’utilise pas la pile classique Debian/Proxmox. Sinon, il examine chaque pont et indique :", + "items": [ + "Nom du pont, état actuel (UP / DOWN), IP assigné.", + "Chaque port déclaré et s'il existe actuellement (ip link show <port>).", + "Pour chaque port invalide : un remplacement proposé (la première interface physique disponible) plus la commande exacte sed pour l'appliquer manuellement.", + "Ponts sans bridge-ports du tout et entrées orphelines auto (pas de bloc correspondant iface)." + ], + "readonlyTitle": "Garantie en lecture seule", + "readonlyBody": "Jusqu'à ce point, le script n'a pas exécuté une seule commande de modification. Vous pouvez laisser l'analyse ouverte, copier les commandes sed suggérées et les appliquer manuellement si vous préférez — ou accepter l'invite suivante pour entrer dans la réparation guidée." + }, + "step2": { + "heading": "Étape 2 : réparation guidée (5 étapes)", + "intro": "Accessible uniquement si vous acceptez la proposition à la fin de l’analyse. Chaque étape présente l’action prévue et demande une confirmation avant de continuer.", + "steps": [ + { + "title": "1. Sauvegarde de sécurité", + "body": "Copie /etc/network/interfaces vers /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Le chemin exact de sauvegarde est affiché avant la copie et à nouveau après, avec la commande rollback.", + "tone": "blue" + }, + { + "title": "2. Examiner la configuration actuelle", + "body": "Ouvre le fichier actuel /etc/network/interfaces dans une boîte de dialogue avec défilement pour voir exactement ce qui va être modifié.", + "tone": "blue" + }, + { + "title": "3. Aperçu des modifications proposées", + "body": "Liste précisément les ponts concernés et les remplacements de ports prévus. Si aucune réparation n’est plus nécessaire parce que le port est réapparu entre les vérifications, la procédure se termine avec « Aucune modification nécessaire. »", + "tone": "blue" + }, + { + "title": "4. Appliquer les modifications", + "body": "Pour chaque pont touché, exécute sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" contre /etc/network/interfaces. Si un pont n'avait pas de remplacement valide disponible, la substitution est ignorée et signalée à l'étape 5.", + "tone": "amber" + }, + { + "title": "5. Vérification", + "body": "Relit le fichier et confirme l’existence du nouveau port de chaque pont. Affiche la commande de retour en arrière (cp <backup> /etc/network/interfaces). Propose enfin Redémarrer le réseau : n’acceptez que si vous disposez d’un accès de secours à la console.", + "tone": "emerald" + } + ], + "restartTitle": "Redémarrer le réseau ≠ annuler sans risque", + "restartBody": "La réparation est écrite sur le disque, même si vous ne redémarrez pas le service. systemctl restart networking applique la modification maintenant, ce qui peut interrompre votre session SSH si le nouveau port est incorrect. En cas de refus, la modification prendra quand même effet au prochain redémarrage : vérifiez d’abord la configuration ou revenez en arrière avec la commande affichée." + }, + "edits": { + "heading": "Ce qui est édité (exactement)", + "body": "Uniquement la ligne bridge-ports de chaque pont concerné. Les autres directives (address, netmask, gateway, bridge-stp, …) restent intactes. Le script ne crée ni ne supprime de blocs iface dans cette procédure : c’est le rôle de l’analyse de configuration & du nettoyage." + }, + "troubleshoot": { + "heading": "Dépannage", + "unsupportedTitle": "L’analyse s’arrête avec « Pile réseau non prise en charge »", + "unsupportedBody": "L'hôte exécute netplan, systemd-networkd ou NetworkManager. L'outil ne prend en charge que /etc/network/interfaces. Passez d'abord l'hôte à la pile classique ou modifiez la configuration avec l'outil natif du gestionnaire (p. ex. netplan apply).", + "noSuggestTitle": "Aucune suggestion n'est imprimée pour un port invalide", + "noSuggestBody": "L'hôte a zéro interfaces physiques libres (tout est déjà dans un autre pont ou n'existe pas). L'analyse revient à suggérer bridge-ports none afin que le pont puisse au moins monter sans port. Ajouter un NIC ou migrer les ports entre les ponts manuellement avant de recommencer.", + "stillDownTitle": "La réparation s'achève mais le pont est toujours DOWN", + "stillDownBody": "sed a mis à jour le fichier, mais systemctl restart networking a été refusé. Exécutez-le manuellement une fois que la nouvelle configuration est vérifiée, ou redémarrez. Si le redémarrage a été accepté et que le pont est toujours DOWN, exécutez ip link show pour confirmer l'existence du nouveau port, puis vérifiez journalctl -u networking pour l'erreur réelle (câble débranché, lien non négocié, port déjà membre d'un autre pont).", + "lostSshTitle": "J'ai perdu l'accès SSH juste après le redémarrage du réseau", + "lostSshIntro": "Utilisez console / IPMI / iKVM pour atteindre l'hôte. Restaurer la sauvegarde :", + "lostSshOutro": "Relancez ensuite l’analyse depuis la console pour comprendre pourquoi la suggestion n’a pas fonctionné, généralement parce que le port de remplacement n’est pas branché." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Noms d'interface persistants", + "href": "/docs/network/persistent-names", + "tail": "— épingler les noms aux adresses MAC afin que ce scénario cesse de se produire." + }, + { + "label": "Analyse de configuration et nettoyage guidé", + "href": "/docs/network/config-analysis", + "tailRich": "— pour les blocs orphelins de iface (l'autre moitié du même problème)." + }, + { + "label": "Sauvegarde et redémarrage des interfaces", + "href": "/docs/network/backup-restore", + "tail": "— les instantanés manuels et le navigateur de restauration." + } + ] + } +} diff --git a/web/messages/fr/docs/network/config-analysis.json b/web/messages/fr/docs/network/config-analysis.json new file mode 100644 index 00000000..1ada39d6 --- /dev/null +++ b/web/messages/fr/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Analyse de configuration et nettoyage guidé | Documentation ProxMenux", + "description": "Détecte les blocs iface de /etc/network/interfaces qui font référence à des cartes réseau physiques absentes de l’hôte. Les signale et propose une suppression guidée en 5 étapes avec sauvegarde obligatoire et aperçu de chaque bloc.", + "ogTitle": "Analyse de configuration et nettoyage guidé | Documentation ProxMenux", + "ogDescription": "Trouver et supprimer les blocs d'interface orphelins laissés derrière par les changements matériels, avec prévisualisation complète et retour." + }, + "header": { + "title": "Analyse de configuration et nettoyage guidé", + "description": "Examine chaque bloc iface de /etc/network/interfaces et vérifie que la carte réseau physique correspondante existe toujours. Signale les blocs orphelins laissés par des changements matériels et propose une suppression guidée avec sauvegarde, aperçu et confirmation de chaque bloc.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Identifie les déclarations iface dont le support NIC n'est plus présent sur l'hôte (typique après le remplacement ou la suppression d'une carte réseau). Ensuite, offre un nettoyage guidé qui supprime seulement les blocs orphelins, avec une sauvegarde, un aperçu par section et une confirmation finale explicite." + }, + "differs": { + "heading": "Comment il diffère de l'analyse de pont", + "headerAspect": "Aspect", + "headerBridge": "Analyse des ponts", + "headerConfig": "Analyse de la configuration", + "rows": [ + { + "aspect": "Regarde", + "bridge": "Ponts (vmbrX) et leur ligne bridge-ports", + "config": "Chaque bloc iface, sauf loopback, ponts et agrégations" + }, + { + "aspect": "Détecte", + "bridge": "Ponts faisant référence à un port inexistant", + "config": "Blocs d'interface autonomes pour les NIC qui n'existent pas" + }, + { + "aspect": "Action de réparation", + "bridge": "Remplace le nom du port dans bridge-ports", + "config": "Supprime tout le bloc iface" + }, + { + "aspect": "Non compris", + "bridge": "Rien — chaque pont est analysé", + "config": "Les ponts (vmbrX) et agrégations (bondX) sont toujours conservés : ils sont virtuels et leur absence peut être normale au démarrage" + } + ], + "outro": "En pratique, on utilise souvent les deux dans cet ordre : analyse de configuration pour supprimer les blocs orphelins, puis analyse des ponts pour réaffecter tout pont qui dépendait de l’interface supprimée." + }, + "step1": { + "heading": "Étape 1: analyse (lecture seule)", + "intro": "Avorte immédiatement si l'hôte n'est pas sur la pile classique. Sinon, liste toutes les interfaces non loopback configurées et indique son état :", + "virtTitle": "Les interfaces virtuelles sont protégées", + "virtBody": "Les ponts (vmbrX) et agrégations (bondX) sont virtuels. Leur absence peut être normale au moment de l’inspection, par exemple pour un pont sans port. L’analyse ne propose donc jamais de les supprimer, seulement les cartes réseau physiques." + }, + "step2": { + "heading": "Étape 2 : nettoyage guidé (5 étapes)", + "intro": "Accessible uniquement si vous acceptez la proposition à la fin de l’analyse. Chaque étape exige une confirmation ; annuler à tout moment quitte sans écrire.", + "steps": [ + { + "title": "1. Sauvegarde de sécurité", + "body": "Copie /etc/network/interfaces vers /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Chemin montré avant et après.", + "tone": "blue" + }, + { + "title": "2. Confirmer la liste de suppression", + "body": "Liste précisément les blocs d’interface physique à supprimer. Si les cartes réseau réapparaissent entre l’analyse et le nettoyage, par exemple après avoir réinséré une carte, la procédure se termine avec « Aucun nettoyage nécessaire. »", + "tone": "blue" + }, + { + "title": "3. Aperçu du ou des blocs exacts", + "body": "Ouvre une vue défilante de chaque bloc iface qui serait supprimé, mot à mot du fichier. C'est le moment de repérer par exemple un crochet up intéressant que vous voulez préserver.", + "tone": "blue" + }, + { + "title": "4. Appliquer la suppression", + "body": "Pour chaque interface orpheline, exécute sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. Cela supprime de la ligne correspondante iface jusqu'à la ligne blanche suivante.", + "tone": "amber" + }, + { + "title": "5. Vérification", + "body": "Relit le fichier, répertorie ce qui a été supprimé et revérifie les interfaces restantes. Imprime la commande de restauration. ne redémarre pas automatiquement le réseau : la suppression d'un bloc inutilisé peut être appliquée en toute sécurité au prochain redémarrage et évite de toucher à l'état actif.", + "tone": "emerald" + } + ], + "noRestartTitle": "Ne redémarre pas automatiquement le réseau", + "noRestartBody": "Contrairement à la réparation de pont, le flux de nettoyage n'offre pas de redémarrer le service. L'élimination d'un bloc d'interface inutilisé n'a aucun effet immédiat sur la configuration en cours d'exécution, de sorte qu'un redémarrage est inutile et serait un risque inutile de connectivité. Le changement prend effet sur le prochain manuel systemctl restart networking ou au prochain redémarrage." + }, + "caveats": { + "heading": "Mises en garde importantes", + "boundaryTitle": "La limite du bloc est la première ligne vide", + "boundaryBody": "Le motif sed supprime de iface <name> jusqu’à la ligne vide suivante. Si /etc/network/interfaces ne contient pas de ligne vide entre les blocs, ce qui est rare mais possible après une modification manuelle, le bloc suivant peut aussi être supprimé. C’est pourquoi l’étape 3 est obligatoire : examinez l’aperçu avant de confirmer.", + "tandemTitle": "Un bloc iface orphelin utilisé par un pont est détecté ici, pas dans l’analyse des ponts", + "tandemBody": "Si vmbr0 déclare bridge-ports enp3s0 et qu’enp3s0 a aussi son propre bloc iface enp3s0 inet manual, supprimer enp3s0 ici laisse une référence invalide dans vmbr0. Après le nettoyage, lancez l’analyse des ponts pour réaffecter ou retirer le port du pont. Les deux procédures sont conçues pour être utilisées ensemble." + }, + "troubleshoot": { + "heading": "Dépannage", + "notFoundTitle": "L’analyse indique « INTROUVABLE » pour ma nouvelle carte réseau", + "notFoundBody": "Le noyau voit le périphérique sous un autre nom que celui du fichier. Lancez ip link show pour connaître son nom réel. C’est précisément un cas où les noms d’interface persistants éviteraient le problème à l’avenir.", + "tooMuchTitle": "Le nettoyage a trop supprimé, dont un bloc que je voulais garder", + "tooMuchBody": "Restaurer à partir de la sauvegarde imprimée dans l'étape 5:", + "tooMuchOutro": "Modifier le fichier à la main pour ajouter une ligne blanche claire entre les blocs avant de ré-exécuter le nettoyage, ou supprimer le bloc problématique manuellement.", + "bridgeBreakTitle": "Un pont s'arrête après le nettoyage", + "bridgeBreakBody": "Le pont utilisait un port dont le bloc iface vient d’être supprimé. Lancez l’analyse des ponts ; la suggestion associera le pont à une interface physique existante." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Analyse des ponts et réparation guidée", + "href": "/docs/network/bridge-analysis", + "tail": "— le suivi naturel après un nettoyage." + }, + { + "label": "Noms d'interface persistants", + "href": "/docs/network/persistent-names", + "tail": "— empêche les blocs orphelins de s'accumuler après chaque changement matériel." + }, + { + "label": "Sauvegarde et redémarrage des interfaces", + "href": "/docs/network/backup-restore", + "tail": "— les instantanés manuels et le navigateur de restauration." + } + ] + } +} diff --git a/web/messages/fr/docs/network/diagnostics.json b/web/messages/fr/docs/network/diagnostics.json new file mode 100644 index 00000000..8560be31 --- /dev/null +++ b/web/messages/fr/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnostic | Documentation ProxMenux", + "description": "Trois diagnostics ponctuels en lecture seule : Afficher la table de routage, Tester la connectivité et Diagnostic avancé. Inspection uniquement — n’écrit jamais dans /etc/network/interfaces et n’exécute aucune commande de modification, à une exception explicite près : la purge de NetworkManager, s’il est détecté, nécessite votre accord.", + "ogTitle": "Diagnostic | Documentation ProxMenux", + "ogDescription": "Diagnostic réseau en lecture seule pour l'hôte Proxmox : routage, accessibilité, statistiques avancées." + }, + "header": { + "title": "Diagnostic", + "description": "Trois contrôles ponctuels en lecture seule de l’état actuel du réseau : table de routage, test de connectivité et statistiques avancées. Aucun n’écrit dans /etc/network/interfaces — utilisables en toute sécurité à tout moment, y compris via SSH. Pour la surveillance interactive en direct, consultez Outils de surveillance en direct.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Présente le routage, l’accessibilité et les statistiques agrégées sans rien modifier. Permet de vérifier le bon fonctionnement du réseau ou de repérer des adresses IP en double. Pour observer le trafic en continu, voir Outils de surveillance en direct ; pour consulter le fichier de configuration brut, voir Sauvegarde et redémarrage des interfaces." + }, + "routing": { + "heading": "Afficher la table de routage", + "body": "Liste chaque route que le noyau utilise actuellement (ip route show) et souligne la passerelle par défaut. Utile pour confirmer que le trafic vers Internet passe par l'interface prévue et qu'aucune route restante n'a priorité." + }, + "connectivity": { + "heading": "Tester la connectivité", + "intro": "Envoie deux sondes ICMP à trois cibles dans l'ordre et signale chacune. Ensuite, exécute un nslookup google.com pour vérifier la résolution DNS indépendamment de ICMP.", + "headerTest": "Essai", + "headerTarget": "Cible", + "headerConfirms": "Ce qu'il confirme", + "rows": [ + { + "test": "Google DNS", + "target": "8.8.8.8", + "confirms": "Accessibilité externe via une adresse IP anycast connue" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Deuxième avis indépendant si Google bloque ICMP" + }, + { + "test": "Passerelle", + "target": "(route par défaut)", + "confirms": "Connectivité L2 / L3 au routeur local" + }, + { + "test": "Résolution DNS", + "target": "google.com", + "confirms": "Le serveur DNS est accessible et répond" + } + ], + "readingTitle": "Lecture du résultat", + "readingBody": "Si le test de la passerelle réussit mais que les cibles DNS publiques ne répondent pas, vous avez une connectivité locale mais pas d’accès Internet : vérifiez la connexion en amont de la passerelle. Si la résolution DNS échoue mais que les pings vers 8.8.8.8 réussissent, le problème vient du résolveur, pas du réseau : vérifiez /etc/resolv.conf." + }, + "advanced": { + "heading": "Diagnostic avancé", + "intro": "Agrège les statistiques du réseau et effectue une série de vérifications des problèmes courants. Affiche le nombre de connexions actives, les ports en écoute et le nombre total d’interfaces, et signale explicitement deux configurations problématiques :", + "items": [ + "NetworkManager fonctionne sur un hôte Proxmox. NetworkManager et ifupdown de Proxmox entrent en conflit : tous deux tentent de gérer les interfaces, ce qui rend les ponts instables ou les empêche de s’activer. Si NetworkManager est détecté, l’outil propose une confirmation interactive pour l’arrêter, le désactiver et le purger (c’est le seul diagnostic pouvant modifier le système, et uniquement après consentement explicite).", + "Adresses IP en double sur différentes interfaces. Deux interfaces utilisant la même adresse IPv4 provoquent des problèmes de connexion intermittents très difficiles à diagnostiquer depuis la VM. Détection via ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "L'invite NetworkManager est la seule action de modification ici", + "nmBody": "Toutes les autres vérifications du diagnostic avancé sont en lecture seule. La suppression de NetworkManager nécessite une confirmation oui/non et exécute systemctl stop / disable + apt-get purge -y network-manager. Si vous refusez, rien ne change." + }, + "troubleshoot": { + "heading": "Dépannage", + "gwTitle": "Tester la connectivité signale un échec de la passerelle, mais l’hôte a accès à Internet", + "gwBody": "Certains routeurs ne répondent pas aux requêtes ICMP sur leur interface interne même s’ils acheminent correctement le trafic. Testez avec traceroute 8.8.8.8 depuis une console : si le deuxième saut correspond à votre FAI et que le test DNS public a réussi, la passerelle fonctionne malgré l’échec du ping.", + "dupTitle": "Le diagnostic avancé signale toujours des adresses IP en double après que j’en ai supprimé une", + "dupBody": "La vérification examine l'état du noyau en direct, et non le fichier de configuration. Exécutez ip -4 addr show pour confirmer ; si le duplicata est toujours présent, une interface le tient toujours. Utilisez ip addr del <IP>/<mask> dev <iface> pour le supprimer de l'état en direct, puis modifiez /etc/network/interfaces si elle est aussi persistante." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Outils de surveillance en direct", + "href": "/docs/network/monitoring", + "tail": "— les lanceurs interactifs iftop / iptraf-ng / iperf3." + }, + { + "label": "Analyse des ponts et réparation guidée", + "href": "/docs/network/bridge-analysis", + "tail": " — lorsqu’un vmbrX n’a plus de port physique." + }, + { + "label": "Analyse de configuration et nettoyage guidé", + "href": "/docs/network/config-analysis", + "tail": " — lorsqu’une ancienne carte réseau est toujours déclarée mais n’est plus présente." + }, + { + "label": "Sauvegarde et redémarrage des interfaces", + "href": "/docs/network/backup-restore", + "tail": " — pour créer un instantané de la configuration avant toute modification." + } + ] + } +} diff --git a/web/messages/fr/docs/network/index.json b/web/messages/fr/docs/network/index.json new file mode 100644 index 00000000..b6eaf6d6 --- /dev/null +++ b/web/messages/fr/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Gestion réseau Proxmox — Ponts, agrégations, diagnostic, réparation | ProxMenux", + "description": "Diagnostics en lecture seule, analyses suivies de suggestions et réparations guidées pour la pile réseau Debian / Proxmox (/etc/network/interfaces). Inspectez les ponts et les agrégations, lancez des outils de surveillance en direct, rendez les noms d’interface persistants, sauvegardez et redémarrez le réseau en toute sécurité — avec sauvegardes obligatoires et aperçus étape par étape.", + "ogTitle": "Gestion réseau Proxmox — Ponts, agrégations, diagnostic, réparation", + "ogDescription": "Diagnostics, analyses et réparations guidées pour la pile réseau Proxmox avec sauvegardes obligatoires et prévisualisations étape par étape.", + "twitterTitle": "Gestion réseau Proxmox | ProxMenux", + "twitterDescription": "Ponts, agrégations, diagnostics et réparations guidées pour la pile réseau Proxmox avec sauvegardes obligatoires." + }, + "header": { + "title": "Gestion du réseau", + "description": "Diagnostics en lecture seule, rapports d’analyse suivis de suggestions et réparations guidées pour la pile réseau classique Debian/Proxmox. Toute opération destructive crée d’abord une sauvegarde et affiche les modifications exactes avant de les appliquer.", + "section": "Réseau" + }, + "intro": { + "title": "À quoi sert ce menu", + "body": "Inspectez, diagnostiquez et, au besoin, réparez la configuration réseau de l’hôte Proxmox sans perdre l’accès à distance. Les outils suivent un principe : lire d’abord, proposer, puis appliquer uniquement avec un consentement explicite et une sauvegarde de sécurité. Il n’existe pas de bouton « Tout réparer automatiquement »." + }, + "openingMenu": { + "heading": "Ouverture du menu", + "intro": "Dans le menu principal de ProxMenux, sélectionnez Réseau. Vous verrez ceci :", + "imageAlt": "Menu de gestion de réseau avec diagnostic, suivi, analyse, réparation, noms persistants et options de sauvegarde" + }, + "safety": { + "heading": "Le modèle de sécurité", + "body": "Modifier la configuration réseau d’un hôte Proxmox distant est l’un des moyens les plus faciles de perdre son accès. ProxMenux en tient compte pour chaque action. Les outils se répartissent en trois niveaux de comportement : choisissez la carte correspondant à votre objectif :" + }, + "tiers": { + "readOnly": { + "title": "Lecture seule", + "body": "Contrôle pur. Impossible de modifier le système en aucune circonstance.", + "items": [ + "Tableau d'acheminement, tests de connectivité, statistiques avancées", + "Surveillance du trafic en temps réel (iftop, iptraf-ng)", + "Test de bande passante (iperf3)" + ] + }, + "analyze": { + "title": "Analysez, puis suggérez", + "body": "Détecte les problèmes, affiche un rapport avec les commandes shell proposées, puis s’arrête. Vous décidez ensuite de lancer ou non la réparation guidée.", + "items": [ + "Analyse de la configuration des ponts", + "Analyse de la configuration du réseau" + ] + }, + "apply": { + "title": "Appliquer avec sauvegarde", + "body": "Modifie le système. Crée toujours d’abord une sauvegarde horodatée du fichier concerné et affiche un aperçu avant l’écriture.", + "items": [ + "Noms d’interface persistants (fichiers .link)", + "Sauvegarde manuelle, restauration et redémarrage du service" + ] + } + }, + "classicTitle": "Pile classique seulement", + "classicBody": "Chaque fonction d'analyse et de réparation vérifie le gestionnaire de réseau actif avant de toucher quoi que ce soit. Si l'hôte exécute netplan, systemd-networkd ou NetworkManager, l'outil avorte immédiatement avec un message clair — le menu ne prend en charge que la pile classique Debian/Proxmox à /etc/network/interfaces. Ceci est intentionnel : l'édition d'un fichier netplan avec des règles écrites pour ifupdown corromprait silencieusement la configuration.", + "backups": { + "heading": "Où vont les sauvegardes", + "intro": "Chaque réparation guidée, restauration ou sauvegarde manuelle écrit une copie horodatée de /etc/network/interfaces à /var/backups/proxmenux/ :", + "rollbackIntro": "Pour revenir manuellement à la configuration précédente depuis une console :" + }, + "readOnlySection": { + "heading": "Contrôle en lecture seule", + "body": "Le point de départ lorsque quelque chose semble anormal. Inspection uniquement — n’écrit jamais dans /etc/network/interfaces et n’exécute aucune commande de modification, à une exception explicite près : la purge de NetworkManager, s’il est détecté, nécessite votre accord. Utilisable en toute sécurité via SSH à tout moment.", + "options": [ + { + "title": "Diagnostic", + "description": "Trois vérifications en lecture seule : Afficher la table d'acheminement, tester la connectivité et les diagnostics avancés. L'inspection pure n'écrit jamais au système." + }, + { + "title": "Outils de surveillance en direct", + "description": "Trois lanceurs interactifs : iftop (bande passante en temps réel par paire d'hôtes), iptraf-ng (moniteur de trafic multimode) et iperf3 (test de bande passante, serveur / mode client)." + } + ] + }, + "analyzeSection": { + "heading": "Analysez, puis suggérez", + "body": "À utiliser lorsqu’une inspection ou une panne réelle révèle un problème de configuration. Chaque outil examine la partie concernée de /etc/network/interfaces, affiche un rapport détaillé avec la commande shell exacte qui corrigerait chaque problème, puis s’arrête. Si vous acceptez ensuite la réparation guidée facultative, chaque modification est sauvegardée et prévisualisée avant d’être écrite.", + "options": [ + { + "title": "Analyse des ponts et réparation guidée", + "description": "Détecte les ponts vmbrX dont les ports sont absents ou invalides, par exemple après une réénumération PCI. Affiche d’abord un rapport et ne répare que si vous acceptez la procédure guidée en 5 étapes." + }, + { + "title": "Analyse de configuration et nettoyage guidé", + "description": "Repère les interfaces physiques déclarées dans /etc/network/interfaces qui n’existent plus, c’est-à-dire les configurations orphelines laissées par des changements matériels. Les signale et propose une suppression guidée." + } + ] + }, + "applySection": { + "heading": "Appliquer avec sauvegarde", + "body": "Ces outils écrivent sur le disque par leur fonctionnement même. Chacun crée une sauvegarde horodatée du fichier concerné avant l’écriture, et les options destructives, comme la restauration et le redémarrage, exigent une confirmation explicite oui/non. Les noms d’interface persistants prennent effet au prochain redémarrage, pas immédiatement ; leur mise en place peut donc être planifiée en toute sécurité même sur un hôte distant.", + "options": [ + { + "title": "Noms d'interface persistants", + "description": "Associe les noms d’interface (eno1, enp3s0, …) aux adresses MAC au moyen de fichiers .link de systemd. Les noms restent stables après un changement d’emplacement PCI, une mise à niveau du noyau ou l’ajout ou le retrait d’autres cartes réseau." + }, + { + "title": "Sauvegarde et redémarrage des interfaces", + "description": "Un instantané manuel de /etc/network/interfaces, parcourir et restaurer les sauvegardes précédentes, afficher la configuration en direct et redémarrer le service de réseautage au besoin." + } + ] + }, + "consoleTitle": "Préparez l'accès à la console", + "consoleSubTitle": "Avant toute réparation sur un hôte distant", + "consoleBody": "Si vous êtes connecté via SSH et ne disposez que d’un seul accès à l’hôte, prévoyez une solution de secours avant toute modification réseau : console physique / IPMI / iKVM ou autre machine sur le même réseau local. Les réparations guidées sont sûres et proposent toujours une commande de retour en arrière, mais un pont mal configuré ou une liaison interrompue peut tout de même vous priver d’accès jusqu’à ce que vous puissiez atteindre la console." +} diff --git a/web/messages/fr/docs/network/monitoring.json b/web/messages/fr/docs/network/monitoring.json new file mode 100644 index 00000000..d81420cb --- /dev/null +++ b/web/messages/fr/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Outils de surveillance en direct | Documentation ProxMenux", + "description": "Trois lanceurs interactifs de surveillance du réseau : iftop (bande passante en temps réel par paire d'hôtes), iptraf-ng (moniteur de trafic multimode) et iperf3 (test de bande passante, serveur / mode client). Chacun est installé lors de la première utilisation.", + "ogTitle": "Outils de surveillance en direct | Documentation ProxMenux", + "ogDescription": "Surveillance réseau interactive et test de bande passante pour l'hôte Proxmox : iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Outils de surveillance en direct", + "description": "Trois lanceurs interactifs pour observer le réseau en temps réel et mesurer la bande passante : iftop, iptraf-ng et iperf3. Chaque outil est installé automatiquement via apt au premier usage, fonctionne au premier plan et se quitte avec la touche indiquée. Ils ne modifient pas la configuration réseau de l’hôte ; iperf3 génère du trafic de test.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Trois outils de suivi interactifs, chacun derrière sa propre entrée de menu. La première fois que vous en lancez un, le paquet est installé silencieusement via apt-get. Les lancements suivants commencent instantanément. Aucun de ces outils ne modifie la configuration du réseau hôte." + }, + "when": { + "heading": "Quel outil utiliser selon le besoin", + "headerQuestion": "Question", + "headerUse": "Utiliser", + "rows": [ + { + "question": "Qui sature le lien maintenant ?", + "use": "iftop" + }, + { + "question": "Quelle est la répartition par protocole, quelles tailles de paquets et quels flux TCP circulent ?", + "use": "iptraf-ng" + }, + { + "question": "Combien de bande passante est disponible entre deux hôtes ?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "Utilisation du réseau en temps réel (iftop)", + "body": "Bande passante en direct par paire d’hôtes (source ↔ destination) — comme top pour le trafic. Affiche les flux les plus importants en haut, avec des moyennes glissantes sur 2/10/40 secondes. L’outil idéal pour savoir « pourquoi ma liaison montante est saturée ».", + "exit": "Quitter : appuyez sur q. ProxMenux affiche un rappel avant le lancement.", + "keysTitle": "Touches utiles dans iftop", + "keysBody": "n active ou désactive les recherches DNS, p affiche les numéros de port, P fige l’affichage, t change le sens des lignes (envoi / réception / les deux), 1/2/3 trie par moyenne sur 2 s / 10 s / 40 s. Liste complète dans man iftop." + }, + "iptraf": { + "heading": "Outil de surveillance du réseau (iptraf-ng)", + "intro": "Un outil de surveillance du trafic à plusieurs modes, piloté par menu. iftop répond à « qui », tandis qu’iptraf-ng répond à « quoi » : compteurs d’octets et de paquets par protocole, suivi de l’état des connexions TCP, histogrammes de taille des paquets et activité LAN par station.", + "menuIntro": "Au lancement, vous obtenez un menu à cinq options :", + "headerMode": "Mode", + "headerUseFor": "Utilisez-le pour", + "rows": [ + { + "mode": "Surveillance du trafic IP", + "useFor": "Liste de flux en direct TCP / UDP / ICMP / autre-IP avec compteurs d'octets et état de connexion" + }, + { + "mode": "Statistiques générales de l'interface", + "useFor": "Nombre total de paquets IPv4 / IPv6 / TCP / UDP / ICMP / non IP par carte réseau" + }, + { + "mode": "Statistiques détaillées de l'interface", + "useFor": "Comme ci-dessus, mais pour une interface, avec la taille du paquet et le détail du taux" + }, + { + "mode": "Ventilations statistiques", + "useFor": "Histogramme de distribution de la taille du paquet, par port TCP / UDP" + }, + { + "mode": "Moniteur de station LAN", + "useFor": "Statistiques du trafic par adresse MAC pour le domaine de diffusion local" + } + ], + "exit": "Quitter : appuyez sur x depuis n’importe quelle vue ou sur Q depuis le menu principal. ProxMenux affiche un rappel avant le lancement.", + "logTitle": "Enregistrer les captures dans un fichier", + "logBody": "Chaque mode propose d’enregistrer les statistiques capturées dans /var/log/iptraf-ng/. Pour conserver une trace d’un pic de trafic, laissez l’outil tourner, enregistrez dans un fichier et examinez-le ensuite plutôt que d’essayer de suivre l’écran en direct." + }, + "iperf3": { + "heading": "Test de bande passante (iperf3)", + "intro1": "Mesure le débit TCP réel entre deux hôtes. Contrairement à iftop / iptraf-ng, qui observent le trafic existant, iperf3 génère du trafic synthétique pour tester la liaison en charge. Indispensable pour répondre à « Ma liaison 10 GbE atteint-elle vraiment 10 GbE ? » ou « Le goulot d’étranglement vient-il de la carte réseau, du commutateur ou du stockage ? ».", + "intro2": "iperf3 est un outil à deux hôtes : l’un est le serveur, en écoute sur le port TCP 5201 ; l’autre est le client, qui se connecte, envoie les données et affiche le débit. Le lanceur ProxMenux demande le mode souhaité :", + "headerMode": "Mode", + "headerBehaviour": "Comportement", + "headerCli": "Commande CLI équivalente", + "rows": [ + { + "mode": "Serveur", + "behaviour": "Écoute sur TCP 5201 et imprime les résultats pour chaque test entrant. Arrête sur Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Client", + "behaviour": "Demande l’adresse IP ou le nom d’hôte du serveur, se connecte, lance le test par défaut de 10 secondes et affiche le rapport.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Typiquement pour tester un lien 10 GbE entre deux hôtes Proxmox :", + "workflow": [ + "Sur l’hôte A, ouvrez le menu Réseau → Test de bande passante (iperf3) → choisissez Serveur.", + "Sur l'hôte B, ouvrez la même entrée de menu → choisissez Client → saisissez l'IP de l'hôte A.", + "Attendez 10 secondes. Comparez le taux déclaré au maximum théorique du lien." + ], + "sample": "Exemple de sortie client :", + "flagsTitle": "Options utiles à utiliser manuellement depuis un shell", + "flagsBody": "-t 60 test plus long (60 s au lieu de 10 s), -P 4 4 flux parallèles (saturation plus rapide), -R sens inverse (serveur → client), -u -b 100M test UDP à 100 Mbit/s (mesure de la gigue et des pertes de paquets), -p 5202 autre port (plusieurs tests en parallèle). Liste complète dans man iperf3.", + "firewallTitle": "Ouvrez le port pare-feu sur le serveur", + "firewallBody": "Le serveur écoute sur TCP 5201 par défaut. Si vous exécutez le serveur à l'intérieur d'un hôte Proxmox avec un pare-feu strict (datacenter ou niveau hôte), autorisez TCP 5201 entrant à partir de l'IP du client pour la durée du test, puis fermez-le à nouveau. Il en va de même pour nftables / iptables sur les hôtes nus." + }, + "install": { + "heading": "Le premier lancement installe le paquet", + "body": "Les trois lanceurs vérifient le binaire et exécutent apt-get update -qq && apt-get install -y <pkg> s'il manque. L'installation est silencieuse : le menu peut apparaître gelé pour 10–30 secondes la première fois. Les lancements suivants commencent instantanément." + }, + "troubleshoot": { + "heading": "Dépannage", + "hangTitle": "L’installation reste bloquée au premier lancement", + "hangBody": "L'hôte n'a pas d'internet ou les repos apt sont inaccessibles. Annuler avec Ctrl+C, exécuter apt-get update manuellement pour voir l'erreur réelle (DNS, repo signature, proxy ...), puis revenir au menu.", + "refusedTitle": "Client iperf3 : « unable to connect to server: Connection refused »", + "refusedBody": "Soit le serveur ne fonctionne pas, soit son pare-feu bloque TCP 5201. Confirmer sur le serveur: ss -tlnp | grep 5201 — doit afficher l'écoute iperf3. Si l'écoute, mais que le client échoue, vérifiez le chemin du pare-feu entre les deux hôtes.", + "slowTitle": "iperf3 indique un débit bien inférieur à la vitesse attendue de la liaison", + "slowBody": "Causes courantes par ordre de probabilité : (1) le processeur d’un hôte limite le débit — essayez iperf3 -c <target> -P 4 pour utiliser plusieurs cœurs ; (2) le trajet passe par une liaison plus lente, par exemple un commutateur gigabit entre deux cartes 10 GbE ; (3) MTU différents — vérifiez ip link show aux deux extrémités ; (4) offloading de la carte réseau désactivé — voir le script communautaire Disable NIC Offloading pour une carte Intel e1000e.", + "noTrafficTitle": "iftop / iptraf-ng ne montre aucun trafic pour un hôte occupé", + "noTrafficBody": "La capture par défaut est sur la première interface détectée. Spécifiez le bon explicitement depuis le shell : iftop -i vmbr0 ou iptraf-ng -i vmbr0. Le lanceur de menu utilise la valeur par défaut; pour les interfaces non par défaut, exécuter à partir d'une console." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Diagnostic", + "href": "/docs/network/diagnostics", + "tail": "— les contrôles en lecture seule (routage, connectivité, statistiques avancées)." + }, + { + "label": "Analyse des ponts et réparation guidée", + "href": "/docs/network/bridge-analysis", + "tail": "— lorsque la surveillance révèle qu'une interface ou un pont ne fait pas ce qu'il faut." + } + ] + } +} diff --git a/web/messages/fr/docs/network/persistent-names.json b/web/messages/fr/docs/network/persistent-names.json new file mode 100644 index 00000000..26472165 --- /dev/null +++ b/web/messages/fr/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Noms d’interface persistants | Documentation ProxMenux", + "description": "Associez les noms d’interface Proxmox (eno1, enp3s0, …) aux adresses MAC via des fichiers .link de systemd pour les conserver après une réénumération PCI, une mise à niveau du noyau ou l’ajout ou le retrait d’autres cartes réseau.", + "ogTitle": "Noms d’interface persistants | Documentation ProxMenux", + "ogDescription": "Empêchez les noms d’interface Proxmox de changer lors des modifications matérielles. Utilise des fichiers .link de systemd associés aux adresses MAC." + }, + "header": { + "title": "Noms d'interface persistants", + "description": "Génère un fichier .link de systemd par carte réseau physique pour associer le nom attribué par le noyau à son adresse MAC. Après application au prochain redémarrage, les noms d’interface restent stables lorsque vous ajoutez, retirez ou déplacez des cartes PCIe.", + "section": "Réseau" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Pour chaque NIC physique sur l'hôte, écrit un petit fichier sous /etc/systemd/network/10-<iface>.link qui dit : \"le périphérique avec ce MAC doit toujours être appelé ce nom\". systemd-udevd applique la règle à chaque démarrage, avant que ifupdown ne lise /etc/network/interfaces." + }, + "problem": { + "heading": "Le problème que cela résout", + "intro": "Linux déduit les noms d’interface par défaut de la topologie PCI — eno1 = intégrée à la carte mère, enp3s0 = carte sur le bus PCI 3, emplacement 0, etc. Le nommage est déterministe pour une même disposition matérielle. Si cette disposition change, les noms changent :", + "items": [ + "Ajoutez un GPU devant un NIC existant → le numéro de bus NIC peut changer → changement de nom.", + "Déplacer une carte vers une autre fente PCIe → nom change.", + "BIOS / UEFI mise à jour qui réénumère les appareils → noms peuvent changer.", + "Remplacer une carte défectueuse par le même modèle dans une autre fente → nom change." + ], + "outro": "Après un tel changement, /etc/network/interfaces fait toujours référence à l’ancien nom ; le pont ne s’active pas et l’hôte perd le réseau. Les noms persistants évitent que cette situation se reproduise." + }, + "howWorks": { + "heading": "Comment ça marche", + "arrowLabel": "par NIC", + "nodes": { + "detectLabel": "Détecter les cartes réseau physiques", + "detectDetail": "ls /sys/class/net/\nexclure vmbr / bond /\ndocker / veth / wireguard …", + "readLabel": "Lire chaque MAC", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": "Écrivez le fichier .link", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Chaque fichier généré est volontairement minimal :", + "minimalOutro": "Au démarrage, systemd-udevd identifie le périphérique par son adresse MAC et lui attribue le nom demandé avant l’intervention d’autres composants (ifupdown, nommage par défaut du noyau). Le préfixe 10- garantit le chargement précoce de ces règles, avant le fichier par défaut 99-default.link." + }, + "scope": { + "heading": "Ce qui est écrit, ce qui est ignoré", + "headerType": "Type", + "headerBehaviour": "Comportement", + "headerWhy": "Pourquoi", + "rows": [ + { + "type": "Carte réseau intégrée / PCIe", + "behaviour": "fichier .link écrit", + "why": "Repose sur un véritable périphérique PCI — le cas auquel l’outil est destiné" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": "fichier .link écrit", + "why": "A une vraie MAC et bénéficie de la stabilité des noms" + }, + { + "type": "Ponts (vmbrX)", + "behaviour": "Ignoré", + "why": "Virtuel; le nom vient de /etc/network/interfaces" + }, + { + "type": "Agrégations (bondX)", + "behaviour": "Ignoré", + "why": "Virtuel ; le nom de l’agrégation est défini par ifupdown" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Ignoré", + "why": "Créé sur demande par Docker / LXC — matériel non persistant" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Ignoré", + "why": "Créé sur demande par Proxmox par VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Ignoré", + "why": "Interfaces logicielles gérées par leurs propres démons" + } + ] + }, + "safety": { + "heading": "Filet de sécurité : les fichiers .link précédents sont sauvegardés", + "intro": "Si /etc/systemd/network/ contient déjà des fichiers .link (à partir d'une exécution précédente ou d'un autre outil), ils sont copiés dans un répertoire de sauvegarde horodaté avant que les nouveaux fichiers ne soient générés :", + "outro": "Pour revenir en arrière : copiez les fichiers à partir du répertoire de sauvegarde et redémarrez.", + "rebootTitle": "Prend effet sur le prochain redémarrage, pas immédiatement", + "rebootBody": "Les modifications des fichiers .link ne s’appliquent qu’au démarrage, lorsque udev réénumère les périphériques. L’outil indique donc « Les modifications s’appliqueront après le redémarrage ». Renommer une interface en cours de fonctionnement est risqué et n’est pas tenté : un vmbr0 actif avec des interfaces membres devrait être reconfiguré de manière atomique. L’opération est donc reportée au prochain démarrage propre." + }, + "afterReboot": { + "heading": "Après le redémarrage", + "intro": "Une fois les noms épinglés, le flux de travail des futurs changements matériels est simple :", + "items": [ + "Éteignez, modifiez le matériel (ajout de carte, changement d’emplacement, …), puis démarrez.", + "Chaque NIC conserve son nom précédent car son MAC correspond à un fichier .link.", + "Si un NIC est remplacé (différent MAC), il obtient un nom de noyau par défaut (enp<bus>s<slot>); ré-exécutez ce menu pour ajouter une entrée .link pour le MAC de la nouvelle carte." + ] + }, + "troubleshoot": { + "heading": "Dépannage", + "emptyTitle": "\"Aucune interface physique trouvée\" après avoir lancé l'outil", + "emptyBody": "Aucune interface reposant sur PCI ou phy80211 n’est visible par le noyau sur l’hôte. Vérifiez avec ls -l /sys/class/net/ : chaque entrée doit avoir soit un lien symbolique device (PCI), soit une entrée phy80211 (Wi-Fi). Si les deux sont absents pour une véritable carte réseau, le pilote n’est pas chargé.", + "noChangeTitle": "Après le redémarrage, les noms n'ont pas changé comme prévu", + "noChangeBody": "Vérifiez que le fichier est présent et correctement formé : cat /etc/systemd/network/10-<iface>.link. Consultez ensuite les journaux udev : journalctl -u systemd-udevd -b | grep -i link. Un ancien paramètre du noyau net.ifnames=0 désactive entièrement le nommage prévisible : supprimez-le de /etc/default/grub, exécutez update-grub, puis redémarrez.", + "undoTitle": "Je veux annuler les noms persistants", + "undoBody": "Supprimez les fichiers .link (rm /etc/systemd/network/10-*.link) ou restaurez-les depuis le répertoire de sauvegarde créé lors de l’exécution précédente. Redémarrez pour appliquer les modifications." + }, + "related": { + "heading": "Voir aussi", + "items": [ + { + "label": "Analyse des ponts et réparation guidée", + "href": "/docs/network/bridge-analysis", + "tailRich": " — la solution de récupération lorsque les noms ont déjà changé." + }, + { + "label": "Analyse de configuration et nettoyage guidé", + "href": "/docs/network/config-analysis", + "tail": " — pour supprimer les blocs orphelins laissés par les changements de noms." + }, + { + "label": "Diagnostic", + "href": "/docs/network/diagnostics", + "tailRich": "— confirmer les nouveaux noms avec Afficher la table d'acheminement après le redémarrage." + } + ] + } +} diff --git a/web/messages/it/docs/network/backup-restore.json b/web/messages/it/docs/network/backup-restore.json new file mode 100644 index 00000000..721be2b8 --- /dev/null +++ b/web/messages/it/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Backup e riavvio delle interfacce | Documentazione ProxMenux", + "description": "Crea istantanee manuali, sfoglia e ripristina i backup di /etc/network/interfaces salvati in /var/backups/proxmenux/. Include il riavvio del servizio di rete con conferma e una vista rapida della configurazione attuale.", + "ogTitle": "Backup e riavvio delle interfacce | Documentazione ProxMenux", + "ogDescription": "Crea un’istantanea e ripristina la configurazione di rete Proxmox; riavvia il servizio di rete con conferma." + }, + "header": { + "title": "Backup e riavvio delle interfacce", + "description": "Quattro utility che ruotano intorno a /etc/network/interfaces: scattano un'istantanea manuale, visualizzano la configurazione live, ripristinano un backup precedente con un'anteprima facoltativa e riavviano il servizio di rete con esplicito consenso. Stessa directory di backup utilizzata dai flussi di riparazione guidati.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Gestisce la stessa directory di backup utilizzata dai flussi di riparazione guidati (/var/backups/proxmenux/). Consente di scattare un'istantanea aggiuntiva prima di qualsiasi cambiamento manuale, sfogliare tutti i backup esistenti e tornare a uno qualsiasi di essi — sempre con un backup pre-restore fresco preso automaticamente come una seconda rete di sicurezza." + }, + "shared": { + "heading": "La directory di backup condivisa", + "intro": "Ogni backup preso ovunque nel menu di rete atterra nello stesso posto:", + "outro": "I nomi dei file contengono data e ora, sono ordinati per data e non vengono mai sovrascritti. La directory viene creata al primo utilizzo." + }, + "show": { + "heading": "Mostra file di configurazione di rete", + "body": "Mostra il contenuto esatto di /etc/network/interfaces nel terminale. Sola lettura. Utile come controllo prima di un backup o dopo un ripristino. Non esiste uno strumento separato per confrontare il file con il backup precedente: esaminare il file attuale è quindi il modo più semplice per verificarne il contenuto." + }, + "create": { + "heading": "Crea backup di rete", + "body": "Copia l'attuale /etc/network/interfaces nella directory di backup con un timestamp fresco. Questo è tutto — nessuna analisi, nessun prompt, solo un'istantanea.", + "whenTitle": "Quando creare un backup manuale", + "whenBody": "Prima di modificare /etc/network/interfaces a mano, di installare un pacchetto che può alterare lo stack di rete (ad esempio NetworkManager o Open vSwitch) o di cambiare l’hardware. Le riparazioni guidate creano già un’istantanea automaticamente: questa opzione serve per gli interventi manuali." + }, + "restore": { + "heading": "Ripristina backup di rete", + "intro": "Elenca tutti i backup in /var/backups/proxmenux/ dal più recente al più vecchio e guida un ripristino protetto:", + "steps": [ + { + "title": "1. Scegli un backup", + "body": "Un menu elenca ogni backup dal suo timestamp. Se non esistono backup, il flusso esce con un messaggio chiaro.", + "tone": "blue" + }, + { + "title": "2. Anteprima opzionale", + "body": "Propone di aprire il backup selezionato in una vista scorrevole prima di confermare il ripristino. Sì è l’opzione predefinita: non saltare questo passaggio su un host remoto.", + "tone": "blue" + }, + { + "title": "3. Backup prima del ripristino", + "body": "Prima di sovrascrivere /etc/network/interfaces, crea un altro backup dello stato attuale. Il ripristino è esso stesso un’operazione distruttiva: la nuova istantanea precedente al ripristino permette di tornare indietro se il backup scelto era quello sbagliato.", + "tone": "amber" + }, + { + "title": "4. Applicare il ripristino", + "body": "Copia il backup scelto su /etc/network/interfaces. La modifica del file è su disco; lo stato del kernel live riflette ancora la configurazione precedente.", + "tone": "amber" + }, + { + "title": "5. Riavvio opzionale", + "body": "Chiede se eseguire systemctl restart networking ora. Rifiuta per rimandare l’applicazione al prossimo riavvio.", + "tone": "emerald" + } + ], + "autoBackupTitle": "Il ripristino crea automaticamente un proprio backup", + "autoBackupBody": "Selezionare Ripristina backup di rete crea sempre una nuova istantanea della configurazione attuale prima di sovrascriverla. Se ripristini il backup sbagliato, il file più recente in /var/backups/proxmenux/ contiene lo stato precedente." + }, + "restart": { + "heading": "Riavvia servizio di rete", + "body": "Esegue systemctl restart networking dopo una conferma sì/no. Mostra il risultato con msg_ok / msg_error. La maggior parte delle procedure di questo menu propone già un riavvio finale: questa opzione serve dopo una modifica manuale o se hai rifiutato il riavvio proposto in precedenza.", + "warnTitle": "Breve disconnessione garantita; possibile disconnessione permanente", + "warnBody": "Riavviare networking disattiva e riconfigura tutte le interfacce dichiarate in /etc/network/interfaces. Le sessioni SSH su queste interfacce si interrompono. Se la nuova configurazione non è valida, la rete non torna disponibile. Esegui questa operazione solo quando:", + "warnItems": [ + "Hai un accesso alla console / IPMI / iKVM pronto, oppure", + "Sei fisicamente accanto alla macchina, oppure", + "Hai appena ripristinato con successo una configurazione sicuramente funzionante e vuoi applicarla." + ] + }, + "manualRollback": { + "heading": "Ripristino manuale da una console", + "intro": "Se non riesci ad accedere al menu (SSH non disponibile, dialog che si arresta, …) ma puoi usare una console, il ripristino richiede due comandi:", + "outro": "È la stessa operazione eseguita dal menu, semplicemente digitata a mano. Se networking continua a non avviarsi, controlla journalctl -u networking -b per individuare l’errore di sintassi o del driver." + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "noneTitle": "\"Nessun backup trovato\" in Ripristino", + "noneBody": "La directory /var/backups/proxmenux/ non esiste o non contiene file interfaces_backup_*. Esegui Crea backup di rete almeno una volta oppure una riparazione guidata, che ne crea uno automaticamente. Verifica con ls /var/backups/proxmenux/.", + "unreachTitle": "Riavviare la rete riesce, ma l'host è ancora irraggiungibile", + "unreachBody": "La nuova configurazione è errata oppure non assegna il tuo IP di gestione. Ripristina la configurazione dalla console (vedi sopra). Poi usa l’analisi dei bridge e l’analisi della configurazione per capire il problema prima di applicarla nuovamente.", + "emptyTitle": "L'anteprima del ripristino mostra un file vuoto", + "emptyBody": "Il file di backup è vuoto (0 byte). Succede se una copia è fallita senza segnalazioni in un’esecuzione precedente. Scegli un backup più vecchio oppure esegui di nuovo Crea backup di rete adesso, se la configurazione attuale funziona." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Analisi dei bridge e riparazione guidata", + "href": "/docs/network/bridge-analysis", + "tail": "— il motivo più comune per cui un backup finisce qui." + }, + { + "label": "Analisi della configurazione e pulizia guidata", + "href": "/docs/network/config-analysis", + "tailRich": "— scrive anche a /var/backups/proxmenux/ automaticamente." + }, + { + "label": "Diagnostica", + "href": "/docs/network/diagnostics", + "tail": " — per verificare lo stato attuale dopo un ripristino." + } + ] + } +} diff --git a/web/messages/it/docs/network/bridge-analysis.json b/web/messages/it/docs/network/bridge-analysis.json new file mode 100644 index 00000000..23bf0ee1 --- /dev/null +++ b/web/messages/it/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Analisi dei bridge e riparazione guidata | Documentazione ProxMenux", + "description": "Rileva bridge vmbrX con porte fisiche mancanti o non valide, tipicamente dopo la rinumerazione PCI. Li segnala con comandi shell proposti e offre una riparazione guidata in 5 passaggi con backup obbligatorio.", + "ogTitle": "Analisi dei bridge e riparazione guidata | Documentazione ProxMenux", + "ogDescription": "Controlla e ripara le porte dei bridge Proxmox dopo modifiche hardware, con una procedura guidata che consente un ripristino sicuro." + }, + "header": { + "title": "Analisi dei bridge e riparazione guidata", + "description": "Controlla ogni bridge vmbrX dichiarato in /etc/network/interfaces, verifica che le porte fisiche esistano e propone una riparazione passo passo se mancano. L’analisi è di sola lettura; la riparazione richiede conferma, mostra un’anteprima e crea un backup.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Legge ogni bridge in /etc/network/interfaces, esamina la riga bridge-ports e verifica che ogni porta dichiarata, ad esempio enp3s0, esista sull’host. Se una porta manca, propone una sostituzione e, con consenso esplicito, applica la modifica seguendo la procedura backup, anteprima, applicazione, verifica." + }, + "when": { + "heading": "Quando hai bisogno di questo", + "intro": "Linux assegna nomi prevedibili alle interfacce in base alla topologia PCI e all’ordine degli slot. Diversi eventi possono modificarli, lasciando in /etc/network/interfaces riferimenti a nomi non più esistenti:", + "items": [ + "Aggiungere o rimuovere una scheda PCIe (scheda di rete, GPU, HBA o adattatore NVMe).", + "Spostare una scheda esistente in un'altra slot PCIe.", + "Aggiornamenti BIOS / UEFI che rinumerano i dispositivi PCI.", + "Migrare il disco di avvio su hardware diverso." + ], + "outro": "Dopo il prossimo avvio, il ponte cerca di attaccare a una porta che non esiste più, non riesce a salire, e l'host perde la sua rete. Nomi di interfaccia persistenti impedisce che questo accada di nuovo — ma se sei già bloccato fuori, questa pagina è il percorso di recupero." + }, + "bigPicture": { + "heading": "Quadro generale", + "diagram1": { + "arrowLabel": "passo 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analizzare (solo lettura)", + "bridgeDetail": "ip link show enp3s0\n→ non esiste", + "targetLabel": "Report + suggerimento", + "targetDetail": "❌ enp3s0: NON TROVATA\nSostituisci con: eno1\n(ancora nessuna modifica)" + } + }, + "diagram2": { + "arrowLabel": "applicare con il backup", + "nodes": { + "sourceLabel": "Riparazione guidata", + "sourceDetail": "1. Backup\n2. Mostra configurazione attuale\n3. Anteprima delle modifiche\n4. Applica\n5. Verifica", + "targetLabel": "/etc/network/interfaces (nuovo)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Passo 1: analisi (solo lettura)", + "intro": "Selezionando Analizza configurazione dei bridge, lo strumento si interrompe se l’host non usa lo stack classico Debian/Proxmox. Altrimenti esamina ogni bridge e mostra:", + "items": [ + "Nome del ponte, stato corrente (UP / DOWN), IP assegnato.", + "Ogni porta dichiarata e la sua effettiva presenza (ip link show <port>).", + "Per ogni porta non valida: una sostituzione proposta (la prima interfaccia fisica disponibile) più il comando esatto sed per applicarla manualmente.", + "Bridge senza alcuna riga bridge-ports e voci auto orfane senza un blocco iface corrispondente." + ], + "readonlyTitle": "Garanzia di sola lettura", + "readonlyBody": "Fino a questo punto lo script non ha eseguito un singolo comando di modifica. È possibile lasciare l'analisi aperta, copiare i comandi sed suggeriti e applicarli manualmente se si preferisce — o accettare il prompt successivo per inserire la riparazione guidata." + }, + "step2": { + "heading": "Passo 2: riparazione guidata (5 passi)", + "intro": "Solo se accetti il prompt alla fine dell'analisi. Ogni passo mostra cosa accadrà e chiederà la conferma prima di continuare.", + "steps": [ + { + "title": "1. Backup di sicurezza", + "body": "Copia /etc/network/interfaces in /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Il percorso esatto del backup viene mostrato prima e dopo la copia, insieme al comando di ripristino.", + "tone": "blue" + }, + { + "title": "2. Controlla la configurazione attuale", + "body": "Apre l’attuale /etc/network/interfaces in una finestra scorrevole per vedere esattamente cosa verrà modificato.", + "tone": "blue" + }, + { + "title": "3. Anteprima delle modifiche proposte", + "body": "Elenca esattamente i bridge interessati e le sostituzioni delle porte previste. Se non serve più alcuna riparazione perché la porta è ricomparsa tra i controlli, la procedura termina con «Nessuna modifica necessaria.»", + "tone": "blue" + }, + { + "title": "4. Applicare le modifiche", + "body": "Per ogni ponte interessato, viene eseguito sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" contro /etc/network/interfaces. Se un ponte non ha una sostituzione valida disponibile, la sostituzione è saltata e riportata nella fase 5.", + "tone": "amber" + }, + { + "title": "5. Verifica", + "body": "Rilegge il file e verifica che la nuova porta di ogni bridge esista. Mostra il comando di ripristino (cp <backup> /etc/network/interfaces). Infine propone Riavvia la rete: accetta solo se hai un accesso di riserva alla console.", + "tone": "emerald" + } + ], + "restartTitle": "Riavviare la rete ≠ annullare senza rischi", + "restartBody": "La riparazione viene scritta su disco anche se non riavvii il servizio. systemctl restart networking applica la modifica ora e può interrompere la sessione SSH se la nuova porta è errata. Se rifiuti, la modifica avrà comunque effetto al prossimo avvio: verifica prima la configurazione oppure annulla con il comando mostrato." + }, + "edits": { + "heading": "Cosa viene modificato (esattamente)", + "body": "Solo la riga bridge-ports di ogni bridge interessato. Le altre direttive (address, netmask, gateway, bridge-stp, …) restano invariate. In questa procedura lo script non crea né rimuove blocchi iface: se ne occupa Analisi della configurazione & pulizia." + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "unsupportedTitle": "L’analisi si interrompe con «Stack di rete non supportato»", + "unsupportedBody": "L'host esegue netplan, systemd-networkd o NetworkManager. Lo strumento supporta solo /etc/network/interfaces. Passare l'host al classico stack prima, o modificare la configurazione con il tooling nativo del manager (ad es. netplan apply).", + "noSuggestTitle": "Non viene mostrato alcun suggerimento per una porta non valida", + "noSuggestBody": "L’host non ha interfacce fisiche libere: sono già tutte in un altro bridge oppure non esistono. L’analisi suggerisce come alternativa bridge-ports none, così il bridge può almeno attivarsi senza una porta. Aggiungi una scheda di rete o sposta manualmente le porte tra i bridge prima di riprovare.", + "stillDownTitle": "La riparazione completa ma il ponte è ancora DOWN", + "stillDownBody": "sed ha aggiornato il file, ma systemctl restart networking è stato rifiutato. Eseguilo manualmente dopo aver verificato la nuova configurazione oppure riavvia. Se il riavvio è stato accettato e il bridge è ancora DOWN, esegui ip link show per verificare che la nuova porta esista e controlla journalctl -u networking per l’errore effettivo: cavo scollegato, collegamento non negoziato o porta già appartenente a un altro bridge.", + "lostSshTitle": "Ho perso l'accesso SSH subito dopo il riavvio della rete", + "lostSshIntro": "Utilizzare la console / IPMI / iKVM per raggiungere l'host. Ripristinare il backup:", + "lostSshOutro": "Esegui poi di nuovo l’analisi dalla console per capire perché il suggerimento non ha funzionato, in genere perché la porta sostitutiva non è collegata." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Nomi di interfaccia persistenti", + "href": "/docs/network/persistent-names", + "tail": " — associa i nomi agli indirizzi MAC per evitare che il problema si ripeta." + }, + { + "label": "Analisi della configurazione e pulizia guidata", + "href": "/docs/network/config-analysis", + "tailRich": "— per i blocchi iface orfani (l'altra metà dello stesso problema)." + }, + { + "label": "Backup e riavvio delle interfacce", + "href": "/docs/network/backup-restore", + "tail": "— snapshot manuali e browser di ripristino." + } + ] + } +} diff --git a/web/messages/it/docs/network/config-analysis.json b/web/messages/it/docs/network/config-analysis.json new file mode 100644 index 00000000..42961b94 --- /dev/null +++ b/web/messages/it/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Analisi della configurazione e pulizia guidata | Documentazione ProxMenux", + "description": "Rileva i blocchi iface in /etc/network/interfaces che fanno riferimento a schede di rete fisiche non più presenti sull’host. Li segnala e offre una rimozione guidata in 5 passaggi con backup obbligatorio e anteprima di ogni blocco.", + "ogTitle": "Analisi della configurazione e pulizia guidata | Documentazione ProxMenux", + "ogDescription": "Trovare e rimuovere i blocchi di interfaccia orfano lasciati dietro da modifiche hardware, con anteprima completa e rollback." + }, + "header": { + "title": "Analisi della configurazione e pulizia guidata", + "description": "Esamina ogni blocco iface in /etc/network/interfaces e verifica che la scheda di rete fisica corrispondente esista ancora. Segnala i blocchi orfani rimasti dopo modifiche hardware e offre una rimozione guidata con backup, anteprima e conferma per ogni blocco.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Identifica le dichiarazioni iface il cui supporto NIC non è più presente sull'host (tipico dopo aver sostituito o rimosso una scheda di rete). Poi offre una pulizia guidata che rimuove solo i blocchi orfani, con un backup, un'anteprima per sezione e una conferma finale esplicita." + }, + "differs": { + "heading": "Come differisce dall'analisi del ponte", + "headerAspect": "Aspetto", + "headerBridge": "Analisi del ponte", + "headerConfig": "Analisi della configurazione", + "rows": [ + { + "aspect": "Esamina", + "bridge": "Ponti (vmbrX) e la loro linea bridge-ports", + "config": "Ogni blocco iface, esclusi loopback, bridge e bond" + }, + { + "aspect": "Rilevamento", + "bridge": "Bridge che fanno riferimento a una porta inesistente", + "config": "Blocchi di interfaccia standalone per NIC che non esistono" + }, + { + "aspect": "Azione di riparazione", + "bridge": "Sostituisce il nome della porta in bridge-ports", + "config": "Rimuove l'intero blocco iface" + }, + { + "aspect": "Esclusi", + "bridge": "Nulla — ogni ponte viene analizzato", + "config": "Bridge (vmbrX) e bond (bondX) vengono sempre mantenuti: sono virtuali e la loro assenza durante l’avvio può essere normale" + } + ], + "outro": "In pratica si usano spesso entrambi in quest’ordine: prima analisi della configurazione per rimuovere i blocchi orfani, poi analisi dei bridge per riassegnare i bridge che dipendevano dall’interfaccia rimossa." + }, + "step1": { + "heading": "Passo 1: analisi (solo lettura)", + "intro": "Interrompe immediatamente se l'host non è sulla pila classica. Altrimenti elenca ogni interfaccia non-loopback configurata e segnala il suo stato:", + "virtTitle": "Le interfacce virtuali sono protette", + "virtBody": "Bridge (vmbrX) e bond (bondX) sono strutture virtuali. È possibile che non esistano al momento dell’ispezione, ad esempio un bridge ancora senza porte. L’analisi non propone mai di rimuoverli, ma solo le schede di rete fisiche." + }, + "step2": { + "heading": "Passo 2: pulizia guidata (5 passi)", + "intro": "Solo se accetti il prompt alla fine dell'analisi. Ogni passo richiede la conferma; la cancellazione in qualsiasi punto esce senza scrivere.", + "steps": [ + { + "title": "1. Backup di sicurezza", + "body": "Copia /etc/network/interfaces in /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Il percorso viene mostrato prima e dopo.", + "tone": "blue" + }, + { + "title": "2. Confermare la lista di rimozione", + "body": "Elenca esattamente i blocchi delle interfacce fisiche da rimuovere. Se le schede di rete riappaiono tra analisi e pulizia, ad esempio dopo aver reinserito una scheda, la procedura termina con «Nessuna pulizia necessaria.»", + "tone": "blue" + }, + { + "title": "3. Visualizza in anteprima i blocchi esatti", + "body": "Apre una vista scorrevole di ogni blocco iface che verrebbe eliminato, esattamente come appare nel file. È il momento di individuare, ad esempio, un hook up che vuoi conservare.", + "tone": "blue" + }, + { + "title": "4. Applicare la rimozione", + "body": "Per ogni interfaccia orfano, funziona sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. Questo elimina dalla linea iface corrispondente fino alla riga vuota successiva.", + "tone": "amber" + }, + { + "title": "5. Verifica", + "body": "Rilegge il file, elenca ciò che è stato rimosso e ricontrolla le restanti interfacce. Stampa il comando di rollback. non riavvia automaticamente la rete: la rimozione di un blocco inutilizzato è sicura da applicare al riavvio successivo ed evita di toccare lo stato live.", + "tone": "emerald" + } + ], + "noRestartTitle": "Non riavvia automaticamente la rete", + "noRestartBody": "A differenza della riparazione del ponte, il flusso di pulizia non offre di riavviare il servizio. Rimuovere un blocco di interfaccia non utilizzato non ha alcun effetto immediato sulla configurazione in esecuzione, quindi un riavvio è inutile e sarebbe un rischio di connettività inutile. La modifica ha effetto sul successivo manuale systemctl restart networking o al successivo riavvio." + }, + "caveats": { + "heading": "Osservazioni importanti", + "boundaryTitle": "Il confine del blocco è la prima linea vuota", + "boundaryBody": "Il pattern sed elimina da iface <name> fino alla riga vuota successiva. Se in /etc/network/interfaces manca una riga vuota tra i blocchi, cosa rara ma possibile dopo modifiche manuali, può essere eliminato anche il blocco successivo. Per questo il passaggio 3 è obbligatorio: controlla l’anteprima prima di confermare.", + "tandemTitle": "Un blocco iface orfano usato da un bridge viene rilevato qui, non nell’analisi dei bridge", + "tandemBody": "Se vmbr0 dichiara bridge-ports enp3s0 e enp3s0 ha anche un blocco iface enp3s0 inet manual, rimuovere enp3s0 qui lascia un riferimento non valido in vmbr0. Dopo la pulizia, esegui l’analisi dei bridge per riassegnare o rimuovere la porta del bridge. Le due procedure sono pensate per essere usate insieme." + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "notFoundTitle": "L’analisi indica «NON TROVATA» per la mia nuova scheda di rete", + "notFoundBody": "Il kernel vede il dispositivo con un nome diverso da quello nel file. Esegui ip link show per trovare il nome effettivo. È proprio il caso in cui i nomi persistenti delle interfacce eviterebbero il problema in futuro.", + "tooMuchTitle": "La pulizia ha rimosso troppo (un blocco che volevo mantenere)", + "tooMuchBody": "Ripristino dal backup stampato nella fase 5:", + "tooMuchOutro": "Modificare il file a mano per aggiungere una linea vuota chiara tra i blocchi prima di eseguire la pulizia, o rimuovere il blocco problematico manualmente.", + "bridgeBreakTitle": "Un ponte smette di funzionare dopo la pulizia", + "bridgeBreakBody": "Il bridge dipendeva da una porta il cui blocco iface è stato appena rimosso. Esegui l’analisi dei bridge: il suggerimento assegnerà al bridge un’interfaccia fisica esistente." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Analisi dei bridge e riparazione guidata", + "href": "/docs/network/bridge-analysis", + "tail": "— il follow-up naturale dopo una pulizia." + }, + { + "label": "Nomi di interfaccia persistenti", + "href": "/docs/network/persistent-names", + "tail": "— blocca i blocchi orfani dall'accumulo dopo ogni cambio hardware." + }, + { + "label": "Backup e riavvio delle interfacce", + "href": "/docs/network/backup-restore", + "tail": "— snapshot manuali e browser di ripristino." + } + ] + } +} diff --git a/web/messages/it/docs/network/diagnostics.json b/web/messages/it/docs/network/diagnostics.json new file mode 100644 index 00000000..9d5f54e0 --- /dev/null +++ b/web/messages/it/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnostica | Documentazione ProxMenux", + "description": "Tre controlli diagnostici puntuali di sola lettura: Mostra tabella di routing, Test di connettività e Diagnostica avanzata. Semplice ispezione: non scrive mai in /etc/network/interfaces e non esegue comandi di modifica, con un’unica eccezione esplicita: la rimozione completa di NetworkManager, se rilevato, richiede il tuo consenso.", + "ogTitle": "Diagnostica | Documentazione ProxMenux", + "ogDescription": "Diagnostica di rete di sola lettura per l'host Proxmox: routing, raggiungibilità, statistiche avanzate." + }, + "header": { + "title": "Diagnostica", + "description": "Tre controlli puntuali di sola lettura dello stato attuale della rete: tabella di routing, test di connettività e statistiche avanzate. Nessuno scrive in /etc/network/interfaces: sono utilizzabili in sicurezza in qualsiasi momento, anche via SSH. Per il monitoraggio interattivo in tempo reale, vedi Strumenti di monitoraggio in tempo reale.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Mostra informazioni su routing, raggiungibilità e statistiche aggregate senza modificare nulla. Usalo per verificare che la rete funzioni o individuare IP duplicati. Per osservare il traffico in modo continuo, vedi Strumenti di monitoraggio in tempo reale; per visualizzare il file di configurazione grezzo, vedi Backup e riavvio delle interfacce." + }, + "routing": { + "heading": "Mostra tabella di routing", + "body": "Elenca tutte le rotte attualmente usate dal kernel (ip route show) ed evidenzia il gateway predefinito. Utile per verificare che il traffico verso Internet esca dall’interfaccia prevista e che nessuna vecchia rotta abbia la precedenza." + }, + "connectivity": { + "heading": "Test connettività", + "intro": "Invia due sonde ICMP a tre obiettivi in sequenza e segnala ciascuno. Poi viene eseguito un nslookup google.com per verificare la risoluzione DNS indipendentemente da ICMP.", + "headerTest": "Test", + "headerTarget": "Destinazione", + "headerConfirms": "Cosa conferma", + "rows": [ + { + "test": "DNS Google", + "target": "8.8.8.8", + "confirms": "Raggiungibilità esterna tramite un indirizzo IP anycast noto" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Seconda opzione indipendente se Google blocca ICMP" + }, + { + "test": "Gateway", + "target": "(percorso di default)", + "confirms": "Connettività L2 / L3 al router locale" + }, + { + "test": "Risoluzione DNS", + "target": "google.com", + "confirms": "Il server DNS è raggiungibile e risponde" + } + ], + "readingTitle": "Leggere il risultato", + "readingBody": "Se il test del gateway riesce ma i server DNS pubblici non rispondono, la connettività locale funziona ma non hai accesso a Internet: controlla la connessione a monte del gateway. Se la risoluzione DNS fallisce ma i ping verso 8.8.8.8 riescono, il problema è il resolver, non la rete: controlla /etc/resolv.conf." + }, + "advanced": { + "heading": "Diagnostica avanzata", + "intro": "Aggrega le statistiche dell’intera rete ed esegue una serie di controlli sui problemi comuni. Mostra il numero di connessioni attive, le porte in ascolto e il totale delle interfacce, e segnala esplicitamente due configurazioni problematiche:", + "items": [ + "NetworkManager in esecuzione su un host Proxmox. NetworkManager e ifupdown di Proxmox entrano in conflitto: entrambi tentano di gestire le interfacce, rendendo i bridge instabili o impedendone l’attivazione. Se viene rilevato, lo strumento propone una conferma interattiva per arrestarlo, disabilitarlo e rimuoverlo completamente (è l’unica diagnostica che può modificare il sistema, e solo dopo un consenso esplicito).", + "Indirizzi IP duplicati su interfacce diverse. Due interfacce con lo stesso indirizzo IPv4 causano problemi di connettività intermittenti molto difficili da diagnosticare dall’interno della VM. Rilevamento tramite ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "Il prompt NetworkManager è l'unica azione di modifica qui", + "nmBody": "Tutti gli altri controlli della diagnostica avanzata sono di sola lettura. La rimozione di NetworkManager richiede una conferma sì/no ed esegue systemctl stop / disable + apt-get purge -y network-manager. Se rifiuti, non cambia nulla." + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "gwTitle": "Il test di connettività segnala un errore del gateway, ma l’host ha accesso a Internet", + "gwBody": "Alcuni router non rispondono a ICMP sulla loro interfaccia interna anche se inoltrano correttamente il traffico. Prova traceroute 8.8.8.8 da una console: se il secondo hop è il tuo ISP e il test del DNS pubblico è riuscito, il gateway funziona nonostante il ping fallito.", + "dupTitle": "La diagnostica avanzata continua a segnalare IP duplicati dopo che ne ho rimosso uno", + "dupBody": "Il controllo guarda lo stato del kernel dal vivo, non il file di configurazione. Eseguire ip -4 addr show per confermare; se il duplicato è ancora presente, un'interfaccia lo tiene ancora. Utilizzare ip addr del <IP>/<mask> dev <iface> per rimuoverlo dallo stato in tempo reale, quindi modificare /etc/network/interfaces se è anche persistente." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Strumenti di monitoraggio dal vivo", + "href": "/docs/network/monitoring", + "tail": "— lanciatori interattivi iftop / iptraf-ng / iperf3." + }, + { + "label": "Analisi dei bridge e riparazione guidata", + "href": "/docs/network/bridge-analysis", + "tail": " — quando a un vmbrX manca la porta fisica." + }, + { + "label": "Analisi della configurazione e pulizia guidata", + "href": "/docs/network/config-analysis", + "tail": " — quando una vecchia scheda di rete è ancora dichiarata ma non è più presente." + }, + { + "label": "Backup e riavvio delle interfacce", + "href": "/docs/network/backup-restore", + "tail": " — per creare un’istantanea della configurazione prima di qualsiasi modifica." + } + ] + } +} diff --git a/web/messages/it/docs/network/index.json b/web/messages/it/docs/network/index.json new file mode 100644 index 00000000..725ea79e --- /dev/null +++ b/web/messages/it/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Gestione della rete Proxmox — Bridge, bond, diagnostica, riparazione | ProxMenux", + "description": "Diagnostica di sola lettura, analisi con suggerimenti e riparazioni guidate per lo stack di rete Debian / Proxmox (/etc/network/interfaces). Ispeziona bridge e bond, avvia strumenti di monitoraggio in tempo reale, rendi persistenti i nomi delle interfacce, crea backup e riavvia la rete in sicurezza, con backup obbligatori e anteprime passo passo.", + "ogTitle": "Gestione della rete Proxmox — Bridge, bond, diagnostica, riparazione", + "ogDescription": "Diagnostica, analisi e riparazioni guidate per lo stack di rete Proxmox con backup obbligatori e anteprime passo-passo.", + "twitterTitle": "Gestione della rete Proxmox | ProxMenux", + "twitterDescription": "Bridge, bond, diagnostica e riparazioni guidate per lo stack di rete Proxmox con backup obbligatori." + }, + "header": { + "title": "Gestione rete", + "description": "Diagnostica di sola lettura, rapporti di analisi con suggerimenti e riparazioni guidate per il classico stack di rete Debian/Proxmox. Ogni operazione distruttiva crea prima un backup e mostra in anteprima le modifiche esatte prima di applicarle.", + "section": "Rete" + }, + "intro": { + "title": "A cosa serve questo menu", + "body": "Ispeziona, diagnostica e, quando necessario, ripara la configurazione di rete dell’host Proxmox senza perdere l’accesso remoto. Gli strumenti seguono un principio: prima leggere, poi proporre e infine applicare solo con consenso esplicito e un backup di sicurezza. Non esiste un pulsante «Correggi tutto automaticamente»." + }, + "openingMenu": { + "heading": "Apertura del menu", + "intro": "Dal menu principale di ProxMenux, seleziona Rete. Vedrai questo:", + "imageAlt": "Menù Gestione rete con diagnostica, monitoraggio, analisi, riparazione, nomi persistenti e opzioni di backup" + }, + "safety": { + "heading": "Il modello di sicurezza", + "body": "Modificare la configurazione di rete di un host Proxmox remoto è uno dei modi più semplici per perdere l’accesso. ProxMenux ne tiene conto per ogni azione. Gli strumenti si dividono in tre livelli di comportamento: scegli la scheda che corrisponde al tuo obiettivo:" + }, + "tiers": { + "readOnly": { + "title": "Sola lettura", + "body": "Ispezione pura. Non può modificare il sistema in nessuna circostanza.", + "items": [ + "Tabella di routing, test di connettività, statistiche avanzate", + "Monitoraggio del traffico dal vivo (iftop, iptraf-ng)", + "Test della larghezza di banda (iperf3)" + ] + }, + "analyze": { + "title": "Analizzare, quindi suggerire", + "body": "Rileva i problemi, stampa un rapporto con i comandi di shell proposti e si ferma. Si decide se entrare nella riparazione guidata dopo.", + "items": [ + "Analisi della configurazione del ponte", + "Analisi della configurazione di rete" + ] + }, + "apply": { + "title": "Applicare con backup", + "body": "Modifica il sistema. Crea sempre prima un backup del file interessato con data e ora e mostra un’anteprima prima di scrivere.", + "items": [ + "Nomi persistenti delle interfacce (file .link)", + "Backup manuale, ripristino e riavvio del servizio" + ] + } + }, + "classicTitle": "Solo pila classica", + "classicBody": "Ogni funzione di analisi e riparazione controlla il gestore di rete attivo prima di toccare qualsiasi cosa. Se l'host esegue netplan, systemd-networkd o NetworkManager, lo strumento si interrompe immediatamente con un messaggio chiaro — il menu supporta solo il classico stack Debian/Proxmox a /etc/network/interfaces. Questo è intenzionale: la modifica di un file netplan con regole scritte per ifupdown avrebbe corrotto silenziosamente la configurazione.", + "backups": { + "heading": "Dove vengono salvati i backup", + "intro": "Ogni riparazione guidata, ripristino o backup manuale scrive una copia di /etc/network/interfaces con data e ora in /var/backups/proxmenux/:", + "rollbackIntro": "Per ripristinare manualmente la configurazione precedente da una console:" + }, + "readOnlySection": { + "heading": "Ispezione di sola lettura", + "body": "Il punto di partenza quando qualcosa non va. Semplice ispezione: non scrive mai in /etc/network/interfaces e non esegue comandi che modificano il sistema, con un’unica eccezione esplicita: la rimozione completa di NetworkManager, se rilevato, richiede il tuo consenso. Utilizzabile in sicurezza via SSH in qualsiasi momento.", + "options": [ + { + "title": "Diagnostica", + "description": "Tre controlli puntuali di sola lettura: Mostra tabella di routing, Test di connettività e Diagnostica avanzata. Semplice ispezione: non scrive mai nel sistema." + }, + { + "title": "Strumenti di monitoraggio dal vivo", + "description": "Tre strumenti avviabili in modo interattivo: iftop (larghezza di banda in tempo reale per coppia di host), iptraf-ng (monitor del traffico con più modalità) e iperf3 (test della larghezza di banda, modalità server / client)." + } + ] + }, + "analyzeSection": { + "heading": "Analizzare, quindi suggerire", + "body": "Da usare quando un’ispezione o un’effettiva interruzione del servizio indica un problema di configurazione. Ogni strumento esamina la parte pertinente di /etc/network/interfaces, stampa un rapporto dettagliato con l’esatto comando shell che risolverebbe ogni problema e si ferma. Se in seguito accetti la riparazione guidata facoltativa, ogni modifica viene preceduta da un backup e mostrata in anteprima prima della scrittura.", + "options": [ + { + "title": "Analisi dei bridge e riparazione guidata", + "description": "Rileva bridge vmbrX con porte mancanti o non valide, situazione tipica dopo la rinumerazione PCI. Mostra prima un rapporto e ripara solo se accetti la procedura guidata in 5 passaggi." + }, + { + "title": "Analisi della configurazione e pulizia guidata", + "description": "Trova le interfacce fisiche dichiarate in /etc/network/interfaces che non esistono più, ossia configurazioni orfane rimaste dopo modifiche hardware. Le segnala e propone una rimozione guidata." + } + ] + }, + "applySection": { + "heading": "Applicare con backup", + "body": "Questi strumenti scrivono su disco per loro natura. Ciascuno crea un backup del file interessato con data e ora prima della scrittura e le opzioni distruttive, come ripristino e riavvio, richiedono una conferma esplicita sì/no. I nomi persistenti delle interfacce diventano effettivi al prossimo riavvio, non subito: puoi quindi pianificarne l’impostazione in sicurezza anche su un host remoto.", + "options": [ + { + "title": "Nomi di interfaccia persistenti", + "description": "Associa i nomi delle interfacce (eno1, enp3s0, …) agli indirizzi MAC tramite file .link di systemd. I nomi restano invariati dopo cambi di slot PCI, aggiornamenti del kernel e aggiunta o rimozione di altre schede di rete." + }, + { + "title": "Backup e riavvio delle interfacce", + "description": "snapshot manuale di /etc/network/interfaces, sfoglia e ripristina i backup precedenti, visualizza la configurazione live e riavvia il servizio di rete quando necessario." + } + ] + }, + "consoleTitle": "Prepara un accesso alla console", + "consoleSubTitle": "Prima di qualsiasi riparazione su un host remoto", + "consoleBody": "Se sei connesso via SSH e hai un solo percorso di accesso all’host, prepara un’alternativa prima di applicare modifiche alla rete: console fisica / IPMI / iKVM oppure un’altra macchina sulla stessa LAN. Le riparazioni guidate sono sicure e offrono sempre un comando per annullare le modifiche, ma un bridge configurato male o una connessione interrotta può comunque lasciarti senza accesso finché non raggiungi la console." +} diff --git a/web/messages/it/docs/network/monitoring.json b/web/messages/it/docs/network/monitoring.json new file mode 100644 index 00000000..3474dec7 --- /dev/null +++ b/web/messages/it/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Strumenti di monitoraggio in tempo reale | Documentazione ProxMenux", + "description": "Tre strumenti interattivi di monitoraggio della rete: iftop (larghezza di banda in tempo reale per coppia di host), iptraf-ng (monitor del traffico con più modalità) e iperf3 (test della larghezza di banda, modalità server / client). Ciascuno viene installato al primo utilizzo.", + "ogTitle": "Strumenti di monitoraggio in tempo reale | Documentazione ProxMenux", + "ogDescription": "Monitoraggio interattivo della rete e test di larghezza di banda per l'host Proxmox: iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Strumenti di monitoraggio dal vivo", + "description": "Tre strumenti avviabili in modo interattivo per osservare la rete in tempo reale e misurare la larghezza di banda: iftop, iptraf-ng e iperf3. Ogni strumento viene installato automaticamente tramite apt al primo utilizzo, viene eseguito in primo piano e si chiude con il tasto indicato. Non modificano la configurazione di rete dell’host; iperf3 genera traffico di prova.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Tre strumenti di monitoraggio interattivo, ciascuno dietro la propria voce menu. La prima volta che si lancia uno, il pacchetto viene installato silenziosamente tramite apt-get. I lanci successivi iniziano immediatamente. Nessuno di questi strumenti modifica la configurazione della rete host." + }, + "when": { + "heading": "Quando usare quale", + "headerQuestion": "Domanda", + "headerUse": "Usa", + "rows": [ + { + "question": "Chi sta saturando il collegamento in questo momento?", + "use": "iftop" + }, + { + "question": "Qual è la ripartizione per protocollo e quali dimensioni dei pacchetti e flussi TCP sono presenti?", + "use": "iptraf-ng" + }, + { + "question": "Quanta larghezza di banda è effettivamente disponibile tra due host?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "Utilizzo rete in tempo reale (iftop)", + "body": "Larghezza di banda in tempo reale per coppia di host (origine ↔ destinazione), come top per il traffico. Mostra in alto i flussi più intensi con medie mobili su 2/10/40 secondi. Lo strumento ideale per rispondere a «perché il mio uplink è saturo?».", + "exit": "Esci: premi q. ProxMenux mostra un promemoria prima dell’avvio.", + "keysTitle": "Tasti utili dentro iftop", + "keysBody": "n attiva o disattiva le ricerche DNS, p mostra i numeri di porta, P mette in pausa la visualizzazione, t cambia la direzione delle righe (inviato / ricevuto / entrambi), 1/2/3 ordina per media su 2 s / 10 s / 40 s. Elenco completo in man iftop." + }, + "iptraf": { + "heading": "Strumento monitoraggio rete (iptraf-ng)", + "intro": "Un monitor del traffico con più modalità, gestito tramite menu. iftop risponde a «chi», mentre iptraf-ng risponde a «cosa»: contatori di byte e pacchetti per protocollo, monitoraggio dello stato delle connessioni TCP, istogrammi delle dimensioni dei pacchetti e attività LAN per stazione.", + "menuIntro": "Al lancio si ottiene un menu a cinque opzioni:", + "headerMode": "Modalità", + "headerUseFor": "Usalo per", + "rows": [ + { + "mode": "Monitor di traffico IP", + "useFor": "Elenco in tempo reale dei flussi TCP / UDP / ICMP / altri protocolli IP con contatori di byte e stato della connessione" + }, + { + "mode": "Statistiche generali delle interfacce", + "useFor": "Conteggi aggregati dei pacchetti IPv4 / IPv6 / TCP / UDP / ICMP / non IP per scheda di rete" + }, + { + "mode": "Statistiche dettagliate dell’interfaccia", + "useFor": "Come sopra, ma per una sola interfaccia, con dettagli sulle dimensioni e sulla frequenza dei pacchetti" + }, + { + "mode": "Ripartizioni statistiche", + "useFor": "Istogramma della distribuzione delle dimensioni dei pacchetti, per porta TCP / UDP" + }, + { + "mode": "Monitor della stazione LAN", + "useFor": "Per-MAC statistiche del traffico per il dominio broadcast locale" + } + ], + "exit": "Esci: premi x da qualsiasi vista oppure Q dal menu principale. ProxMenux mostra un promemoria prima dell’avvio.", + "logTitle": "Salvataggio delle acquisizioni su file", + "logBody": "Ogni modalità offre di registrare le statistiche catturate a /var/log/iptraf-ng/. Utile se avete bisogno di un record di un picco di traffico — lasciare in esecuzione, registrare a file, rivedere il file dopo invece di cercare di leggere lo schermo dal vivo." + }, + "iperf3": { + "heading": "Test della larghezza di banda (iperf3)", + "intro1": "Misura la velocità TCP effettiva tra due host. A differenza di iftop / iptraf-ng, che osservano il traffico esistente, iperf3 genera traffico sintetico per una prova di carico del collegamento. Indispensabile per domande come «la mia rete 10 GbE raggiunge davvero 10 GbE?» oppure «il collo di bottiglia è la scheda di rete, lo switch o lo storage?».", + "intro2": "iperf3 è uno strumento per due host: uno funge da server e ascolta sulla porta TCP 5201; l’altro funge da client, si connette, invia dati e mostra la velocità. Il lanciatore di ProxMenux chiede quale modalità vuoi:", + "headerMode": "Modalità", + "headerBehaviour": "Comportamento", + "headerCli": "Comando CLI equivalente", + "rows": [ + { + "mode": "Server", + "behaviour": "Ascolta sulla porta TCP 5201 e mostra i risultati di ogni test in arrivo. Si arresta con Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Cliente", + "behaviour": "Chiede l’indirizzo IP o il nome host del server, si connette, esegue il test predefinito di 10 secondi e mostra il rapporto.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Flusso di lavoro tipico per testare un collegamento 10 GbE tra due host Proxmox:", + "workflow": [ + "Sull’host A, apri il menu Rete → Test della larghezza di banda (iperf3) → scegli Server.", + "Su host B, apri la stessa voce del menu → scegli Client → inserisci l'IP dell'host A.", + "Attendere 10 secondi. Confrontare il tasso riportato al massimo teorico del link." + ], + "sample": "Esempio di output del client:", + "flagsTitle": "Opzioni utili da usare manualmente dalla shell", + "flagsBody": "-t 60 test più lungo (60 s invece di 10 s), -P 4 4 flussi paralleli (saturano prima il collegamento), -R direzione inversa (server → client), -u -b 100M test UDP a 100 Mbit/s (misurazione di jitter e perdita di pacchetti), -p 5202 porta diversa (più test in parallelo). Elenco completo in man iperf3.", + "firewallTitle": "Aprire la porta firewall sul server", + "firewallBody": "Il server ascolta TCP 5201 per impostazione predefinita. Se si esegue il server all'interno di un host Proxmox con un firewall rigoroso (datacenter o livello host), consentire TCP 5201 in entrata dall'IP del client per la durata del test, quindi chiudere di nuovo. Lo stesso vale per nftables / iptables su host nudi." + }, + "install": { + "heading": "Il primo lancio installa il pacchetto", + "body": "Tutti e tre i lanciatori controllano il binario ed eseguono apt-get update -qq && apt-get install -y <pkg> se manca. L'installazione è silenziosa: il menu può apparire congelato per 10–30 secondi la prima volta. I lanci successivi iniziano immediatamente." + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "hangTitle": "L’installazione resta bloccata al primo avvio", + "hangBody": "L’host non ha accesso a Internet oppure i repository apt sono irraggiungibili. Annulla con Ctrl+C, esegui apt-get update manualmente per vedere l’errore effettivo (DNS, firma del repository, proxy, …), poi torna al menu.", + "refusedTitle": "Client iperf3: «unable to connect to server: Connection refused»", + "refusedBody": "Il server non è in esecuzione oppure il suo firewall blocca TCP 5201. Verifica sul server con ss -tlnp | grep 5201: dovrebbe mostrare iperf3 in ascolto. Se è in ascolto ma il client continua a fallire, controlla i firewall lungo il percorso tra i due host.", + "slowTitle": "iperf3 mostra una velocità molto inferiore a quella prevista per il collegamento", + "slowBody": "Cause comuni in ordine di probabilità: (1) la CPU di uno degli host limita la velocità: prova iperf3 -c <target> -P 4 per usare più core; (2) il percorso passa per un collegamento più lento, come uno switch gigabit tra due schede 10 GbE; (3) MTU diversi: controlla ip link show su entrambi i lati; (4) offloading della scheda di rete disabilitato: vedi lo script della community Disable NIC Offloading se hai una scheda Intel e1000e.", + "noTrafficTitle": "iftop / iptraf-ng non mostrano traffico per un host occupato", + "noTrafficBody": "La cattura predefinita è sulla prima interfaccia rilevata. Specificare quello giusto esplicitamente dalla shell: iftop -i vmbr0 o iptraf-ng -i vmbr0. Il menu launcher utilizza il default; per le interfacce non di default, viene eseguito da una console." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Diagnostica", + "href": "/docs/network/diagnostics", + "tail": "— i controlli di sola lettura (routing, connettività, statistiche avanzate)." + }, + { + "label": "Analisi dei bridge e riparazione guidata", + "href": "/docs/network/bridge-analysis", + "tail": "— quando il monitoraggio rivela un'interfaccia o un ponte non sta facendo ciò che dovrebbe." + } + ] + } +} diff --git a/web/messages/it/docs/network/persistent-names.json b/web/messages/it/docs/network/persistent-names.json new file mode 100644 index 00000000..37fd520c --- /dev/null +++ b/web/messages/it/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Nomi di interfaccia persistenti | Documentazione ProxMenux", + "description": "Associa i nomi delle interfacce Proxmox (eno1, enp3s0, …) agli indirizzi MAC tramite file .link di systemd per mantenerli dopo la rinumerazione PCI, aggiornamenti del kernel e aggiunta o rimozione di altre schede di rete.", + "ogTitle": "Nomi di interfaccia persistenti | Documentazione ProxMenux", + "ogDescription": "Evita che i nomi delle interfacce Proxmox cambino quando modifichi l’hardware. Usa file .link di systemd basati sull’indirizzo MAC." + }, + "header": { + "title": "Nomi di interfaccia persistenti", + "description": "Genera un file .link di systemd per ogni scheda di rete fisica, associando il nome assegnato dal kernel all’indirizzo MAC della scheda. Dopo l’applicazione al prossimo riavvio, i nomi delle interfacce rimangono stabili quando aggiungi, rimuovi o sposti schede PCIe.", + "section": "Rete" + }, + "intro": { + "title": "Cosa fa", + "body": "Per ogni NIC fisico sull'host, scrive un piccolo file sotto /etc/systemd/network/10-<iface>.link che dice: \"il dispositivo con questo MAC deve sempre essere chiamato questo nome\". systemd-udevd applica la regola ad ogni avvio, prima che ifupdown legga /etc/network/interfaces." + }, + "problem": { + "heading": "Il problema che risolve", + "intro": "Linux ricava i nomi predefiniti delle interfacce dalla topologia PCI: eno1 = integrata, enp3s0 = scheda sul bus PCI 3, slot 0, ecc. Lo schema dei nomi è deterministico a parità di disposizione hardware. Se la disposizione cambia, cambiano anche i nomi:", + "items": [ + "Aggiungere un GPU di fronte a un NIC esistente → il numero di bus NIC può cambiare → cambi di nome.", + "Spostare una scheda in una diversa slot PCIe → cambia nome.", + "Aggiornamento BIOS / UEFI che rienumera i dispositivi → i nomi possono cambiare.", + "Sostituire una scheda difettosa con lo stesso modello in una slot diversa → cambia nome." + ], + "outro": "Dopo una modifica simile, /etc/network/interfaces fa ancora riferimento al nome precedente; il bridge non si attiva e l’host perde la rete. Impostare nomi persistenti impedisce che la situazione si ripeta." + }, + "howWorks": { + "heading": "Come funziona", + "arrowLabel": "per scheda di rete", + "nodes": { + "detectLabel": "Rileva le schede di rete fisiche", + "detectDetail": "ls /sys/class/net/\nescludi vmbr / bond /\ndocker / veth / wireguard …", + "readLabel": "Leggi ogni MAC", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": "Scrivi file .link", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Ogni file generato è intenzionalmente minimo:", + "minimalOutro": "All’avvio, systemd-udevd identifica il dispositivo tramite MAC e assegna il nome richiesto prima che intervengano altri componenti (ifupdown, denominazione predefinita del kernel). Il prefisso 10- fa caricare queste regole in anticipo, prima del file predefinito 99-default.link." + }, + "scope": { + "heading": "Quello che viene scritto, quello che viene saltato", + "headerType": "Tipo", + "headerBehaviour": "Comportamento", + "headerWhy": "Perché?", + "rows": [ + { + "type": "Scheda di rete integrata / PCIe", + "behaviour": "File .link scritto", + "why": "Corrisponde a un dispositivo PCI reale: il caso per cui è pensato lo strumento" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": "File .link scritto", + "why": "Ha un MAC reale e benefici dalla stabilità dei nomi" + }, + { + "type": "Ponti (vmbrX)", + "behaviour": "Ignorato", + "why": "Virtuale; il nome viene da /etc/network/interfaces" + }, + { + "type": "Bond (bondX)", + "behaviour": "Ignorato", + "why": "Virtuale; il nome del bond è impostato da ifupdown" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Ignorato", + "why": "Creato su richiesta da Docker / LXC — hardware non persistente" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Ignorato", + "why": "Creato su richiesta da Proxmox per VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Ignorato", + "why": "Interfacce software gestite dai propri demoni" + } + ] + }, + "safety": { + "heading": "Rete di sicurezza: backup dei file .link precedenti", + "intro": "Se /etc/systemd/network/ contiene già file .link da un’esecuzione precedente o da altri strumenti, questi vengono copiati in una directory di backup con data e ora prima di generare i nuovi file:", + "outro": "Per tornare indietro: copiare i file dalla directory di backup e riavviare.", + "rebootTitle": "Prende effetto sul prossimo riavvio, non immediatamente", + "rebootBody": "Le modifiche ai file .link si applicano solo all’avvio, quando udev enumera nuovamente i dispositivi. Per questo lo strumento mostra «Le modifiche saranno applicate dopo il riavvio». Rinominare un’interfaccia in uso è rischioso e non viene tentato: un vmbr0 attivo con interfacce membro andrebbe riconfigurato atomicamente, quindi l’operazione è rimandata al prossimo avvio pulito." + }, + "afterReboot": { + "heading": "Dopo il riavvio", + "intro": "Una volta che i nomi sono bloccati, il flusso di lavoro per le future modifiche hardware è semplice:", + "items": [ + "Spegni, modifica l’hardware (aggiungi una scheda, cambia slot, …), poi avvia.", + "Ogni NIC mantiene il suo nome precedente perché il suo MAC corrisponde a un file .link.", + "Se una scheda di rete viene sostituita e ha un MAC diverso, riceve un nome predefinito del kernel (enp<bus>s<slot>); esegui di nuovo questo menu per aggiungere una voce .link per il MAC della nuova scheda." + ] + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "emptyTitle": "\"Nessuna interfaccia fisica trovata\" dopo aver eseguito lo strumento", + "emptyBody": "Il kernel non vede interfacce basate su PCI / phy80211 sull’host. Verifica con ls -l /sys/class/net/: ogni voce dovrebbe avere un collegamento simbolico device (PCI) oppure una voce phy80211 (Wi-Fi). Se mancano entrambi per una scheda di rete reale, il driver non è caricato.", + "noChangeTitle": "Dopo il riavvio, i nomi non sono cambiati come previsto", + "noChangeBody": "Verifica che il file esista e sia ben formato: cat /etc/systemd/network/10-<iface>.link. Poi controlla i log di udev: journalctl -u systemd-udevd -b | grep -i link. Una causa comune è un vecchio parametro del kernel net.ifnames=0 che disabilita del tutto i nomi prevedibili: rimuovilo da /etc/default/grub, esegui update-grub e riavvia.", + "undoTitle": "Voglio annullare la denominazione persistente", + "undoBody": "Elimina i file .link (rm /etc/systemd/network/10-*.link) oppure ripristinali dalla directory di backup creata nell’esecuzione precedente. Riavvia per applicare." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "label": "Analisi dei bridge e riparazione guidata", + "href": "/docs/network/bridge-analysis", + "tailRich": " — il percorso di ripristino quando i nomi sono già cambiati." + }, + { + "label": "Analisi della configurazione e pulizia guidata", + "href": "/docs/network/config-analysis", + "tail": "— rimuovere i blocchi orfani lasciati alle spalle con i cambi di nome." + }, + { + "label": "Diagnostica", + "href": "/docs/network/diagnostics", + "tailRich": " — verifica i nuovi nomi con Mostra tabella di routing dopo il riavvio." + } + ] + } +} diff --git a/web/messages/pt/docs/network/backup-restore.json b/web/messages/pt/docs/network/backup-restore.json new file mode 100644 index 00000000..76e2b371 --- /dev/null +++ b/web/messages/pt/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Cópia de segurança e reinício das interfaces | Documentação ProxMenux", + "description": "Snapshot manual, navegar e restaurar backups /etc/network/interfaces armazenados em /var/backups/proxmenux/. Inclui uma reinicialização do serviço de rede com confirmação e uma visão rápida da configuração ao vivo.", + "ogTitle": "Cópia de segurança e reinício das interfaces | Documentação ProxMenux", + "ogDescription": "Instantâneo e restauração da configuração de rede Proxmox; reinicie o serviço de rede com confirmação." + }, + "header": { + "title": "Cópia de segurança e reinício das interfaces", + "description": "Quatro utilitários que giram em torno de /etc/network/interfaces: tirar um instantâneo manual, visualizar a configuração ao vivo, restaurar um backup anterior com visualização opcional, e reiniciar o serviço de rede com consentimento explícito. Mesmo diretório de backup usado pelos fluxos de reparo guiados.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Gere o mesmo diretório de cópias de segurança usado pelas reparações guiadas (/var/backups/proxmenux/). Permite criar uma cópia extra antes de qualquer alteração manual, consultar todas as cópias existentes e repor qualquer uma delas — sempre com uma nova cópia do estado anterior ao restauro, criada automaticamente como segunda proteção." + }, + "shared": { + "heading": "O diretório de backup compartilhado", + "intro": "Cada backup feito em qualquer lugar no menu Rede chega no mesmo lugar:", + "outro": "Os nomes dos ficheiros contêm data e hora, são ordenados por data e nunca são substituídos. O diretório é criado na primeira utilização." + }, + "show": { + "heading": "Mostrar arquivo de configuração de rede", + "body": "Imprime /etc/network/interfaces na íntegra para o terminal. Só para leitura. Útil como uma verificação de sanidade antes de fazer um backup ou depois de uma restauração — não há nenhuma ferramenta separada \"diff com backup anterior\", então olhar para o arquivo ao vivo é a maneira mais fácil de confirmar o que você tem." + }, + "create": { + "heading": "Criar backup de rede", + "body": "Copia o /etc/network/interfaces atual para o diretório de backup com um timestamp fresco. É isso — sem análise, sem prompts, apenas um instantâneo.", + "whenTitle": "Quando criar uma cópia de segurança manual", + "whenBody": "Antes de editar /etc/network/interfaces à mão, instalar um pacote que possa alterar a pilha de rede (por exemplo NetworkManager ou Open vSwitch) ou fazer qualquer alteração de hardware. As reparações guiadas já criam uma cópia automaticamente — esta opção serve para as operações manuais." + }, + "restore": { + "heading": "Restaurar backup de rede", + "intro": "Lista todas as cópias em /var/backups/proxmenux/ da mais recente à mais antiga e guia um restauro com proteções:", + "steps": [ + { + "title": "1. Escolher uma cópia de segurança", + "body": "Um menu lista cada backup pelo seu timestamp. Se não existirem backups, o fluxo sai com uma mensagem clara.", + "tone": "blue" + }, + { + "title": "2. Visualização opcional", + "body": "Propõe abrir a cópia selecionada numa vista com deslocamento antes de confirmar o restauro. A opção predefinida é Sim — não ignore este passo num host remoto.", + "tone": "blue" + }, + { + "title": "3. Cópia de segurança antes do restauro", + "body": "Antes de sobrepor /etc/network/interfaces, toma outro backup do estado atual. Restaurar um backup é em si uma ação destrutiva, então o novo instantâneo pré-restore permite que você volte se o backup escolhido acabar sendo o errado.", + "tone": "amber" + }, + { + "title": "4. Aplicar a restauração", + "body": "Substitui /etc/network/interfaces pela cópia selecionada. A alteração do ficheiro está no disco; o estado atual do kernel ainda reflete a configuração anterior.", + "tone": "amber" + }, + { + "title": "5. Reinício opcional", + "body": "Pergunta se deve executar o systemctl restart networking agora. Recusar para adiar a alteração para a próxima reinicialização.", + "tone": "emerald" + } + ], + "autoBackupTitle": "O restauro cria automaticamente a sua própria cópia de segurança", + "autoBackupBody": "Selecionar Restaurar backup de rede cria sempre uma nova cópia da configuração atual antes de a substituir. Se restaurar a cópia errada, o ficheiro mais recente em /var/backups/proxmenux/ contém o estado anterior." + }, + "restart": { + "heading": "Reinicie o serviço de rede", + "body": "Executa systemctl restart networking após uma confirmação sim/não. Relata o resultado com msg_ok / msg_error. A maioria dos fluxos neste menu oferece o seu próprio prompt de reinicialização no final; esta opção é para reiniciar após uma edição manual ou depois de recusar o reinício do fluxo antes.", + "warnTitle": "Breve desconexão garantida; desconexão permanente possível", + "warnBody": "Reiniciando networking derruba e reaplica cada interface declarada em /etc/network/interfaces. As sessões SSH hospedadas nessas interfaces caem. Se a nova configuração for inválida, a rede nunca voltará. Execute isto apenas quando:", + "warnItems": [ + "Você tem o console / IPMI / iKVM pronto, ou", + "Está fisicamente ao lado da máquina, ou", + "Você acabou de voltar com sucesso para uma configuração conhecida-boa e quer aplicá-la." + ] + }, + "manualRollback": { + "heading": "Retroceder manualmente de uma consola", + "intro": "Se você não conseguir alcançar o menu (sem SSH, a janela está a falhar, ...) mas pode chegar a uma consola, o rollback são dois comandos:", + "outro": "É a mesma operação executada pelo menu, apenas escrita à mão. Se networking continuar sem iniciar, consulte journalctl -u networking -b para identificar o erro de sintaxe ou do controlador." + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "noneTitle": "\"Nenhum backup encontrado\" na Restauração", + "noneBody": "O diretório /var/backups/proxmenux/ não existe ou não contém ficheiros interfaces_backup_*. Execute Criar backup de rede pelo menos uma vez ou faça uma reparação guiada, que cria uma cópia automaticamente. Confirme com ls /var/backups/proxmenux/.", + "unreachTitle": "Reiniciar a rede tem sucesso, mas a máquina ainda não está acessível", + "unreachBody": "A nova configuração está errada ou não atribui o seu IP de gestão. Reponha a configuração a partir da consola (ver acima). Depois use a análise de bridges e a análise de configuração para perceber o problema antes de voltar a aplicar.", + "emptyTitle": "A antevisão da restauração mostra um arquivo vazio", + "emptyBody": "O ficheiro de backup está vazio (0 bytes). Isto acontece se uma cópia falhar silenciosamente numa execução anterior. Escolha uma cópia mais antiga ou execute novamente Criar backup de rede agora, se a configuração atual funcionar." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Análise de ponte e reparo guiado", + "href": "/docs/network/bridge-analysis", + "tail": " — a razão mais comum pela qual um backup acaba aqui." + }, + { + "label": "Análise de configuração e limpeza guiada", + "href": "/docs/network/config-analysis", + "tailRich": " — escreve igualmente para o /var/backups/proxmenux/ automaticamente." + }, + { + "label": "Diagnósticos", + "href": "/docs/network/diagnostics", + "tail": " — para verificar o estado atual após um restauro." + } + ] + } +} diff --git a/web/messages/pt/docs/network/bridge-analysis.json b/web/messages/pt/docs/network/bridge-analysis.json new file mode 100644 index 00000000..f20eddca --- /dev/null +++ b/web/messages/pt/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Análise de bridges e reparação guiada | Documentação ProxMenux", + "description": "Deteta bridges vmbrX com portas físicas em falta ou inválidas, tipicamente após a reenumeração PCI. Apresenta-os com sugestões de comandos de shell e propõe uma reparação guiada em 5 passos com cópia de segurança obrigatória.", + "ogTitle": "Análise de bridges e reparação guiada | Documentação ProxMenux", + "ogDescription": "Verifique e repare portas de bridges Proxmox após alterações de hardware, com um procedimento guiado que permite reverter em segurança." + }, + "header": { + "title": "Análise de ponte e reparo guiado", + "description": "Verifica cada bridge vmbrX declarado em /etc/network/interfaces e se as suas portas físicas existem. Caso faltem, propõe uma reparação passo a passo. A análise é só de leitura; a reparação exige confirmação, apresenta uma pré-visualização e cria uma cópia de segurança.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Lê cada ponte em /etc/network/interfaces, olha para sua linha bridge-ports e verifica se cada porta declarada (por exemplo. enp3s0) realmente existe na máquina. Se falta uma porta, propõe um substituto e – com consentimento explícito – aplica a alteração com um backup, visualização, aplicação, verificação." + }, + "when": { + "heading": "Quando precisares disto", + "intro": "Linux atribui nomes de interface previsíveis de topologia PCI e ordem de fenda. Vários eventos os embaralham e deixam o /etc/network/interfaces referindo-se a nomes que já não existem:", + "items": [ + "Adicionar ou remover uma placa PCIe (placa de rede, GPU, HBA ou adaptador NVMe).", + "Mudar uma placa existente para outro slot PCIe.", + "BIOS / UEFI atualiza que re-enumeram dispositivos PCI.", + "Migrando o disco de inicialização para hardware diferente." + ], + "outro": "Após a próxima inicialização, a ponte tenta anexar a uma porta que já não existe, falha em aparecer, e o host perde sua rede. Nomes de interface persistentes impede que isso aconteça novamente — mas se você já estiver bloqueado, esta página é o caminho de recuperação." + }, + "bigPicture": { + "heading": "O quadro geral", + "diagram1": { + "arrowLabel": "passo 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analisar (somente leitura)", + "bridgeDetail": "ip link show enp3s0\n→ não existe", + "targetLabel": "Relatório + sugestão", + "targetDetail": "❌ enp3s0: NÃO ENCONTRADA\nSubstituir por: eno1\n(ainda sem alterações)" + } + }, + "diagram2": { + "arrowLabel": "aplicar com backup", + "nodes": { + "sourceLabel": "Reparação guiada", + "sourceDetail": "1. Cópia de Segurança\n2. Mostrar actual\n3. Antevisão das alterações\n4. Aplicar\n5. Verificar", + "targetLabel": "/etc/network/interfaces (novo)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Passo 1: análise (somente leitura)", + "intro": "Ao selecionar Analisar configuração de bridges, a ferramenta para imediatamente se o host não usar a pilha clássica Debian/Proxmox. Caso contrário, verifica cada bridge e apresenta:", + "items": [ + "Nome do bridge, estado atual (UP / DOWN) e IP atribuído.", + "Cada porta declarada e se existe atualmente (ip link show <port>).", + "Para cada porta inválida: uma substituição proposta (a primeira interface física disponível) mais o comando exato sed para aplicá-lo manualmente.", + "Bridges sem qualquer linha bridge-ports e entradas auto órfãs sem bloco iface correspondente." + ], + "readonlyTitle": "Garantia apenas para leitura", + "readonlyBody": "Até este ponto o script não executou um único comando modificando. Você pode deixar a análise aberta, copiar os comandos sed sugeridos e aplicá-los manualmente se preferir — ou aceitar o próximo prompt para inserir o reparo guiado." + }, + "step2": { + "heading": "Passo 2: reparação guiada (5 passos)", + "intro": "Só se inicia se aceitar a proposta no fim da análise. Cada passo mostra o que vai acontecer e pede confirmação antes de continuar.", + "steps": [ + { + "title": "1. Cópia de segurança", + "body": "Copia /etc/network/interfaces para /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. O caminho exato de backup é mostrado antes da cópia e novamente depois, com o comando rollback.", + "tone": "blue" + }, + { + "title": "2. Rever a configuração actual", + "body": "Abre o /etc/network/interfaces ao vivo numa janela de rolagem para que possa ver exactamente o que está prestes a ser alterado.", + "tone": "blue" + }, + { + "title": "3. Antever as alterações propostas", + "body": "Lista exatamente os bridges afetados e as substituições de portas previstas. Se já não for necessária reparação porque a porta reapareceu entre as verificações, o processo termina com «Não são necessárias alterações.»", + "tone": "blue" + }, + { + "title": "4. Aplicar alterações", + "body": "Para cada ponte afetada, executa sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" contra /etc/network/interfaces. Se uma ponte não tiver nenhuma substituição válida disponível, a substituição é ignorada e relatada na etapa 5.", + "tone": "amber" + }, + { + "title": "5. Verificação", + "body": "Lê novamente o ficheiro e confirma a existência da nova porta de cada bridge. Mostra o comando para reverter (cp <backup> /etc/network/interfaces). Por fim propõe Reiniciar a rede — aceite apenas se tiver acesso de reserva à consola.", + "tone": "emerald" + } + ], + "restartTitle": "Reiniciar a rede ≠ anular sem risco", + "restartBody": "A reparação é gravada no disco mesmo que não reinicie o serviço. systemctl restart networking aplica a alteração agora e pode interromper a sessão SSH se a nova porta estiver errada. Se recusar, a alteração entra na mesma em vigor no próximo arranque — confirme primeiro a configuração ou reverta com o comando apresentado." + }, + "edits": { + "heading": "O que é editado (exatamente)", + "body": "Apenas a linha bridge-ports de cada bridge afetado. As outras diretivas (address, netmask, gateway, bridge-stp, …) ficam intactas. Neste procedimento o script não cria nem remove blocos iface — isso cabe à Análise de configuração & limpeza." + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "unsupportedTitle": "A análise para com «Pilha de rede não suportada»", + "unsupportedBody": "O host usa netplan, systemd-networkd ou NetworkManager. A ferramenta só suporta /etc/network/interfaces. Mude primeiro o host para a pilha clássica ou altere a configuração com as ferramentas nativas do gestor (por exemplo netplan apply).", + "noSuggestTitle": "Nenhuma sugestão é impressa para uma porta inválida", + "noSuggestBody": "O host tem zero interfaces físicas livres (tudo já está em outra ponte ou não existe). A análise remonta a sugerir bridge-ports none para que a ponte possa pelo menos subir sem um porto. Adicionar um NIC ou migrar portas entre pontes manualmente antes de repetir.", + "stillDownTitle": "O reparo completa, mas a ponte ainda está em baixo", + "stillDownBody": "sed atualizou o arquivo, mas systemctl restart networking foi recusado. Execute-o manualmente assim que a nova configuração for verificada, ou reinicie. Se o reinício foi aceito e a ponte ainda estiver baixa, execute ip link show para confirmar a nova porta existe, em seguida, verifique journalctl -u networking para o erro real (cabo desconectado, link não negociado, porto já um membro de outra ponte).", + "lostSshTitle": "Perdi o acesso SSH logo após reiniciar a rede", + "lostSshIntro": "Use console / IPMI / iKVM para chegar ao host. Restaurar o backup:", + "lostSshOutro": "Em seguida, re-execute a análise do console para descobrir por que a sugestão não funcionou (normalmente: a porta de substituição não está realmente conectada)." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Nomes de interface persistentes", + "href": "/docs/network/persistent-names", + "tail": " — atribuir nomes aos endereços MAC para que este cenário deixe de acontecer." + }, + { + "label": "Análise de configuração e limpeza guiada", + "href": "/docs/network/config-analysis", + "tailRich": " — para os blocos iface órfãos (a outra metade do mesmo problema)." + }, + { + "label": "Cópia de segurança e reinício das interfaces", + "href": "/docs/network/backup-restore", + "tail": " — instantâneos manuais e o navegador de restauração." + } + ] + } +} diff --git a/web/messages/pt/docs/network/config-analysis.json b/web/messages/pt/docs/network/config-analysis.json new file mode 100644 index 00000000..b165f844 --- /dev/null +++ b/web/messages/pt/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Análise de configuração e limpeza guiada | Documentação ProxMenux", + "description": "Deteta blocos iface em /etc/network/interfaces que referem placas de rede físicas já ausentes do host. Apresenta-os e propõe uma remoção guiada em 5 passos com cópia de segurança obrigatória e pré-visualização de cada bloco.", + "ogTitle": "Análise de configuração e limpeza guiada | Documentação ProxMenux", + "ogDescription": "Encontrar e remover blocos de interface órfão deixados para trás por mudanças de hardware, com visualização completa e rollback." + }, + "header": { + "title": "Análise de configuração e limpeza guiada", + "description": "Examina cada bloco iface em /etc/network/interfaces e verifica se a placa de rede física correspondente ainda existe. Apresenta os blocos órfãos deixados por alterações de hardware e propõe uma remoção guiada com cópia de segurança, pré-visualização e confirmação de cada bloco.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Identifica declarações iface cuja NIC de apoio não está mais presente no host (típico após a substituição ou remoção de uma placa de rede). Em seguida, oferece uma limpeza guiada que remove apenas os blocos órfãos, com um backup, uma visualização por seção e uma confirmação final explícita." + }, + "differs": { + "heading": "Como difere da análise da ponte", + "headerAspect": "Aspecto", + "headerBridge": "Análise da ponte", + "headerConfig": "Análise de configuração", + "rows": [ + { + "aspect": "Olha para", + "bridge": "Pontes (vmbrX) e sua linha bridge-ports", + "config": "Cada bloco iface, excluindo loopback, bridges e bonds" + }, + { + "aspect": "Detecta", + "bridge": "Bridges que referem uma porta inexistente", + "config": "Blocos de interface independentes para NICs que não existem" + }, + { + "aspect": "Acção de reparação", + "bridge": "Substitui o nome do porto em bridge-ports", + "config": "Remove todo o bloco iface" + }, + { + "aspect": "Exclui", + "bridge": "Nada — cada ponte é analisada", + "config": "Bridges (vmbrX) e bonds (bondX) são sempre mantidos, pois são virtuais e a sua ausência durante o arranque pode ser normal" + } + ], + "outro": "Na prática, usam-se frequentemente ambas por esta ordem: primeiro a análise de configuração para remover blocos órfãos, depois a análise de bridges para reatribuir os bridges que dependiam da interface removida." + }, + "step1": { + "heading": "Passo 1: análise (somente leitura)", + "intro": "Aborta imediatamente se a máquina não estiver na pilha clássica. Caso contrário, lista todas as interfaces não- loopback configuradas e reporta o seu estado:", + "virtTitle": "As interfaces virtuais estão protegidas", + "virtBody": "Bridges (vmbrX) e bonds (bondX) são estruturas virtuais. Podem legitimamente não existir no momento da inspeção, como um bridge ainda sem portas. A análise nunca propõe removê-los — apenas placas de rede físicas." + }, + "step2": { + "heading": "Passo 2: limpeza guiada (5 passos)", + "intro": "Só se inicia se aceitar a proposta no fim da análise. Cada passo exige confirmação; cancelar em qualquer ponto termina sem escrever.", + "steps": [ + { + "title": "1. Cópia de segurança", + "body": "Copia /etc/network/interfaces para /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Caminho mostrado antes e depois.", + "tone": "blue" + }, + { + "title": "2. Confirmar a lista de remoção", + "body": "Lista exatamente os blocos de interfaces físicas a remover. Se as placas de rede reaparecerem entre a análise e a limpeza, por exemplo após voltar a inserir uma placa, o processo termina com «Não é necessária limpeza.»", + "tone": "blue" + }, + { + "title": "3. Visualizar o( s) bloco( s) exato( s)", + "body": "Abre uma vista com deslocamento de cada bloco iface que seria eliminado, exatamente como está no ficheiro. É a altura de identificar, por exemplo, um hook up que queira preservar.", + "tone": "blue" + }, + { + "title": "4. Aplicar remoção", + "body": "Para cada interface órfã, executa sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. Isso apaga da linha iface correspondente até a próxima linha em branco.", + "tone": "amber" + }, + { + "title": "5. Verificação", + "body": "Lê novamente o ficheiro, lista o que foi removido e verifica as interfaces restantes. Mostra o comando para reverter. Não reinicia a rede automaticamente — remover um bloco não usado pode ser aplicado em segurança no próximo reinício, evitando alterar o estado ativo.", + "tone": "emerald" + } + ], + "noRestartTitle": "Não reinicia automaticamente a rede", + "noRestartBody": "Ao contrário do reparo de ponte, o fluxo de limpeza não oferece para reiniciar o serviço. Remover um bloco de interface não utilizado não tem efeito imediato na configuração em execução, então uma reinicialização é desnecessária e seria um risco desnecessário de conectividade. A mudança produz efeito no próximo manual systemctl restart networking ou na próxima reinicialização." + }, + "caveats": { + "heading": "Advertências importantes", + "boundaryTitle": "O limite do bloco é a primeira linha em branco", + "boundaryBody": "O padrão sed elimina desde iface <name> até à próxima linha vazia. Se /etc/network/interfaces não tiver linhas vazias entre blocos, algo raro mas possível após edição manual, o bloco seguinte também pode ser eliminado. Por isso, o passo 3 é obrigatório: reveja a pré-visualização antes de confirmar.", + "tandemTitle": "Um bloco iface órfão usado por um bridge é detetado aqui, não na análise de bridges", + "tandemBody": "Se vmbr0 declarar bridge-ports enp3s0 e enp3s0 também tiver o seu bloco iface enp3s0 inet manual, remover enp3s0 aqui deixa uma referência inválida em vmbr0. Após a limpeza, execute a análise de bridges para reatribuir ou remover a porta do bridge. Os dois procedimentos foram concebidos para uso conjunto." + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "notFoundTitle": "A análise mostra o meu novo NIC como \"NÃO ENCONTRADO\"", + "notFoundBody": "O kernel vê o dispositivo com um nome diferente do que está no ficheiro. Execute ip link show para obter o nome real. É precisamente um caso em que os nomes persistentes das interfaces evitariam o problema no futuro.", + "tooMuchTitle": "A limpeza removeu demasiado, incluindo um bloco que queria manter", + "tooMuchBody": "Restaurar a partir do backup impresso na etapa 5:", + "tooMuchOutro": "Edite o arquivo manualmente para adicionar uma linha em branco clara entre os blocos antes de repetir a limpeza ou remover o bloco problemático manualmente.", + "bridgeBreakTitle": "Uma ponte pára de funcionar após a limpeza", + "bridgeBreakBody": "O bridge dependia de uma porta cujo bloco iface foi removido. Execute a análise de bridges e a sugestão associará o bridge a uma interface física existente." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Análise de ponte e reparo guiado", + "href": "/docs/network/bridge-analysis", + "tail": " — o acompanhamento natural após uma limpeza." + }, + { + "label": "Nomes de interface persistentes", + "href": "/docs/network/persistent-names", + "tail": " — impede a acumulação de blocos órfãos após cada mudança de hardware." + }, + { + "label": "Cópia de segurança e reinício das interfaces", + "href": "/docs/network/backup-restore", + "tail": " — instantâneos manuais e o navegador de restauração." + } + ] + } +} diff --git a/web/messages/pt/docs/network/diagnostics.json b/web/messages/pt/docs/network/diagnostics.json new file mode 100644 index 00000000..b7d2abd0 --- /dev/null +++ b/web/messages/pt/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnósticos | Documentação ProxMenux", + "description": "Três diagnósticos pontuais só de leitura: Mostrar tabela de encaminhamento, Testar conectividade e Diagnósticos avançados. Apenas inspeção — nunca escreve em /etc/network/interfaces nem executa comandos de alteração, com uma exceção explícita que exige a sua aceitação: purgar o NetworkManager, caso seja detetado.", + "ogTitle": "Diagnósticos | Documentação ProxMenux", + "ogDescription": "Diagnósticos de rede somente leitura para o host Proxmox: roteamento, acessibilidade, estatísticas avançadas." + }, + "header": { + "title": "Diagnósticos", + "description": "Três verificações pontuais só de leitura do estado atual da rede: tabela de encaminhamento, teste de conectividade e estatísticas avançadas. Nenhuma escreve em /etc/network/interfaces — são seguras a qualquer momento, incluindo por SSH. Para monitorização interativa em tempo real, consulte Ferramentas de monitorização em tempo real.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Apresenta informações de encaminhamento, acessibilidade e estatísticas agregadas sem modificar nada. Use para confirmar que a rede funciona ou identificar IPs duplicados. Para observar o tráfego continuamente, consulte Ferramentas de monitorização em tempo real; para ver o ficheiro de configuração em bruto, consulte Cópia de segurança e reinício das interfaces." + }, + "routing": { + "heading": "Mostrar tabela de roteamento", + "body": "Lista todas as rotas que o kernel usa atualmente (ip route show) e destaca o gateway padrão. Útil para confirmar que o tráfego para a internet sai através da interface esperada e que nenhuma rota restante está tendo precedência." + }, + "connectivity": { + "heading": "Testar conectividade", + "intro": "Envia duas sondas ICMP para três alvos em sequência e reporta cada um. Em seguida, executa um nslookup google.com para verificar a resolução DNS independentemente do ICMP.", + "headerTest": "Teste", + "headerTarget": "Alvo", + "headerConfirms": "O que confirma", + "rows": [ + { + "test": "DNS do Google", + "target": "8.8.8.8", + "confirms": "Alcance externo através de um conhecido IP anycast" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Segunda opinião independente se o Google bloquear ICMP" + }, + { + "test": "Gateway", + "target": "(rota padrão)", + "confirms": "Conectividade L2 / L3 com o roteador local" + }, + { + "test": "Resolução DNS", + "target": "google.com", + "confirms": "O servidor DNS está acessível e responde" + } + ], + "readingTitle": "Lendo o resultado", + "readingBody": "Se o teste do gateway passar mas os destinos DNS públicos falharem, tem conectividade local mas não tem Internet — verifique a ligação a montante do gateway. Se a resolução DNS falhar mas os pings para 8.8.8.8 tiverem sucesso, o problema está no resolvedor, não na rede: verifique /etc/resolv.conf." + }, + "advanced": { + "heading": "Diagnóstico Avançado", + "intro": "Agrega estatísticas em toda a rede e executa uma bateria de verificações de questões comuns. Relata as contagens de conexão ativa, portas de escuta, interfaces totais e sinaliza dois anti- padrões explicitamente:", + "items": [ + "NetworkManager em execução num host Proxmox. O NetworkManager e o ifupdown do Proxmox entram em conflito: ambos tentam gerir as interfaces, tornando os bridges instáveis ou impedindo a sua ativação. Se for detetado, a ferramenta pede confirmação interativa para o parar, desativar e purgar (é o único diagnóstico que pode modificar o sistema, e só após consentimento explícito).", + "Endereços IP duplicados em diferentes interfaces. Duas interfaces com o mesmo IPv4 causam conectividade intermitente extremamente difícil de depurar de dentro do VM. Detectado através de ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "O prompt NetworkManager é a única ação modificadora aqui", + "nmBody": "Todas as outras verificações dos diagnósticos avançados são só de leitura. A remoção do NetworkManager exige confirmação sim/não e executa systemctl stop / disable + apt-get purge -y network-manager. Se recusar, nada muda." + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "gwTitle": "Testar conectividade indica uma falha do gateway, mas o host tem acesso à Internet", + "gwBody": "Alguns routers não respondem a ICMP na interface interna mesmo quando encaminham corretamente o tráfego. Teste com traceroute 8.8.8.8 numa consola: se o segundo salto for o seu ISP e o teste de DNS público tiver passado, o gateway está a funcionar apesar do ping falhado.", + "dupTitle": "Diagnóstico avançado continua sinalizando IPs duplicados depois que eu removi um", + "dupBody": "A verificação olha para o estado do kernel ao vivo, não para o arquivo de configuração. Executar ip -4 addr show para confirmar; se a duplicata ainda estiver presente, uma interface ainda está segurando-a. Use ip addr del <IP>/<mask> dev <iface> para removê-lo do estado ao vivo, em seguida, edite /etc/network/interfaces se ele também é persistente." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Ferramentas de monitorização ao vivo", + "href": "/docs/network/monitoring", + "tail": " — lançadores interativos iftop / iptraf-ng / iperf3." + }, + { + "label": "Análise de ponte e reparo guiado", + "href": "/docs/network/bridge-analysis", + "tail": " — quando um vmbrX não tem a sua porta física." + }, + { + "label": "Análise de configuração e limpeza guiada", + "href": "/docs/network/config-analysis", + "tail": " — quando uma NIC antiga é ainda declarada, mas já não está presente." + }, + { + "label": "Cópia de segurança e reinício das interfaces", + "href": "/docs/network/backup-restore", + "tail": " — para criar uma cópia da configuração antes de qualquer alteração." + } + ] + } +} diff --git a/web/messages/pt/docs/network/index.json b/web/messages/pt/docs/network/index.json new file mode 100644 index 00000000..ab44ee7e --- /dev/null +++ b/web/messages/pt/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Gestão de rede Proxmox — Bridges, bonds, diagnóstico, reparação | ProxMenux", + "description": "Diagnósticos só de leitura, análises seguidas de sugestões e reparações guiadas para a pilha de rede Debian / Proxmox (/etc/network/interfaces). Inspecione bridges e bonds, execute ferramentas de monitorização em tempo real, torne persistentes os nomes das interfaces, crie cópias de segurança e reinicie a rede em segurança — com cópias obrigatórias e pré-visualizações passo a passo.", + "ogTitle": "Gestão de rede Proxmox — Bridges, bonds, diagnóstico, reparação", + "ogDescription": "Diagnósticos, análise e reparos guiados para a pilha de rede Proxmox com backups obrigatórios e pré-visualizações passo a passo.", + "twitterTitle": "Gestão de rede Proxmox | ProxMenux", + "twitterDescription": "Bridges, bonds, diagnósticos e reparações guiadas para a pilha de rede Proxmox com cópias de segurança obrigatórias." + }, + "header": { + "title": "Gerenciamento de rede", + "description": "Diagnósticos só de leitura, relatórios de análise com sugestões e reparações guiadas para a pilha de rede clássica Debian/Proxmox. Cada operação destrutiva cria primeiro uma cópia de segurança e mostra as alterações exatas antes de as aplicar.", + "section": "Rede" + }, + "intro": { + "title": "Para que serve este menu", + "body": "Inspecione, diagnostique e, quando necessário, repare a configuração de rede do host Proxmox sem perder o acesso remoto. As ferramentas seguem um princípio: ler primeiro, propor e só depois aplicar com consentimento explícito e uma cópia de segurança. Não existe um botão «Corrigir tudo automaticamente»." + }, + "openingMenu": { + "heading": "Abrir o menu", + "intro": "No menu principal do ProxMenux, selecione Rede. Verá o seguinte:", + "imageAlt": "Menu de gerenciamento de rede com diagnósticos, monitoramento, análise, reparo, nomes persistentes e opções de backup" + }, + "safety": { + "heading": "O modelo de segurança", + "body": "Editar a configuração de rede de um host Proxmox remoto é uma das formas mais fáceis de perder o acesso. O ProxMenux tem isso em conta em cada ação. As ferramentas dividem-se em três níveis de comportamento — escolha o cartão que corresponde ao seu objetivo:" + }, + "tiers": { + "readOnly": { + "title": "Somente leitura", + "body": "Inspecção pura. Não é possível modificar o sistema em nenhuma circunstância.", + "items": [ + "Tabela de roteamento, testes de conectividade, estatísticas avançadas", + "Monitorização do tráfego ao vivo (iftop, iptraf-ng)", + "Teste de largura de banda (iperf3)" + ] + }, + "analyze": { + "title": "Analisar, então sugerir", + "body": "Detecta problemas, imprime um relatório com comandos de shell propostos e pára. Você decide se deve entrar na reparação guiada depois.", + "items": [ + "Análise de configuração da ponte", + "Análise da configuração da rede" + ] + }, + "apply": { + "title": "Aplicar com backup", + "body": "Modifica o sistema. Cria sempre primeiro uma cópia de segurança do ficheiro afetado com data e hora e mostra uma pré-visualização antes de escrever.", + "items": [ + "Nomes de interface persistentes (ficheiros .link)", + "Backup manual, restauração e reinício do serviço" + ] + } + }, + "classicTitle": "Apenas a pilha clássica", + "classicBody": "Cada função de análise e reparo verifica o gerenciador de rede ativo antes de tocar em qualquer coisa. Se o host executar netplan, systemd-networkd ou NetworkManager, a ferramenta aborta imediatamente com uma mensagem clara — o menu só suporta a pilha Debian/Proxmox clássica no /etc/network/interfaces. Isto é intencional: editar um arquivo netplan com regras escritas para ifupdown iria silenciosamente corromper a configuração.", + "backups": { + "heading": "Para onde vão os backups", + "intro": "Cada reparação guiada, restauro ou cópia de segurança manual grava uma cópia de /etc/network/interfaces com data e hora em /var/backups/proxmenux/:", + "rollbackIntro": "Para repor manualmente a configuração anterior a partir de uma consola:" + }, + "readOnlySection": { + "heading": "Inspeção somente de leitura", + "body": "O ponto de partida quando algo parece errado. Apenas inspeção — nunca escreve em /etc/network/interfaces nem executa comandos que alterem o sistema, com uma única exceção explícita que exige a sua aceitação: purgar o NetworkManager, caso seja detetado. Pode ser usado em segurança por SSH a qualquer momento.", + "options": [ + { + "title": "Diagnósticos", + "description": "Três verificações pontuais só de leitura: Mostrar tabela de encaminhamento, Testar conectividade e Diagnósticos avançados. Apenas inspeção — nunca escreve no sistema." + }, + { + "title": "Ferramentas de monitorização ao vivo", + "description": "Três lançadores interativos: iftop (largura de banda em tempo real por par host), iptraf-ng (monitor de tráfego multimodo) e iperf3 (teste de largura de banda, servidor / modo cliente)." + } + ] + }, + "analyzeSection": { + "heading": "Analisar, então sugerir", + "body": "Usadas quando uma inspeção ou uma falha real aponta para um problema de configuração. Cada ferramenta examina a parte relevante de /etc/network/interfaces, apresenta um relatório detalhado com o comando exato de shell que corrigiria cada problema e para. Se aceitar depois a reparação guiada opcional, é criada uma cópia de segurança antes de cada alteração, que também é pré-visualizada antes da escrita.", + "options": [ + { + "title": "Análise de ponte e reparo guiado", + "description": "Deteta bridges vmbrX com portas em falta ou inválidas, algo típico após a reenumeração PCI. Mostra primeiro um relatório e só repara quando aceita o procedimento guiado de 5 passos." + }, + { + "title": "Análise de configuração e limpeza guiada", + "description": "Encontra interfaces físicas declaradas em /etc/network/interfaces que já não existem (configurações órfãs deixadas por mudanças de hardware). Comunica-os e oferece uma remoção guiada." + } + ] + }, + "applySection": { + "heading": "Aplicar com backup", + "body": "Estas ferramentas escrevem no disco por natureza. Cada uma cria primeiro uma cópia de segurança do ficheiro afetado com data e hora, e as opções destrutivas, como restauro e reinício, exigem confirmação explícita sim/não. Os nomes persistentes das interfaces entram em vigor no próximo reinício, não de imediato, pelo que a sua configuração pode ser agendada em segurança mesmo num host remoto.", + "options": [ + { + "title": "Nomes de interface persistentes", + "description": "Associa os nomes das interfaces (eno1, enp3s0, …) aos endereços MAC através de ficheiros .link do systemd. Os nomes mantêm-se após mudanças de slot PCI, atualizações do kernel e adição ou remoção de outras placas de rede." + }, + { + "title": "Cópia de segurança e reinício das interfaces", + "description": "Instantâneo manual do /etc/network/interfaces, navegar e restaurar backups anteriores, visualizar a configuração ao vivo e reiniciar o serviço de rede quando necessário." + } + ] + }, + "consoleTitle": "Tenha acesso à consola preparado", + "consoleSubTitle": "Antes de qualquer reparação em uma máquina remota", + "consoleBody": "Se estiver ligado por SSH e tiver apenas um caminho de acesso ao host, prepare uma alternativa antes de alterar a rede: consola física / IPMI / iKVM ou outra máquina na mesma LAN. As reparações guiadas são seguras e oferecem sempre um comando para reverter as alterações, mas um bridge mal configurado ou uma ligação interrompida pode deixá-lo sem acesso até conseguir chegar à consola." +} diff --git a/web/messages/pt/docs/network/monitoring.json b/web/messages/pt/docs/network/monitoring.json new file mode 100644 index 00000000..c1e8307b --- /dev/null +++ b/web/messages/pt/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Ferramentas de monitorização em tempo real | Documentação ProxMenux", + "description": "Três lançadores interativos de monitoramento de rede: iftop (largura de banda em tempo real por par host), iptraf-ng (monitor de tráfego multimodo) e iperf3 (teste de largura de banda, servidor / modo cliente). Cada um é instalado na primeira utilização.", + "ogTitle": "Ferramentas de monitorização em tempo real | Documentação ProxMenux", + "ogDescription": "Monitoramento de rede interativo e testes de largura de banda para o host Proxmox: iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Ferramentas de monitorização ao vivo", + "description": "Três ferramentas interativas para observar a rede em tempo real e medir a largura de banda: iftop, iptraf-ng e iperf3. Cada uma é instalada automaticamente através do apt na primeira utilização, corre em primeiro plano e termina com a tecla indicada. Não alteram a configuração de rede do host; o iperf3 gera tráfego de teste.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Três ferramentas interativas de monitoramento, cada uma atrás de seu próprio menu. A primeira vez que você lança um, o pacote é instalado silenciosamente via apt-get. Lançamentos subsequentes começam instantaneamente. Nenhuma destas ferramentas modifica a configuração da rede host." + }, + "when": { + "heading": "Quando utilizar qual", + "headerQuestion": "Pergunta", + "headerUse": "Usar", + "rows": [ + { + "question": "Quem está saturando o link agora?", + "use": "iftop" + }, + { + "question": "Qual é a distribuição por protocolo e que tamanhos de pacotes e fluxos TCP estão presentes?", + "use": "iptraf-ng" + }, + { + "question": "Quanta largura de banda está disponível entre duas máquinas?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "uso de rede em tempo real (iftop)", + "body": "Largura de banda em tempo real por par de hosts (origem ↔ destino) — como o top para o tráfego. Mostra os fluxos mais intensos no topo, com médias móveis de 2/10/40 segundos. A melhor ferramenta para responder a «porque está a minha ligação de saída saturada?».", + "exit": "Sair: prima q. O ProxMenux mostra um lembrete antes de iniciar.", + "keysTitle": "Teclas úteis no iftop", + "keysBody": "n alterna a consulta DNS, p mostra os números das portas, P pausa a visualização, t alterna a direção das linhas (enviado / recebido / ambos), 1/2/3 ordena pela média de 2 s / 10 s / 40 s. Lista completa em man iftop." + }, + "iptraf": { + "heading": "ferramenta de monitoramento de rede (iptraf-ng)", + "intro": "Um monitor de tráfego com vários modos, controlado por menus. O iftop responde a «quem», enquanto o iptraf-ng responde a «o quê»: contagens de bytes e pacotes por protocolo, acompanhamento do estado das ligações TCP, histogramas de tamanho dos pacotes e atividade LAN por estação.", + "menuIntro": "No lançamento você recebe um menu de cinco opções:", + "headerMode": "Modo", + "headerUseFor": "Use-o para", + "rows": [ + { + "mode": "Monitor de tráfego IP", + "useFor": "Lista em tempo real de fluxos TCP / UDP / ICMP / outros protocolos IP, com contadores de bytes e estado da ligação" + }, + { + "mode": "Estatísticas gerais da interface", + "useFor": "Contagens agregadas de pacotes IPv4 / IPv6 / TCP / UDP / ICMP / não IP por placa de rede" + }, + { + "mode": "Estatísticas de interface detalhadas", + "useFor": "O mesmo que acima, mas para uma interface, com tamanho de pacote e detalhe de taxa" + }, + { + "mode": "Desagregações estatísticas", + "useFor": "Histograma de distribuição do tamanho do pacote, por porta TCP / UDP" + }, + { + "mode": "Monitor de estação LAN", + "useFor": "Estatísticas de tráfego por MAC para o domínio de transmissão local" + } + ], + "exit": "Saída: pressione x de qualquer vista (ou Q do menu principal). O ProxMenux mostra uma janela de lembrete antes do lançamento.", + "logTitle": "Guardar capturas num ficheiro", + "logBody": "Cada modo oferece para registrar estatísticas capturadas para /var/log/iptraf-ng/. Útil se você precisar de um registro de um pico de tráfego — deixe-o em execução, faça log para arquivo, reveja o arquivo depois, em vez de tentar ler a tela ao vivo." + }, + "iperf3": { + "heading": "Teste de largura de banda (iperf3)", + "intro1": "Mede o débito TCP real entre dois hosts. Ao contrário do iftop / iptraf-ng, que observam o tráfego existente, o iperf3 gera tráfego sintético para testar a ligação sob carga. Indispensável para perguntas como «a minha ligação 10 GbE atinge realmente 10 GbE?» ou «o estrangulamento está na placa de rede, no switch ou no armazenamento?».", + "intro2": "O iperf3 é uma ferramenta para dois hosts: um funciona como servidor, à escuta na porta TCP 5201, e o outro como cliente, que se liga, envia dados e mostra o débito. O ProxMenux pergunta qual o modo pretendido:", + "headerMode": "Modo", + "headerBehaviour": "Comportamento", + "headerCli": "CLI equivalente", + "rows": [ + { + "mode": "Servidor", + "behaviour": "Escuta em TCP 5201 e imprime resultados para cada teste de entrada. Para em Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Cliente", + "behaviour": "Pede o IP ou nome do servidor, liga-se, executa o teste predefinido de 10 segundos e mostra o relatório.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Fluxo de trabalho típico para testar uma ligação 10 GbE entre duas máquinas Proxmox:", + "workflow": [ + "No host A, abra o menu Rede → Teste de largura de banda (iperf3) → escolha Servidor.", + "No host B, abra a mesma entrada de menu → escolha Cliente → introduza o IP do host A.", + "Espere 10 segundos. Compare o débito apresentado com o máximo teórico da ligação." + ], + "sample": "Exemplo de saída do cliente:", + "flagsTitle": "Opções úteis para executar manualmente numa shell", + "flagsBody": "-t 60 teste mais longo (60s em vez de 10s padrão), -P 4 4 fluxos paralelos (satura mais rápido), -R direção reversa (servidor → cliente), -u -b 100M UDP teste em 100 Mbit/s (para medições de jitter / perda de pacotes), -p 5202 usar uma porta diferente (testes múltiplos em paralelo). man iperf3 para o conjunto completo.", + "firewallTitle": "Abrir a porta de firewall no servidor", + "firewallBody": "O servidor escuta por predefinição na porta TCP 5201. Se o executar num host Proxmox com firewall restritiva, ao nível do datacenter ou do host, permita ligações TCP 5201 de entrada a partir do IP do cliente durante o teste e volte a fechar a porta no fim. O mesmo se aplica a nftables / iptables em hosts físicos." + }, + "install": { + "heading": "Primeiro lançamento instala o pacote", + "body": "Todos os três lançadores verificam o binário e executam o apt-get update -qq && apt-get install -y <pkg> se estiver em falta. A instalação é silenciosa: o menu pode aparecer congelado para 10–30 segundos na primeira vez. Lançamentos subsequentes começam instantaneamente." + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "hangTitle": "A instalação fica bloqueada na primeira execução", + "hangBody": "O host não tem Internet ou os repositórios apt estão inacessíveis. Cancele com Ctrl+C, execute apt-get update manualmente para ver o erro real (DNS, assinatura do repositório, proxy, …) e volte ao menu.", + "refusedTitle": "Cliente iperf3: «unable to connect to server: Connection refused»", + "refusedBody": "Ou o servidor não está em execução, ou seu firewall bloqueia TCP 5201. Confirme no servidor: ss -tlnp | grep 5201 — deve mostrar iperf3 ouvindo. Se ouvir mas o cliente ainda falhar, verifique o caminho do firewall entre os dois hosts.", + "slowTitle": "O iperf3 apresenta um débito muito inferior à velocidade esperada da ligação", + "slowBody": "Causas comuns por ordem de probabilidade: (1) a CPU de um host limita o débito — tente iperf3 -c <target> -P 4 para usar vários núcleos; (2) o percurso passa por uma ligação mais lenta, como um switch gigabit entre duas placas 10 GbE; (3) MTU diferentes — verifique ip link show nas duas pontas; (4) offloading da placa de rede desativado — consulte o script da comunidade Disable NIC Offloading se tiver uma placa Intel e1000e.", + "noTrafficTitle": "iftop / iptraf-ng não mostram tráfego para uma máquina ocupada", + "noTrafficBody": "A captura padrão está na primeira interface detectada. Especifique o direito explicitamente a partir do shell: iftop -i vmbr0 ou iptraf-ng -i vmbr0. O lançador de menus usa o padrão; para interfaces não padrão, execute a partir de uma consola." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Diagnósticos", + "href": "/docs/network/diagnostics", + "tail": " — as verificações pontuais só de leitura (encaminhamento, conectividade e estatísticas avançadas)." + }, + { + "label": "Análise de ponte e reparo guiado", + "href": "/docs/network/bridge-analysis", + "tail": " — quando o controlo revela que uma interface ou ponte não está a fazer o que deveria." + } + ] + } +} diff --git a/web/messages/pt/docs/network/persistent-names.json b/web/messages/pt/docs/network/persistent-names.json new file mode 100644 index 00000000..ac215469 --- /dev/null +++ b/web/messages/pt/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Nomes de interface persistentes | Documentação ProxMenux", + "description": "Associe os nomes das interfaces Proxmox (eno1, enp3s0, …) aos endereços MAC através de ficheiros .link do systemd para os manter após a reenumeração PCI, atualizações do kernel e adição ou remoção de outras placas de rede.", + "ogTitle": "Nomes de interface persistentes | Documentação ProxMenux", + "ogDescription": "Impeça que os nomes das interfaces Proxmox mudem quando altera o hardware. Usa ficheiros .link do systemd associados ao endereço MAC." + }, + "header": { + "title": "Nomes de interface persistentes", + "description": "Gera um ficheiro .link do systemd por placa de rede física para associar o nome atribuído pelo kernel ao endereço MAC da placa. Após a aplicação no próximo reinício, os nomes das interfaces mantêm-se estáveis ao adicionar, remover ou mudar placas PCIe de lugar.", + "section": "Rede" + }, + "intro": { + "title": "O que isto faz", + "body": "Para cada NIC físico no host, escreve um pequeno arquivo sob /etc/systemd/network/10-<iface>.link que diz: \"o dispositivo com este MAC deve ser sempre chamado este nome\". systemd-udevd aplica a regra em cada inicialização, antes que ifupdown leia /etc/network/interfaces." + }, + "problem": { + "heading": "O problema que isto resolve", + "intro": "O Linux deriva os nomes predefinidos das interfaces da topologia PCI — eno1 = integrada na placa principal, enp3s0 = placa no barramento PCI 3, slot 0, etc. O esquema de nomes é determinístico para a mesma disposição de hardware. Se a disposição mudar, os nomes mudam:", + "items": [ + "Adicione um GPU na frente de um NIC existente → o número de barramento NIC pode mudar → alterações de nome.", + "Mudar uma placa para outro slot PCIe → o nome muda.", + "BIOS / UEFI atualização que re-enumera dispositivos → nomes podem mudar.", + "Substituir uma placa avariada pelo mesmo modelo noutro slot → o nome muda." + ], + "outro": "Após uma alteração destas, /etc/network/interfaces continua a referir o nome antigo; o bridge não se ativa e o host perde a rede. Configurar nomes persistentes impede que este cenário se repita." + }, + "howWorks": { + "heading": "Como funciona", + "arrowLabel": "por NIC", + "nodes": { + "detectLabel": "Detetar placas de rede físicas", + "detectDetail": "ls /sys/class/net/\nexcluir vmbr / bond /\ndocker / veth / wireguard …", + "readLabel": "Ler cada MAC", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": "Gravar o ficheiro .link", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Cada arquivo gerado é intencionalmente mínimo:", + "minimalOutro": "No arranque, o systemd-udevd identifica o dispositivo pelo MAC e atribui o nome pedido antes da intervenção de outros componentes (ifupdown, nomes predefinidos do kernel). O prefixo 10- garante que estas regras carregam cedo, antes do ficheiro predefinido 99-default.link." + }, + "scope": { + "heading": "O que é escrito, o que é ignorado", + "headerType": "Tipo", + "headerBehaviour": "Comportamento", + "headerWhy": "Porquê?", + "rows": [ + { + "type": "Placa de rede integrada / PCIe", + "behaviour": "Arquivo .link escrito", + "why": "Apoiado por um dispositivo PCI real — o caso em que a ferramenta se destina" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": "Arquivo .link escrito", + "why": "Tem um MAC real e beneficia da estabilidade do nome" + }, + { + "type": "Pontes (vmbrX)", + "behaviour": "Ignorado", + "why": "Virtual; o nome vem do /etc/network/interfaces" + }, + { + "type": "Bonds (bondX)", + "behaviour": "Ignorado", + "why": "Virtual; o nome do bond é definido pelo ifupdown" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Ignorado", + "why": "Criados a pedido pelo Docker / LXC — não são hardware persistente" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Ignorado", + "why": "Criados a pedido pelo Proxmox para cada VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Ignorado", + "why": "Interfaces de software gerenciadas por seus próprios daemons" + } + ] + }, + "safety": { + "heading": "Proteção: são criadas cópias de segurança dos ficheiros .link anteriores", + "intro": "Se /etc/systemd/network/ já contiver ficheiros .link de uma execução anterior ou de outra ferramenta, são copiados para um diretório de segurança com data e hora antes de gerar os novos:", + "outro": "Para voltar atrás: copie os arquivos de volta do diretório de backup e reinicie.", + "rebootTitle": "Faz efeito na próxima reinicialização, não imediatamente", + "rebootBody": "Alterações nos arquivos .link só se aplicam no arranque, quando o udev re-enumera dispositivos. A ferramenta informa que \"Alterações serão aplicadas após reiniciar\" por esta razão. Renomear uma interface ao vivo é arriscado e não tentado: um vmbr0 ativo com membros teria que ser reconfigurado atomicamente, por isso a operação é adiada para a próxima inicialização limpa." + }, + "afterReboot": { + "heading": "Após a reinicialização", + "intro": "Uma vez que os nomes são fixados, o fluxo de trabalho para futuras mudanças de hardware é simples:", + "items": [ + "Desligue, altere o hardware (adicione uma placa, mude de slot, …) e ligue novamente.", + "Cada NIC mantém seu nome anterior porque seu MAC corresponde a um arquivo .link.", + "Se um NIC é substituído (MAC diferente), ele recebe um nome padrão do kernel (enp<bus>s<slot>); execute novamente este menu para adicionar uma entrada .link para o MAC do novo cartão." + ] + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "emptyTitle": "\"Nenhuma interface física encontrada\" após executar a ferramenta", + "emptyBody": "O kernel não vê interfaces baseadas em PCI / phy80211 no host. Confirme com ls -l /sys/class/net/ — cada entrada deve ter uma ligação simbólica device (PCI) ou uma entrada phy80211 (Wi-Fi). Se ambas faltarem numa placa de rede real, o controlador não está carregado.", + "noChangeTitle": "Após a reinicialização, os nomes não mudaram como esperado", + "noChangeBody": "Verifique se o arquivo está presente e bem formado: cat /etc/systemd/network/10-<iface>.link. Em seguida, verifique registros udev: journalctl -u systemd-udevd -b | grep -i link. Uma causa comum é um parâmetro antigo do kernel net.ifnames=0 que desativa inteiramente a nomeação previsível — remova-a do /etc/default/grub, execute o update-grub, reinicie.", + "undoTitle": "Quero anular os nomes persistentes", + "undoBody": "Ou delete os arquivos .link (rm /etc/systemd/network/10-*.link) ou restaure do diretório de backup gerado na execução anterior. Reiniciar para aplicar." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "label": "Análise de ponte e reparo guiado", + "href": "/docs/network/bridge-analysis", + "tailRich": " — o caminho de recuperação quando os nomes já mudaram." + }, + { + "label": "Análise de configuração e limpeza guiada", + "href": "/docs/network/config-analysis", + "tail": " — para remover blocos órfãos deixados pelas mudanças de nomes." + }, + { + "label": "Diagnósticos", + "href": "/docs/network/diagnostics", + "tailRich": " — confirme os novos nomes com Mostrar tabela de roteamento após reiniciar." + } + ] + } +} diff --git a/web/messages/sk/docs/network/backup-restore.json b/web/messages/sk/docs/network/backup-restore.json new file mode 100644 index 00000000..077fdfba --- /dev/null +++ b/web/messages/sk/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Záloha a reštart rozhraní | Dokumentácia ProxMenux", + "description": "Ručné vytváranie snímok, prehliadanie a obnova záloh /etc/network/interfaces uložených v /var/backups/proxmenux/. Zahŕňa reštart sieťovej služby s potvrdením a rýchle zobrazenie aktuálnej konfigurácie.", + "ogTitle": "Záloha a reštart rozhraní | Dokumentácia ProxMenux", + "ogDescription": "Vytvorte snímku a obnovte sieťovú konfiguráciu Proxmox; reštartujte sieťovú službu s potvrdením." + }, + "header": { + "title": "Záloha a reštart rozhraní", + "description": "Štyri nástroje pre /etc/network/interfaces: ručne vytvoriť snímku, zobraziť aktuálnu konfiguráciu, obnoviť predchádzajúcu zálohu s voliteľným náhľadom a reštartovať sieťovú službu s výslovným súhlasom. Používajú rovnaký záložný adresár ako opravy so sprievodcom.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Spravuje rovnaký adresár záloh ako opravy so sprievodcom (/var/backups/proxmenux/). Umožňuje vytvoriť ďalšiu snímku pred každou ručnou zmenou, prehliadať všetky existujúce zálohy a obnoviť ktorúkoľvek z nich — vždy s automaticky vytvorenou novou zálohou stavu pred obnovou ako druhou ochranou." + }, + "shared": { + "heading": "Zdieľaný adresár zálohovania", + "intro": "Každá záloha vytvorená kdekoľvek v menu Sieť sa uloží na rovnaké miesto:", + "outro": "Názvy súborov sú časovo označené, zoradené podľa dátumu a nikdy neprepísané. Adresár je vytvorený pri prvom použití." + }, + "show": { + "heading": "Zobraziť konfiguračný súbor siete", + "body": "Vypíše presný obsah /etc/network/interfaces do terminálu. Iba na čítanie. Užitočné na kontrolu pred zálohovaním alebo po obnove. Samostatný nástroj na porovnanie s predchádzajúcou zálohou neexistuje, takže najjednoduchšie je skontrolovať obsah aktuálneho súboru pohľadom." + }, + "create": { + "heading": "Vytvoriť zálohu siete", + "body": "Skopíruje aktuálny /etc/network/interfaces do adresára záloh s novou časovou pečiatkou. To je všetko — bez analýzy, bez výziev, iba snímka.", + "whenTitle": "Kedy vytvoriť ručnú zálohu", + "whenBody": "Pred ručnou úpravou /etc/network/interfaces, pred inštaláciou balíka, ktorý môže meniť sieťový stack (napríklad NetworkManager alebo Open vSwitch), alebo pred zmenami hardvéru. Opravy so sprievodcom už vytvárajú snímky automaticky — táto možnosť je určená na ručné zásahy." + }, + "restore": { + "heading": "Obnoviť sieť zo zálohy", + "intro": "Zobrazí všetky zálohy v /var/backups/proxmenux/ od najnovšej po najstaršiu a prevedie vás obnovou s bezpečnostnými opatreniami:", + "steps": [ + { + "title": "1. Vybrať zálohu", + "body": "Menu zobrazí každú zálohu podľa jej časovej pečiatky. Ak neexistujú žiadne zálohy, postup sa ukončí s jasným hlásením.", + "tone": "blue" + }, + { + "title": "2. Nepovinný náhľad", + "body": "Ponúkne otvorenie vybranej zálohy v posúvateľnom zobrazení pred potvrdením obnovy. Predvolená odpoveď je Áno — na vzdialenom hostiteľovi tento krok nevynechávajte.", + "tone": "blue" + }, + { + "title": "3. Záloha pred obnovou", + "body": "Pred prepísaním /etc/network/interfaces vytvorí ďalšiu zálohu aktuálneho stavu. Obnova je sama osebe deštruktívna akcia, preto nová snímka pred obnovou umožní návrat, ak bola vybraná záloha nesprávna.", + "tone": "amber" + }, + { + "title": "4. Použiť obnovenie", + "body": "Kopíruje vybranú zálohu cez /etc/network/interfaces. Zmena súboru je na disku; stav živého jadra stále odráža predchádzajúcu konfiguráciu.", + "tone": "amber" + }, + { + "title": "5. Nepovinný reštart", + "body": "Pýta sa, či má teraz spustiť systemctl restart networking. Odmietnutím odložíte použitie zmeny na ďalší reštart.", + "tone": "emerald" + } + ], + "autoBackupTitle": "Obnova automaticky vytvorí vlastnú zálohu", + "autoBackupBody": "Výber Obnoviť sieť zo zálohy vždy vytvorí novú snímku aktuálnej konfigurácie pred jej prepísaním. Ak obnovíte nesprávnu zálohu, najnovší súbor v /var/backups/proxmenux/ obsahuje predchádzajúci stav." + }, + "restart": { + "heading": "Reštartovať sieťovú službu", + "body": "Po potvrdení áno/nie spustí systemctl restart networking. Výsledok oznámi cez msg_ok / msg_error. Väčšina postupov v tomto menu ponúka vlastný reštart na konci. Táto možnosť slúži na reštart po ručnej úprave alebo po odmietnutí skôr ponúknutého reštartu.", + "warnTitle": "Zaručené krátke odpojenie; možné trvalé odpojenie", + "warnBody": "Reštart networking deaktivuje a znovu nastaví každé rozhranie deklarované v /etc/network/interfaces. Relácie SSH cez tieto rozhrania sa prerušia. Ak je nová konfigurácia neplatná, sieť sa neobnoví. Spustite to iba vtedy, keď:", + "warnItems": [ + "Máte konzolu / IPMI / iKVM prístup pripravený, alebo", + "Ste fyzicky vedľa stroja, alebo", + "Práve ste úspešne obnovili overenú funkčnú konfiguráciu a chcete ju použiť." + ] + }, + "manualRollback": { + "heading": "Ručné vrátenie zmien z konzoly", + "intro": "Ak nemôžete dosiahnuť menu (žiadne SSH, dialógové zrútenie, ...), ale môžete dosiahnuť konzolu, rollback je dva príkazy:", + "outro": "Ide o rovnakú operáciu, akú vykonáva menu, iba zadanú ručne. Ak sa networking stále nespustí, skontrolujte journalctl -u networking -b a zistite príčinu v syntaxi alebo ovládači." + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "noneTitle": "„Nenašli sa žiadne zálohy“ pri obnove", + "noneBody": "Adresár /var/backups/proxmenux/ neexistuje alebo neobsahuje súbory interfaces_backup_*. Aspoň raz spustite Vytvoriť zálohu siete alebo opravu so sprievodcom, ktorá vytvorí zálohu automaticky. Overte to cez ls /var/backups/proxmenux/.", + "unreachTitle": "Reštart siete prejde, ale hostiteľ je stále nedosiahnuteľný", + "unreachBody": "Nová konfigurácia je nesprávna alebo nepriraďuje vašu IP adresu na správu. Obnovte predchádzajúci stav z konzoly (pozri vyššie). Potom použite analýzu mostov a analýzu konfigurácie, aby ste pred opätovným použitím pochopili príčinu.", + "emptyTitle": "Náhľad obnovy zobrazuje prázdny súbor", + "emptyBody": "Záložný súbor je prázdny (0 bajtov). Stane sa to, ak kopírovanie počas predchádzajúceho spustenia nepozorovane zlyhalo. Vyberte staršiu zálohu alebo znovu spustite Vytvoriť zálohu siete, ak je aktuálna konfigurácia funkčná." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Analýza mostov a oprava so sprievodcom", + "href": "/docs/network/bridge-analysis", + "tail": " — Najbežnejší dôvod, prečo tu končí záloha." + }, + { + "label": "Analýza konfigurácie a čistenie so sprievodcom", + "href": "/docs/network/config-analysis", + "tailRich": " — tiež automaticky zapisuje do /var/backups/proxmenux/." + }, + { + "label": "Diagnostika", + "href": "/docs/network/diagnostics", + "tail": " — na overenie aktuálneho stavu po obnove." + } + ] + } +} diff --git a/web/messages/sk/docs/network/bridge-analysis.json b/web/messages/sk/docs/network/bridge-analysis.json new file mode 100644 index 00000000..c4f7388b --- /dev/null +++ b/web/messages/sk/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Analýza mostov a oprava so sprievodcom | Dokumentácia ProxMenux", + "description": "Zistí mosty vmbrX s chýbajúcimi alebo neplatnými fyzickými portmi, typicky po opätovnom číslovaní PCI. Nahlási ich s návrhmi príkazov shellu a ponúkne opravu so sprievodcom v 5 krokoch s povinnou zálohou.", + "ogTitle": "Analýza mostov a oprava so sprievodcom | Dokumentácia ProxMenux", + "ogDescription": "Skontrolujte a opravte porty mostov Proxmox po zmenách hardvéru pomocou postupu so sprievodcom a bezpečným vrátením zmien." + }, + "header": { + "title": "Analýza mostov a oprava so sprievodcom", + "description": "Skontroluje každý most vmbrX deklarovaný v /etc/network/interfaces, overí existenciu fyzických portov a pri chýbajúcich ponúkne opravu krok za krokom. Analýza je iba na čítanie; oprava vyžaduje súhlas, zobrazí náhľad a vytvorí zálohu.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Prečíta každý most v /etc/network/interfaces, skontroluje jeho riadok bridge-ports a overí, či každý deklarovaný port, napríklad enp3s0, existuje na hostiteľovi. Ak port chýba, navrhne náhradu a s výslovným súhlasom použije zmenu postupom záloha, náhľad, použitie, overenie." + }, + "when": { + "heading": "Keď to potrebujete", + "intro": "Linux prideľuje predvídateľné názvy rozhraní podľa topológie PCI a poradia slotov. Viaceré udalosti ich môžu zmeniť, takže /etc/network/interfaces odkazuje na názvy, ktoré už neexistujú:", + "items": [ + "Pridanie alebo odstránenie PCIe karty (NIC, GPU, a HBA, nosič NVMe).", + "Presun existujúcej karty na iný PCIe slot.", + "Aktualizácie BIOS / UEFI, ktoré znovu číslujú zariadenia PCI.", + "Migrácia zavádzacieho disku na iný hardvér." + ], + "outro": "Po ďalšom štarte sa most pokúsi pripojiť k portu, ktorý už neexistuje, neaktivuje sa a hostiteľ stratí sieťové pripojenie. Trvalé názvy rozhraní zabránia opakovaniu tohto problému. Ak ste už stratili prístup, táto stránka opisuje postup obnovy." + }, + "bigPicture": { + "heading": "Celkový prehľad", + "diagram1": { + "arrowLabel": "krok 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analyzovať (iba na čítanie)", + "bridgeDetail": "ip link show enp3s0\n→ neexistuje", + "targetLabel": "Správa + návrh", + "targetDetail": "❌ enp3s0: NENÁJDENÉ\nNahradiť za: eno1\n(zatiaľ bez zmien)" + } + }, + "diagram2": { + "arrowLabel": "aplikovať so zálohou", + "nodes": { + "sourceLabel": "Oprava so sprievodcom", + "sourceDetail": "1. Záloha\n2. Zobraziť aktuálny stav\n3. Náhľad zmien\n4. Použiť\n5. Overiť", + "targetLabel": "/etc/network/interfaces (nový)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Krok 1: analýza (iba na čítanie)", + "intro": "Po výbere Analyzovať konfiguráciu mostov sa nástroj okamžite zastaví, ak hostiteľ nepoužíva klasický stack Debian/Proxmox. Inak prejde každý most a nahlási:", + "items": [ + "Názov mosta, aktuálny stav (UP / DOWN) a pridelená IP adresa.", + "Každý deklarovaný port a či v súčasnosti existuje (ip link show <port>).", + "Pre každý neplatný port: navrhovaná náhrada (prvé dostupné fyzické rozhranie) plus presný sed príkaz na manuálne použitie.", + "Mosty bez riadka bridge-ports a osirelé položky auto bez zodpovedajúceho bloku iface." + ], + "readonlyTitle": "Záruka režimu iba na čítanie", + "readonlyBody": "Až do tohto bodu skript nespustil žiadny príkaz meniaci systém. Analýzu môžete nechať otvorenú, skopírovať navrhnuté príkazy sed a použiť ich ručne, ak to uprednostňujete. Alebo prijmite nasledujúcu výzvu na spustenie opravy so sprievodcom." + }, + "step2": { + "heading": "Krok 2: oprava so sprievodcom (5 krokov)", + "intro": "Spustí sa iba po prijatí výzvy na konci analýzy. Každý krok zobrazí plánovanú akciu a pred pokračovaním vyžiada potvrdenie.", + "steps": [ + { + "title": "1. Bezpečnostná záloha", + "body": "Skopíruje /etc/network/interfaces do /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Presnú cestu zálohy zobrazí pred kopírovaním aj po ňom spolu s príkazom na vrátenie zmien.", + "tone": "blue" + }, + { + "title": "2. Preskúmanie aktuálnej konfigurácie", + "body": "Otvorí aktuálny /etc/network/interfaces v posúvateľnom dialógu, aby ste presne videli, čo sa zmení.", + "tone": "blue" + }, + { + "title": "3. Náhľad navrhovaných zmien", + "body": "Presne vypíše dotknuté mosty a plánované náhrady portov. Ak už oprava nie je potrebná, pretože sa port medzi kontrolami znovu objavil, postup sa ukončí hlásením „Nie sú potrebné žiadne zmeny.“", + "tone": "blue" + }, + { + "title": "4. Použiť zmeny", + "body": "Pre každý postihnutý most beží sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" proti /etc/network/interfaces. Ak most nemal k dispozícii platnú náhradu, substitúcia sa preskočí a zaznamená v kroku 5.", + "tone": "amber" + }, + { + "title": "5. Overovanie", + "body": "Znovu prečíta súbor a potvrdí, že nový port každého mosta existuje. Vypíše príkaz na vrátenie zmien (cp <backup> /etc/network/interfaces). Nakoniec ponúkne Reštartovať sieť — prijmite iba vtedy, ak máte náhradný prístup cez konzolu.", + "tone": "emerald" + } + ], + "restartTitle": "Reštart siete ≠ bezpečné vrátenie zmien", + "restartBody": "Oprava sa zapíše na disk bez ohľadu na reštart služby. systemctl restart networking použije zmenu teraz, čo môže prerušiť reláciu SSH, ak je nový port nesprávny. Ak reštart odmietnete, zmena sa aj tak prejaví pri ďalšom štarte. Najprv overte novú konfiguráciu alebo ju vráťte zobrazeným príkazom." + }, + "edits": { + "heading": "Čo sa edituje (presne)", + "body": "Iba riadok bridge-ports každého dotknutého mosta. Ostatné direktívy (address, netmask, gateway, bridge-stp, …) zostávajú nezmenené. Skript v tomto postupe nevytvára ani neodstraňuje bloky iface — to je úloha analýzy konfigurácie & čistenia." + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "unsupportedTitle": "Analýza sa zastaví s hlásením „Nepodporovaný sieťový stack“", + "unsupportedBody": "Hostiteľ používa netplan, systemd-networkd alebo NetworkManager. Nástroj podporuje iba /etc/network/interfaces. Najprv prepnite hostiteľa na klasický stack alebo upravte konfiguráciu natívnymi nástrojmi správcu (napríklad netplan apply).", + "noSuggestTitle": "Pre neplatný port sa nevypíše žiadny návrh", + "noSuggestBody": "Hostiteľ nemá žiadne voľné fyzické rozhrania: všetky sú už v inom moste alebo neexistujú. Analýza preto navrhne bridge-ports none, aby sa most mohol aspoň aktivovať bez portu. Pred opätovným spustením pridajte sieťovú kartu alebo ručne presuňte porty medzi mostmi.", + "stillDownTitle": "Oprava sa skončila, ale most je stále dole", + "stillDownBody": "sed aktualizoval súbor, ale systemctl restart networking bol odmietnutý. Po overení novej konfigurácie ho spustite ručne alebo reštartujte systém. Ak bol reštart prijatý a most je stále DOWN, overte existenciu nového portu cez ip link show a skontrolujte journalctl -u networking pre skutočnú chybu: odpojený kábel, nevyjednané spojenie alebo port už patriaci do iného mosta.", + "lostSshTitle": "Stratila som prístup SSH hneď po reštarte siete", + "lostSshIntro": "Použite konzolu / IPMI / iKVM na dosiahnutie hostiteľa. Obnoviť zálohu:", + "lostSshOutro": "Potom znovu spustite analýzu z konzoly a zistite, prečo návrh nefungoval, typicky preto, že náhradný port nie je zapojený." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Trvalé názvy rozhraní", + "href": "/docs/network/persistent-names", + "tail": " — priradí názvy k MAC adresám, aby sa problém neopakoval." + }, + { + "label": "Analýza konfigurácie a čistenie so sprievodcom", + "href": "/docs/network/config-analysis", + "tailRich": " — Pre osirotené iface bloky (druhá polovica rovnakého problému)." + }, + { + "label": "Záloha a reštart rozhraní", + "href": "/docs/network/backup-restore", + "tail": " — ručné snímky a prehliadanie záloh na obnovu." + } + ] + } +} diff --git a/web/messages/sk/docs/network/config-analysis.json b/web/messages/sk/docs/network/config-analysis.json new file mode 100644 index 00000000..819ef52f --- /dev/null +++ b/web/messages/sk/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Analýza konfigurácie a čistenie so sprievodcom | Dokumentácia ProxMenux", + "description": "Zistí bloky iface v /etc/network/interfaces odkazujúce na fyzické sieťové karty, ktoré už nie sú na hostiteľovi. Nahlási ich a ponúkne odstránenie so sprievodcom v 5 krokoch s povinnou zálohou a náhľadom každého bloku.", + "ogTitle": "Analýza konfigurácie a čistenie so sprievodcom | Dokumentácia ProxMenux", + "ogDescription": "Nájdite a odstráňte osirelé bloky rozhraní, ktoré zostali po zmenách hardvéru, s úplným náhľadom a možnosťou vrátenia zmien." + }, + "header": { + "title": "Analýza konfigurácie a čistenie so sprievodcom", + "description": "Prejde každý blok iface v /etc/network/interfaces a overí, či príslušná fyzická sieťová karta stále existuje. Nahlási osirelé bloky po zmenách hardvéru a ponúkne odstránenie so sprievodcom, zálohou, náhľadom a potvrdením každého bloku.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Identifikuje deklarácie iface, ktorých sieťová karta už nie je na hostiteľovi, typicky po výmene alebo odobratí karty. Potom ponúkne čistenie so sprievodcom, ktoré odstráni iba osirelé bloky, so zálohou, náhľadom každej sekcie a výslovným záverečným potvrdením." + }, + "differs": { + "heading": "Ako sa líši od analýzy mosta", + "headerAspect": "Aspekt", + "headerBridge": "Analýza mosta", + "headerConfig": "Analýza konfigurácie", + "rows": [ + { + "aspect": "Pozerá sa na", + "bridge": "Mosty (vmbrX) a ich riadok bridge-ports", + "config": "Každý blok iface okrem loopbacku, mostov a bondov" + }, + { + "aspect": "Zisťuje", + "bridge": "Mosty odkazujúce na neexistujúci port", + "config": "Samostatné bloky rozhrania pre NIC, ktoré neexistujú" + }, + { + "aspect": "Oprava", + "bridge": "Nahrádza názov portu v bridge-ports", + "config": "Odstráni celý blok iface" + }, + { + "aspect": "Nezahŕňa", + "bridge": "Nič, každý most je analyzovaný", + "config": "Mosty (vmbrX) a bondy (bondX) sa vždy zachovajú, pretože sú virtuálne a ich neprítomnosť počas štartu môže byť normálna" + } + ], + "outro": "V praxi často použijete oba nástroje v tomto poradí: najprv analýzu konfigurácie na odstránenie osirelých blokov, potom analýzu mostov na nové priradenie mostov, ktoré používali odstránené rozhranie." + }, + "step1": { + "heading": "Krok 1: analýza (iba na čítanie)", + "intro": "Okamžite sa zastaví, ak hostiteľ nepoužíva klasický sieťový stack. Inak zobrazí všetky nakonfigurované rozhrania okrem loopbacku a ich stav:", + "virtTitle": "Virtuálne rozhrania sú chránené", + "virtBody": "Mosty (vmbrX) a bondy (bondX) sú virtuálne konštrukcie. Pri kontrole môžu oprávnene chýbať, napríklad most bez portov. Analýza preto nikdy nenavrhuje ich odstránenie, iba odstránenie fyzických sieťových kariet." + }, + "step2": { + "heading": "Krok 2: čistenie so sprievodcom (5 krokov)", + "intro": "Spustí sa iba po prijatí výzvy na konci analýzy. Každý krok vyžaduje potvrdenie; zrušením v ktoromkoľvek bode sa postup ukončí bez zápisu.", + "steps": [ + { + "title": "1. Bezpečnostná záloha", + "body": "Skopíruje /etc/network/interfaces do /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Cestu zobrazí pred kopírovaním aj po ňom.", + "tone": "blue" + }, + { + "title": "2. Potvrdenie zoznamu odstránenia", + "body": "Presne vypíše bloky fyzických rozhraní určené na odstránenie. Ak sa karty medzi analýzou a čistením znovu objavia, napríklad po opätovnom zasunutí, postup sa ukončí hlásením „Čistenie nie je potrebné.“", + "tone": "blue" + }, + { + "title": "3. Náhľad presných blokov", + "body": "Otvorí rolovací pohľad na každý blok iface, ktorý by bol vymazaný, doslovne zo súboru. Toto je chvíľa, kedy si môžete všimnúť napríklad zaujímavý up hák, ktorý chcete zachovať.", + "tone": "blue" + }, + { + "title": "4. Použiť odstránenie", + "body": "Pre každé sirotské rozhranie beží sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. To odstráni zo zodpovedajúcej línie iface až do ďalšieho prázdneho riadku.", + "tone": "amber" + }, + { + "title": "5. Overovanie", + "body": "Opätovne načíta súbor, vypíše, čo bolo odstránené, a znova skontroluje zostávajúce rozhrania. Vytlačí príkaz na vrátenie. nereštartuje automaticky sieť – odstránenie nepoužívaného bloku je bezpečné použiť pri ďalšom reštarte a nedotýka sa aktívneho stavu.", + "tone": "emerald" + } + ], + "noRestartTitle": "Nereštartuje sieť automaticky", + "noRestartBody": "Na rozdiel od opravy mostov čistenie neponúka reštart služby. Odstránenie nepoužívaného bloku rozhrania okamžite neovplyvní bežiacu konfiguráciu, takže reštart nie je potrebný a zbytočne by ohrozil pripojenie. Zmena sa prejaví pri ďalšom ručnom systemctl restart networking alebo pri ďalšom reštarte systému." + }, + "caveats": { + "heading": "Dôležité upozornenia", + "boundaryTitle": "Hranica bloku je prvý prázdny riadok", + "boundaryBody": "Vzor sed odstráni obsah od iface <name> po ďalší prázdny riadok. Ak v /etc/network/interfaces chýbajú prázdne riadky medzi blokmi, čo je zriedkavé, ale možné po ručných úpravách, môže sa odstrániť aj nasledujúci blok. Preto je krok 3 povinný: pred potvrdením skontrolujte náhľad.", + "tandemTitle": "Osirelý blok iface používaný mostom sa zistí tu, nie pri analýze mostov", + "tandemBody": "Ak vmbr0 deklaruje bridge-ports enp3s0 a enp3s0 má aj vlastný blok iface enp3s0 inet manual, odstránenie enp3s0 tu ponechá neplatný odkaz v vmbr0. Po čistení spustite analýzu mostov na zmenu priradenia alebo odstránenie portu mosta. Oba postupy sú navrhnuté na spoločné použitie." + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "notFoundTitle": "Analýza zobrazuje moju novú sieťovú kartu ako „NENÁJDENÁ“", + "notFoundBody": "Jadro vidí zariadenie pod iným názvom, než je v súbore. Spustite ip link show a zistite skutočný názov. Presne v takomto prípade by trvalé názvy rozhraní zabránili problému v budúcnosti.", + "tooMuchTitle": "Čistenie odstránilo príliš veľa, aj blok, ktorý som chcel zachovať", + "tooMuchBody": "Obnovte stav zo zálohy uvedenej v kroku 5:", + "tooMuchOutro": "Pred opätovným spustením čistenia ručne upravte súbor a pridajte prázdny riadok medzi bloky alebo problematický blok odstráňte ručne.", + "bridgeBreakTitle": "Most prestane pracovať po vyčistení.", + "bridgeBreakBody": "Most používal port, ktorého blok iface sa práve odstránil. Spustite analýzu mostov a návrh priradí most k existujúcemu fyzickému rozhraniu." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Analýza mostov a oprava so sprievodcom", + "href": "/docs/network/bridge-analysis", + "tail": " — prirodzený ďalší krok po čistení." + }, + { + "label": "Trvalé názvy rozhraní", + "href": "/docs/network/persistent-names", + "tail": " — zabraňuje hromadeniu osirelých blokov po každej zmene hardvéru." + }, + { + "label": "Záloha a reštart rozhraní", + "href": "/docs/network/backup-restore", + "tail": " — ručné snímky a prehliadanie záloh na obnovu." + } + ] + } +} diff --git a/web/messages/sk/docs/network/diagnostics.json b/web/messages/sk/docs/network/diagnostics.json new file mode 100644 index 00000000..1381afe4 --- /dev/null +++ b/web/messages/sk/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnostika | Dokumentácia ProxMenux", + "description": "Tri jednorazové diagnostické kontroly iba na čítanie: Zobraziť smerovaciu tabuľku, Test pripojenia a Pokročilá diagnostika. Iba kontrola — nikdy nezapisuje do /etc/network/interfaces ani nespúšťa príkazy meniace systém, s jedinou výslovnou výnimkou: úplné odstránenie zisteného NetworkManager vyžaduje váš súhlas.", + "ogTitle": "Diagnostika | Dokumentácia ProxMenux", + "ogDescription": "Sieťová diagnostika iba na čítanie pre hostiteľa Proxmoxu: smerovanie, dosiahnuteľnosť, pokročilé štatistiky." + }, + "header": { + "title": "Diagnostika", + "description": "Tri jednorazové kontroly aktuálneho stavu siete iba na čítanie: smerovacia tabuľka, test pripojenia a pokročilé štatistiky. Žiadna nezapisuje do /etc/network/interfaces — bezpečné použitie kedykoľvek, aj cez SSH. Pre interaktívne sledovanie v reálnom čase pozrite Nástroje na sledovanie v reálnom čase.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Zobrazuje informácie o smerovaní, dosiahnuteľnosti a súhrnných štatistikách bez akýchkoľvek zmien. Overte ním správne fungovanie siete alebo odhaľte duplicitné IP adresy. Na nepretržité sledovanie prevádzky pozrite Nástroje na sledovanie v reálnom čase; na zobrazenie nespracovaného konfiguračného súboru pozrite Záloha a reštart rozhraní." + }, + "routing": { + "heading": "Zobraziť smerovaciu tabuľku", + "body": "Zobrazí všetky trasy aktuálne používané jadrom (ip route show) a zvýrazní predvolenú bránu. Umožňuje overiť, že prevádzka do internetu odchádza očakávaným rozhraním a žiadne zvyšné trasy nemajú prednosť." + }, + "connectivity": { + "heading": "Otestovať pripojenie", + "intro": "Postupne odošle na každý z troch cieľov dve sondy ICMP a vypíše výsledky. Potom spustí nslookup google.com na overenie prekladu DNS nezávisle od ICMP.", + "headerTest": "Skúška", + "headerTarget": "Cieľ", + "headerConfirms": "Čo potvrdzuje", + "rows": [ + { + "test": "Google DNS", + "target": "8.8.8.8", + "confirms": "Externá dosiahnuteľnosť cez známu IP adresu anycast" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Nezávislé druhé stanovisko, ak Google blokuje ICMP" + }, + { + "test": "Brána", + "target": "(štandardná trasa)", + "confirms": "L2 / L3 pripojenie k miestnemu routeru" + }, + { + "test": "Preklad DNS", + "target": "google.com", + "confirms": "DNS server je dosiahnuteľný a odpovedá" + } + ], + "readingTitle": "Čítanie výsledku", + "readingBody": "Ak test brány prejde, ale verejné servery DNS neodpovedajú, máte lokálne pripojenie, no nemáte internet — skontrolujte pripojenie brány smerom k poskytovateľovi. Ak preklad DNS zlyhá, ale ping na 8.8.8.8 funguje, problém je v resolveri, nie v sieti: skontrolujte /etc/resolv.conf." + }, + "advanced": { + "heading": "Pokročilá diagnostika", + "intro": "Zhromažďuje štatistiky celej siete a vykonáva sériu kontrol bežných problémov. Hlási počet aktívnych pripojení, načúvajúce porty a celkový počet rozhraní a výslovne upozorňuje na dve nevhodné konfigurácie:", + "items": [ + "NetworkManager beží na hostiteľovi Proxmox. NetworkManager a ifupdown systému Proxmox sú v konflikte: oba sa pokúšajú spravovať rozhrania, takže mosty opakovane menia stav alebo sa nedajú aktivovať. Ak sa zistí NetworkManager, nástroj ponúkne interaktívne potvrdenie jeho zastavenia, zakázania a úplného odstránenia (ide o jedinú diagnostiku, ktorá môže meniť systém, a to iba po výslovnom súhlase).", + "Duplicitné IP adresy na rôznych rozhraniach. Dve rozhrania s rovnakou IPv4 adresou spôsobujú občasné výpadky pripojenia, ktoré sa zvnútra VM veľmi ťažko diagnostikujú. Zisťujú sa cez ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "Výzva pre NetworkManager je tu jedinou akciou, ktorá môže meniť systém", + "nmBody": "Všetky ostatné kontroly pokročilej diagnostiky sú iba na čítanie. Odstránenie NetworkManager vyžaduje potvrdenie áno/nie a spustí systemctl stop / disable + apt-get purge -y network-manager. Ak odmietnete, nič sa nezmení." + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "gwTitle": "Test pripojenia hlási zlyhanie brány, ale hostiteľ má prístup na internet", + "gwBody": "Niektoré smerovače neodpovedajú na ICMP na internom rozhraní, aj keď správne preposielajú prevádzku. V konzole skúste traceroute 8.8.8.8: ak je druhým skokom váš poskytovateľ a test verejného DNS prešiel, brána funguje aj napriek neúspešnému pingu.", + "dupTitle": "Pokročilá diagnostika stále hlási duplicitné IP adresy aj po odstránení jednej z nich", + "dupBody": "Kontrola skúma aktuálny stav jadra, nie konfiguračný súbor. Overte ho príkazom ip -4 addr show; ak je duplicitná adresa stále prítomná, niektoré rozhranie ju stále používa. Pomocou ip addr del <IP>/<mask> dev <iface> ju odstráňte z aktuálneho stavu a potom upravte /etc/network/interfaces, ak je nastavená aj natrvalo." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Nástroje na sledovanie v reálnom čase", + "href": "/docs/network/monitoring", + "tail": " — Interaktívne iftop / iptraf-ng / iperf3 spúšťače." + }, + { + "label": "Analýza mostov a oprava so sprievodcom", + "href": "/docs/network/bridge-analysis", + "tail": " — Keď vmbrX chýba fyzický port." + }, + { + "label": "Analýza konfigurácie a čistenie so sprievodcom", + "href": "/docs/network/config-analysis", + "tail": " — keď je stará sieťová karta stále deklarovaná, ale už nie je prítomná." + }, + { + "label": "Záloha a reštart rozhraní", + "href": "/docs/network/backup-restore", + "tail": " — na vytvorenie snímky konfigurácie pred každou zmenou." + } + ] + } +} diff --git a/web/messages/sk/docs/network/index.json b/web/messages/sk/docs/network/index.json new file mode 100644 index 00000000..871acbef --- /dev/null +++ b/web/messages/sk/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Správa siete Proxmox — Mosty, bondy, diagnostika, oprava | ProxMenux", + "description": "Diagnostika iba na čítanie, analýzy s následnými návrhmi a opravy so sprievodcom pre sieťový stack Debian / Proxmox (/etc/network/interfaces). Skontrolujte mosty a bondy, spustite nástroje na sledovanie v reálnom čase, nastavte trvalé názvy rozhraní, zálohujte konfiguráciu a bezpečne reštartujte sieť — s povinnými zálohami a náhľadmi jednotlivých krokov.", + "ogTitle": "Správa siete Proxmox — Mosty, bondy, diagnostika, oprava", + "ogDescription": "Diagnostika, analýza a opravy sieťového stacku Proxmox so sprievodcom, povinnými zálohami a náhľadmi jednotlivých krokov.", + "twitterTitle": "Správa siete Proxmox | ProxMenux", + "twitterDescription": "Mosty, bondy, diagnostika a opravy sieťového stacku Proxmox so sprievodcom a povinnými zálohami." + }, + "header": { + "title": "Správa siete", + "description": "Diagnostika iba na čítanie, analytické správy s následnými návrhmi a opravy so sprievodcom pre klasický sieťový stack Debian/Proxmox. Každý deštruktívny postup najprv vytvorí zálohu a pred použitím zobrazí presné zmeny.", + "section": "Sieť" + }, + "intro": { + "title": "Na čo je toto menu", + "body": "Skontrolujte, diagnostikujte a v prípade potreby opravte sieťovú konfiguráciu hostiteľa Proxmox bez straty vzdialeného prístupu. Nástroje sa riadia jednou zásadou: najprv prečítať, potom navrhnúť a zmeny použiť iba s výslovným súhlasom a bezpečnostnou zálohou. Tlačidlo „Automaticky opraviť všetko“ neexistuje." + }, + "openingMenu": { + "heading": "Otvorenie menu", + "intro": "V hlavnom menu ProxMenux vyberte Sieť. Zobrazí sa toto:", + "imageAlt": "Menu Správa siete s možnosťami diagnostiky, sledovania, analýzy, opráv, trvalých názvov a zálohovania" + }, + "safety": { + "heading": "Bezpečnostný model", + "body": "Úprava sieťovej konfigurácie vzdialeného hostiteľa Proxmox je jedným z najjednoduchších spôsobov, ako stratiť prístup. ProxMenux to zohľadňuje pri každej akcii. Nástroje sú rozdelené do troch úrovní správania — vyberte kartu, ktorá zodpovedá vášmu zámeru:" + }, + "tiers": { + "readOnly": { + "title": "Iba na čítanie", + "body": "Iba kontrola. Za žiadnych okolností nemôže meniť systém.", + "items": [ + "Smerovacia tabuľka, testy pripojenia, pokročilé štatistiky", + "Sledovanie sieťovej prevádzky v reálnom čase (iftop, iptraf-ng)", + "Test priepustnosti (iperf3)" + ] + }, + "analyze": { + "title": "Analyzovať, potom navrhnúť", + "body": "Zistí problémy, vypíše správu s navrhovanými príkazmi shellu a zastaví sa. Sami sa rozhodnete, či potom spustíte opravu so sprievodcom.", + "items": [ + "Analýza konfigurácie mosta", + "Analýza konfigurácie siete" + ] + }, + "apply": { + "title": "Aplikovať so zálohou", + "body": "Mení systém. Vždy najprv vytvorí zálohu dotknutého súboru s časovou pečiatkou a pred zápisom zobrazí náhľad.", + "items": [ + "Trvalé názvy rozhraní (súbory .link)", + "Ručné zálohovanie, obnovenie a reštart služby" + ] + } + }, + "classicTitle": "Iba klasický sieťový stack", + "classicBody": "Každá funkcia analýzy a opravy pred zásahom skontroluje aktívneho správcu siete. Ak hostiteľ používa netplan, systemd-networkd alebo NetworkManager, nástroj sa ihneď zastaví s jasným hlásením. Menu podporuje iba klasickú sieťovú konfiguráciu Debian/Proxmox v /etc/network/interfaces. Je to zámerné: úprava súboru netplan podľa pravidiel pre ifupdown by mohla konfiguráciu nepozorovane poškodiť.", + "backups": { + "heading": "Kam sa ukladajú zálohy", + "intro": "Každá oprava so sprievodcom, obnova alebo ručné zálohovanie zapíše kópiu /etc/network/interfaces s časovou pečiatkou do /var/backups/proxmenux/:", + "rollbackIntro": "Na ručné obnovenie predchádzajúceho stavu z konzoly:" + }, + "readOnlySection": { + "heading": "Kontrola len na čítanie", + "body": "Východiskový bod, keď niečo nefunguje správne. Iba kontrola — nikdy nezapisuje do /etc/network/interfaces ani nespúšťa príkazy meniace systém, s jedinou výslovnou výnimkou: úplné odstránenie zisteného NetworkManager vyžaduje váš súhlas. Bezpečné použitie cez SSH kedykoľvek.", + "options": [ + { + "title": "Diagnostika", + "description": "Tri jednorazové kontroly iba na čítanie: Zobraziť smerovaciu tabuľku, Test pripojenia a Pokročilá diagnostika. Iba kontrola — nikdy nezapisuje do systému." + }, + { + "title": "Nástroje na sledovanie v reálnom čase", + "description": "Tri interaktívne spúšťače: iftop (šírka pásma v reálnom čase pre každú dvojicu hostiteľov), iptraf-ng (sledovanie prevádzky vo viacerých režimoch) a iperf3 (test šírky pásma, režim server / klient)." + } + ] + }, + "analyzeSection": { + "heading": "Analyzovať, potom navrhnúť", + "body": "Používajú sa, keď kontrola alebo skutočný výpadok poukazuje na problém konfigurácie. Každý nástroj prejde príslušnú časť /etc/network/interfaces, vypíše podrobnú správu s presným príkazom shellu na opravu každého zistenia a zastaví sa. Ak potom prijmete voliteľnú opravu so sprievodcom, pred každou zmenou sa vytvorí záloha a zmena sa zobrazí pred zápisom.", + "options": [ + { + "title": "Analýza mostov a oprava so sprievodcom", + "description": "Zistí mosty vmbrX s chýbajúcimi alebo neplatnými portmi, typicky po opätovnom číslovaní zariadení PCI. Najprv zobrazí správu a opravu vykoná iba po prijatí postupu so sprievodcom v 5 krokoch." + }, + { + "title": "Analýza konfigurácie a čistenie so sprievodcom", + "description": "Nájde fyzické rozhrania deklarované v /etc/network/interfaces, ktoré už neexistujú — osirelé konfigurácie po zmenách hardvéru. Nahlási ich a ponúkne odstránenie so sprievodcom." + } + ] + }, + "applySection": { + "heading": "Aplikovať so zálohou", + "body": "Tieto nástroje zo svojej podstaty zapisujú na disk. Každý pred zápisom vytvorí zálohu dotknutého súboru s časovou pečiatkou. Deštruktívne možnosti, ako obnova a reštart, vyžadujú výslovné potvrdenie áno/nie. Trvalé názvy rozhraní sa prejavia až pri ďalšom reštarte, nie okamžite, takže ich nastavenie možno bezpečne naplánovať aj na vzdialenom hostiteľovi.", + "options": [ + { + "title": "Trvalé názvy rozhraní", + "description": "Priradí názvy rozhraní (eno1, enp3s0, …) k MAC adresám pomocou súborov .link služby systemd. Názvy zostanú zachované pri zmene slotov PCI, aktualizáciách jadra aj pridávaní alebo odoberaní iných sieťových kariet." + }, + { + "title": "Záloha a reštart rozhraní", + "description": "Ručné vytvorenie snímky /etc/network/interfaces, prehliadanie a obnova predchádzajúcich záloh, zobrazenie aktuálnej konfigurácie a reštart sieťovej služby podľa potreby." + } + ] + }, + "consoleTitle": "Pripravte si prístup ku konzole", + "consoleSubTitle": "Pred akoukoľvek opravou na vzdialenom hostiteľovi", + "consoleBody": "Ak ste pripojení cez SSH a máte iba jednu cestu k hostiteľovi, pred zmenami siete si pripravte náhradný prístup: fyzickú konzolu / IPMI / iKVM alebo iný počítač v rovnakej LAN. Opravy so sprievodcom sú bezpečné a vždy ponúkajú príkaz na vrátenie zmien, ale nesprávne nakonfigurovaný most alebo prerušené spojenie vás aj tak môže pripraviť o prístup, kým sa nedostanete ku konzole." +} diff --git a/web/messages/sk/docs/network/monitoring.json b/web/messages/sk/docs/network/monitoring.json new file mode 100644 index 00000000..6038905a --- /dev/null +++ b/web/messages/sk/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Nástroje na sledovanie v reálnom čase | Dokumentácia ProxMenux", + "description": "Tri interaktívne spúšťače nástrojov na sledovanie siete: iftop (šírka pásma v reálnom čase pre každú dvojicu hostiteľov), iptraf-ng (sledovanie prevádzky vo viacerých režimoch) a iperf3 (test šírky pásma, režim server / klient). Každý sa nainštaluje pri prvom použití.", + "ogTitle": "Nástroje na sledovanie v reálnom čase | Dokumentácia ProxMenux", + "ogDescription": "Interaktívne monitorovanie siete a testovanie šírky pásma pre Proxmox host: iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Nástroje na sledovanie v reálnom čase", + "description": "Tri interaktívne spúšťače na sledovanie siete v reálnom čase a meranie šírky pásma: iftop, iptraf-ng a iperf3. Každý nástroj sa pri prvom použití automaticky nainštaluje cez apt, beží v popredí a ukončuje sa uvedeným klávesom. Nemenia sieťovú konfiguráciu hostiteľa; iperf3 generuje testovaciu prevádzku.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Tri interaktívne monitorovacie nástroje, každý za vlastným vstupom do menu. Pri prvom spustení jedného sa balík inštaluje ticho cez apt-get. Následné štarty začínajú okamžite. Žiadny z týchto nástrojov nemení konfiguráciu hostiteľskej siete." + }, + "when": { + "heading": "Kedy použiť ktorý", + "headerQuestion": "Otázka", + "headerUse": "Použitie", + "rows": [ + { + "question": "Kto vyťažuje spojenie práve teraz?", + "use": "iftop" + }, + { + "question": "Aké členenie protokolu / veľkosti paketov / toky TCP prúdia?", + "use": "iptraf-ng" + }, + { + "question": "Koľko šírky pásma je skutočne k dispozícii medzi dvoma hostiteľmi?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "Sieťová prevádzka v reálnom čase (iftop)", + "body": "Šírka pásma v reálnom čase pre dvojice hostiteľov (zdroj ↔ cieľ) — ako top pre sieťovú prevádzku. Najväčšie toky zobrazuje navrchu s kĺzavými priemermi za 2/10/40 sekúnd. Najlepší nástroj na otázku „Prečo je moje odchádzajúce spojenie vyťažené?“.", + "exit": "Ukončenie: stlačte q. ProxMenux pred spustením zobrazí pripomenutie.", + "keysTitle": "Užitočné klávesy v iftop", + "keysBody": "n prepína vyhľadávanie DNS, p zobrazuje čísla portov, P pozastaví zobrazenie, t prepína smer riadku (odoslané / prijaté / oboje), 1/2/3 zoradí podľa priemeru za 2 s / 10 s / 40 s. Úplný zoznam nájdete v man iftop." + }, + "iptraf": { + "heading": "Nástroj na sledovanie siete (iptraf-ng)", + "intro": "Nástroj na sledovanie prevádzky vo viacerých režimoch, ovládaný cez menu. iftop odpovedá na „kto“, iptraf-ng na „čo“: počty bajtov a paketov podľa protokolu, sledovanie stavu spojení TCP, histogramy veľkosti paketov a aktivita LAN podľa stanice.", + "menuIntro": "Pri spustení dostanete ponuku piatich možností:", + "headerMode": "Režim", + "headerUseFor": "Použiť pre", + "rows": [ + { + "mode": "Monitor prevádzky IP", + "useFor": "Zoznam tokov TCP / UDP / ICMP / iných protokolov IP v reálnom čase s počítadlami bajtov a stavom pripojenia" + }, + { + "mode": "Všeobecné štatistiky rozhraní", + "useFor": "Súhrnný počet paketov IPv4 / IPv6 / TCP / UDP / ICMP / non-IP paketov na sieťovú kartu" + }, + { + "mode": "Podrobné štatistiky rozhrania", + "useFor": "Rovnaké ako vyššie, ale pre jedno rozhranie, s podrobnosťami o veľkosti a rýchlosti paketov" + }, + { + "mode": "Štatistické členenia", + "useFor": "Histogram rozdelenia veľkosti paketov podľa portu TCP / UDP" + }, + { + "mode": "Monitor stanice LAN", + "useFor": "Štatistiky prevádzky podľa MAC adresy pre lokálnu broadcastovú doménu" + } + ], + "exit": "Ukončenie: stlačte x v ľubovoľnom zobrazení alebo Q v hlavnom menu. ProxMenux pred spustením zobrazí pripomenutie.", + "logTitle": "Zaznamenávanie zachytených údajov do súboru", + "logBody": "Každý režim ponúka zápis zachytených štatistík do /var/log/iptraf-ng/. Ak potrebujete záznam špičky prevádzky, nechajte nástroj bežať, zapisujte do súboru a skontrolujte ho neskôr namiesto sledovania obrazovky v reálnom čase." + }, + "iperf3": { + "heading": "Test priepustnosti (iperf3)", + "intro1": "Meria skutočnú priepustnosť TCP medzi dvoma hostiteľmi. Na rozdiel od iftop / iptraf-ng, ktoré sledujú existujúcu prevádzku, iperf3 generuje syntetickú prevádzku na záťažový test spojenia. Nevyhnutné pri otázkach ako „Dosahuje moje 10 GbE skutočne 10 GbE?“ alebo „Je úzkym miestom sieťová karta, prepínač alebo úložisko?“.", + "intro2": "iperf3 je nástroj pre dvoch hostiteľov: jeden beží ako server a načúva na porte TCP 5201, druhý ako klient, ktorý sa pripojí, odošle údaje a vypíše rýchlosť. Spúšťač ProxMenux sa opýta na požadovaný režim:", + "headerMode": "Režim", + "headerBehaviour": "Správanie", + "headerCli": "Ekvivalentný príkaz CLI", + "rows": [ + { + "mode": "Server", + "behaviour": "Načúva na porte TCP 5201 a vypíše výsledky každého prichádzajúceho testu. Ukončí sa cez Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Klient", + "behaviour": "Vyžiada IP adresu alebo názov servera, pripojí sa, spustí predvolený 10-sekundový test a vypíše správu.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Typický pracovný postup na testovanie 10 GbE spojenia medzi dvoma hostiteľmi Proxmox:", + "workflow": [ + "Na hostiteľovi A otvorte menu Sieť → Test priepustnosti (iperf3) → vyberte Server.", + "Na hostiteľovi B otvorte rovnakú položku ponuky → vyberte Klient → zadajte IP hostiteľa A.", + "Počkajte 10 sekúnd. Porovnajte nameranú rýchlosť s teoretickým maximom spojenia." + ], + "sample": "Vzorový výstup klienta:", + "flagsTitle": "Užitočné voľby pri ručnom spustení zo shellu", + "flagsBody": "-t 60 dlhší test (60 s namiesto 10 s), -P 4 4 paralelné toky (rýchlejšie vyťažia spojenie), -R opačný smer (server → klient), -u -b 100M test UDP pri 100 Mbit/s (meranie kolísania oneskorenia a straty paketov), -p 5202 iný port (viac testov súčasne). Úplný zoznam nájdete v man iperf3.", + "firewallTitle": "Otvoriť firewall port na serveri", + "firewallBody": "Server predvolene načúva na porte TCP 5201. Ak ho spustíte na hostiteľovi Proxmox s prísnym firewallom na úrovni dátového centra alebo hostiteľa, počas testu povoľte prichádzajúce TCP 5201 z IP adresy klienta a potom port opäť zatvorte. To isté platí pre nftables / iptables na fyzických hostiteľoch." + }, + "install": { + "heading": "Prvé spustenie nainštaluje balík", + "body": "Všetky tri spúšťače overia prítomnosť programu a ak chýba, spustia apt-get update -qq && apt-get install -y <pkg>. Inštalácia prebieha bez výpisu: pri prvom použití môže menu pôsobiť zamrznuto 10–30 sekúnd. Ďalšie spustenia sú okamžité." + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "hangTitle": "Inštalácia visí navždy pri prvom spustení", + "hangBody": "Hostiteľ nemá internet alebo apt repo sú nedosiahnuteľné. Zrušiť pomocou Ctrl+C, spustiť apt-get update ručne vidieť skutočnú chybu (DNS, repo podpis, proxy ...), potom sa vrátiť do menu.", + "refusedTitle": "Klient iperf3: „unable to connect to server: Connection refused“", + "refusedBody": "Server buď nebeží, alebo jeho firewall blokuje TCP 5201. Na serveri overte cez ss -tlnp | grep 5201, že iperf3 načúva. Ak načúva, ale klient stále zlyháva, skontrolujte firewally na trase medzi hostiteľmi.", + "slowTitle": "iperf3 hlási výrazne nižšiu rýchlosť, než sa od spojenia očakáva", + "slowBody": "Bežné príčiny v poradí pravdepodobnosti: (1) CPU jedného hostiteľa je úzkym miestom — skúste iperf3 -c <target> -P 4 na využitie viacerých jadier; (2) prenos prechádza pomalším spojom, napríklad gigabitovým prepínačom medzi dvoma kartami 10 GbE; (3) nezhodné MTU — skontrolujte ip link show na oboch koncoch; (4) vypnutý NIC offloading — pri karte Intel e1000e pozrite komunitný skript Disable NIC Offloading.", + "noTrafficTitle": "iftop / iptraf-ng nevykazujú žiadnu prevádzku pre rušného hostiteľa", + "noTrafficBody": "Predvolené zachytávanie používa prvé zistené rozhranie. Správne rozhranie zadajte výslovne zo shellu: iftop -i vmbr0 alebo iptraf-ng -i vmbr0. Spúšťač v menu používa predvolené rozhranie; pre iné rozhrania spustite nástroj z konzoly." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Diagnostika", + "href": "/docs/network/diagnostics", + "tail": " — jednorazové kontroly iba na čítanie (smerovanie, pripojenie a pokročilé štatistiky)." + }, + { + "label": "Analýza mostov a oprava so sprievodcom", + "href": "/docs/network/bridge-analysis", + "tail": " — keď sledovanie odhalí, že rozhranie alebo most nefunguje správne." + } + ] + } +} diff --git a/web/messages/sk/docs/network/persistent-names.json b/web/messages/sk/docs/network/persistent-names.json new file mode 100644 index 00000000..bb2a952e --- /dev/null +++ b/web/messages/sk/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Trvalé názvy rozhraní | Dokumentácia ProxMenux", + "description": "Priraďte názvy rozhraní Proxmox (eno1, enp3s0, …) k MAC adresám pomocou súborov .link služby systemd, aby zostali zachované pri opätovnom číslovaní PCI, aktualizáciách jadra a pridávaní či odoberaní iných sieťových kariet.", + "ogTitle": "Trvalé názvy rozhraní | Dokumentácia ProxMenux", + "ogDescription": "Zabráňte zmene názvov rozhraní Proxmox pri zmene hardvéru. Používa súbory .link služby systemd s priradením podľa MAC adresy." + }, + "header": { + "title": "Trvalé názvy rozhraní", + "description": "Pre každú fyzickú sieťovú kartu vytvorí súbor .link služby systemd, ktorý priradí názov pridelený jadrom k MAC adrese karty. Po použití pri ďalšom reštarte sa názvy rozhraní prestanú meniť pri pridávaní, odoberaní alebo presúvaní kariet PCIe.", + "section": "Sieť" + }, + "intro": { + "title": "Čo to robí?", + "body": "Pre každú fyzickú sieťovú kartu hostiteľa zapíše malý súbor /etc/systemd/network/10-<iface>.link s pravidlom: „Zariadenie s touto MAC adresou sa musí vždy volať týmto názvom.“ systemd-udevd použije pravidlo pri každom štarte skôr, než ifupdown prečíta /etc/network/interfaces." + }, + "problem": { + "heading": "Problém, ktorý rieši", + "intro": "Linux odvodzuje predvolené názvy rozhraní z topológie PCI — eno1 = integrovaná karta, enp3s0 = karta na zbernici PCI 3, slot 0 atď. Schéma názvov je deterministická pri rovnakom rozložení hardvéru. Ak sa rozloženie zmení, zmenia sa aj názvy:", + "items": [ + "Pridanie GPU pred existujúcu sieťovú kartu → číslo zbernice sieťovej karty sa môže zmeniť → zmení sa názov.", + "Presunutie karty do iného slotu PCIe → zmení sa názov.", + "Aktualizácia BIOS / UEFI s opätovným číslovaním zariadení → názvy sa môžu zmeniť.", + "Nahradenie chybnej karty rovnakým modelom v inom slote → zmení sa názov." + ], + "outro": "Po takejto zmene /etc/network/interfaces stále odkazuje na starý názov; most sa neaktivuje a hostiteľ stratí sieťové pripojenie. Nastavenie trvalých názvov zabráni opakovaniu tejto situácie." + }, + "howWorks": { + "heading": "Ako to funguje", + "arrowLabel": "na NIC", + "nodes": { + "detectLabel": "Zistiť fyzické sieťové karty", + "detectDetail": "ls /sys/class/net/\nvylúčiť vmbr / bond /\ndocker / veth / wireguard …", + "readLabel": "Prečítať každú MAC adresu", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": "Zapísať súbor .link", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Každý vygenerovaný súbor je zámerne minimálny:", + "minimalOutro": "Pri štarte systemd-udevd identifikuje zariadenie podľa MAC adresy a pridelí požadovaný názov skôr, než zasiahnu iné komponenty (ifupdown, predvolené pomenovanie jadra). Predpona 10- zabezpečuje skoré načítanie pravidiel pred predvoleným súborom 99-default.link." + }, + "scope": { + "heading": "Čo sa napíše, čo sa preskočí", + "headerType": "Typ", + "headerBehaviour": "Správanie", + "headerWhy": "Prečo?", + "rows": [ + { + "type": "Integrovaná / PCIe sieťová karta", + "behaviour": "Zapíše sa súbor .link", + "why": "Zodpovedá skutočnému zariadeniu PCI — prípad, pre ktorý je nástroj určený" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": "Zapíše sa súbor .link", + "why": "Má skutočnú MAC adresu a využije stabilný názov" + }, + { + "type": "Mosty (vmbrX)", + "behaviour": "Preskočené", + "why": "Virtuálne; meno pochádza z /etc/network/interfaces" + }, + { + "type": "Bondy (bondX)", + "behaviour": "Preskočené", + "why": "Virtuálne; názov bondu nastavuje ifupdown" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Preskočené", + "why": "Vytvárané podľa potreby systémom Docker / LXC — nejde o trvalý hardvér" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Preskočené", + "why": "Vytvárané podľa potreby systémom Proxmox pre každú VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Preskočené", + "why": "Softvérové rozhrania spravované vlastnými démonmi" + } + ] + }, + "safety": { + "heading": "Ochrana: predchádzajúce súbory .link sa zálohujú", + "intro": "Ak /etc/systemd/network/ už obsahuje .link súbory (z predchádzajúceho spustenia alebo iného nástroja), skopírujú sa do záložného adresára s časovou značkou pred generovaním nových:", + "outro": "Ak chcete vrátiť späť: skopírujte súbory späť zo záložného adresára a reštartujte.", + "rebootTitle": "Prejaví sa pri ďalšom reštarte, nie okamžite", + "rebootBody": "Zmeny súborov .link sa použijú iba pri štarte, keď udev znovu enumeruje zariadenia. Preto nástroj hlási „Zmeny sa použijú po reštarte“. Premenovanie rozhrania za behu je riskantné a nástroj sa oň nepokúša: aktívny vmbr0 s členskými rozhraniami by sa musel prekonfigurovať atómovo. Operácia sa preto odkladá na ďalší čistý štart." + }, + "afterReboot": { + "heading": "Po reštarte", + "intro": "Keď sú názvy napevno priradené, postup pri budúcich zmenách hardvéru je jednoduchý:", + "items": [ + "Vypnite počítač, zmeňte hardvér (pridajte kartu, zmeňte slot, …) a spustite ho.", + "Každý NIC si ponechá svoje predchádzajúce meno, pretože jeho MAC zodpovedá súboru .link.", + "Ak je NIC nahradený (iný MAC), dostane predvolený názov jadra (enp<bus>s<slot>); znovu spustite toto menu pre pridanie položky .link pre MAC novej karty." + ] + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "emptyTitle": "\"Nenájdené žiadne fyzické rozhrania\" po spustení nástroja", + "emptyBody": "Jadro na hostiteľovi nevidí žiadne rozhrania založené na PCI / phy80211. Overte to cez ls -l /sys/class/net/ — každá položka by mala obsahovať symbolický odkaz device (PCI) alebo položku phy80211 (Wi-Fi). Ak pri skutočnej sieťovej karte chýbajú obe, ovládač nie je načítaný.", + "noChangeTitle": "Po reštarte sa mená nezmenili podľa očakávania.", + "noChangeBody": "Skontrolujte, či súbor existuje a má správny formát: cat /etc/systemd/network/10-<iface>.link. Potom skontrolujte záznamy udev: journalctl -u systemd-udevd -b | grep -i link. Bežnou príčinou je starý parameter jadra net.ifnames=0, ktorý úplne vypína predvídateľné názvy — odstráňte ho z /etc/default/grub, spustite update-grub a reštartujte.", + "undoTitle": "Chcem zrušiť trvalé pomenovanie.", + "undoBody": "Buď odstráňte súbory .link (rm /etc/systemd/network/10-*.link), alebo ich obnovte z adresára záloh vytvoreného pri predchádzajúcom spustení. Zmenu použijete reštartom." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "label": "Analýza mostov a oprava so sprievodcom", + "href": "/docs/network/bridge-analysis", + "tailRich": " — postup obnovy, keď sa názvy už zmenili." + }, + { + "label": "Analýza konfigurácie a čistenie so sprievodcom", + "href": "/docs/network/config-analysis", + "tail": " — na odstránenie osirelých blokov, ktoré zostali po zmenách názvov." + }, + { + "label": "Diagnostika", + "href": "/docs/network/diagnostics", + "tailRich": " — po reštarte overte nové názvy cez Zobraziť smerovaciu tabuľku." + } + ] + } +} diff --git a/web/messages/sv/docs/network/backup-restore.json b/web/messages/sv/docs/network/backup-restore.json new file mode 100644 index 00000000..6b0269b3 --- /dev/null +++ b/web/messages/sv/docs/network/backup-restore.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Säkerhetskopiering och omstart av gränssnitt | ProxMenux-dokumentation", + "description": "Ta manuella ögonblicksbilder, bläddra bland och återställ säkerhetskopior av /etc/network/interfaces som lagras i /var/backups/proxmenux/. Inkluderar omstart av nätverkstjänsten med bekräftelse och en snabbvy av den aktuella konfigurationen.", + "ogTitle": "Säkerhetskopiering och omstart av gränssnitt | ProxMenux-dokumentation", + "ogDescription": "Ta en ögonblicksbild och återställ Proxmox nätverkskonfiguration; starta om nätverkstjänsten med bekräftelse." + }, + "header": { + "title": "Säkerhetskopiering och omstart av gränssnitt", + "description": "Fyra verktyg för /etc/network/interfaces: ta en manuell ögonblicksbild, visa aktuell konfiguration, återställa en tidigare säkerhetskopia med valfri förhandsvisning och starta om nätverkstjänsten med uttryckligt samtycke. Samma katalog för säkerhetskopior används av de guidade reparationerna.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "Hanterar samma katalog för säkerhetskopior som de guidade reparationerna (/var/backups/proxmenux/). Du kan ta en extra ögonblicksbild före manuella ändringar, bläddra bland alla befintliga säkerhetskopior och återställa vilken som helst — alltid med en ny automatisk säkerhetskopia före återställningen som ett andra skydd." + }, + "shared": { + "heading": "Den gemensamma katalogen för säkerhetskopior", + "intro": "Varje säkerhetskopia som tas någonstans i nätverksmenyn landar på samma ställe:", + "outro": "Filnamnen är tidsstämplade och sorterade efter datum, och filerna skrivs aldrig över. Katalogen skapas vid första användningen." + }, + "show": { + "heading": "Visa nätverkskonfigurationsfil", + "body": "Skriver ut /etc/network/interfaces oförändrad i terminalen. Skrivskyddat. Användbart som kontroll före säkerhetskopiering eller efter återställning. Det finns inget separat verktyg för att jämföra med föregående säkerhetskopia, så att granska den aktuella filen är det enklaste sättet att kontrollera innehållet." + }, + "create": { + "heading": "Skapa nätverksbackup", + "body": "Kopierar den nuvarande /etc/network/interfaces i backupkatalogen med en ny tidsstämpel. Det är det - ingen analys, inga prompts, bara en ögonblicksbild.", + "whenTitle": "När man tar en manuell backup", + "whenBody": "Innan du redigerar /etc/network/interfaces för hand, installerar ett paket som kan ändra nätverksstacken (till exempel NetworkManager eller Open vSwitch) eller gör en maskinvaruändring. De guidade reparationerna tar redan ögonblicksbilder automatiskt — det här alternativet är för manuella ingrepp." + }, + "restore": { + "heading": "Återställ nätverkssäkerhetskopia", + "intro": "Listar alla säkerhetskopior i /var/backups/proxmenux/ från nyast till äldst och vägleder genom en återställning med skyddsåtgärder:", + "steps": [ + { + "title": "1. Välj en backup", + "body": "En meny listar varje säkerhetskopia med dess tidsstämpel. Om det inte finns några säkerhetskopior avslutas flödet med ett tydligt meddelande.", + "tone": "blue" + }, + { + "title": "2. Valfri förhandsvisning", + "body": "Erbjuder att öppna den valda säkerhetskopian i en rullningsbar vy innan återställningen bekräftas. Ja är standardvalet — hoppa inte över detta på en fjärransluten värd.", + "tone": "blue" + }, + { + "title": "3. Säkerhetskopiering före återställning", + "body": "Tar ytterligare en säkerhetskopia av det aktuella tillståndet innan /etc/network/interfaces skrivs över. Återställning är i sig en destruktiv åtgärd, så den nya ögonblicksbilden före återställningen gör att du kan gå tillbaka om du valde fel säkerhetskopia.", + "tone": "amber" + }, + { + "title": "4. Applicera återställningen", + "body": "Skriver den valda säkerhetskopian över /etc/network/interfaces. Filen har ändrats på disk, men kärnans aktuella tillstånd speglar fortfarande den tidigare konfigurationen.", + "tone": "amber" + }, + { + "title": "5. Valfri omstart", + "body": "Frågar om systemctl restart networking ska köras nu. Avböj för att skjuta upp ändringen till nästa omstart.", + "tone": "emerald" + } + ], + "autoBackupTitle": "En återställning tar sin egen backup automatiskt", + "autoBackupBody": "När du väljer Återställ nätverkssäkerhetskopia tas alltid en ny ögonblicksbild av den aktuella konfigurationen innan den skrivs över. Om du återställer fel säkerhetskopia innehåller den senaste filen i /var/backups/proxmenux/ tillståndet du hade före återställningen." + }, + "restart": { + "heading": "Starta om nätverkstjänsten", + "body": "Kör systemctl restart networking efter en ja/nej-bekräftelse. Rapporterar resultatet med msg_ok / msg_error. De flesta flöden i menyn erbjuder redan en omstart i slutet. Detta alternativ används efter en manuell ändring eller om du tidigare avböjt den erbjudna omstarten.", + "warnTitle": "Kort frånkoppling garanteras; permanent frånkoppling är möjlig", + "warnBody": "Omstart av networking tar ned och konfigurerar om alla gränssnitt som deklareras i /etc/network/interfaces. SSH-sessioner via dessa gränssnitt bryts. Om den nya konfigurationen är ogiltig kommer nätverket inte tillbaka. Kör endast detta när:", + "warnItems": [ + "Du har konsolåtkomst / IPMI / iKVM redo, eller", + "Du befinner dig fysiskt bredvid maskinen, eller", + "Du har precis återställt en konfiguration som du vet fungerar och vill tillämpa den." + ] + }, + "manualRollback": { + "heading": "Manuell återgång från en konsol", + "intro": "Om du inte kan nå menyn (ingen SSH, dialog kraschar, ...) men kan nå en konsol, är återgång två kommandon:", + "outro": "Det är samma åtgärd som menyn utför, bara inmatad för hand. Om networking fortfarande inte startar, kontrollera journalctl -u networking -b för att hitta det bakomliggande syntax- eller drivrutinsfelet." + }, + "troubleshoot": { + "heading": "Felsökning", + "noneTitle": "”Inga säkerhetskopior hittades” vid återställning", + "noneBody": "Katalogen /var/backups/proxmenux/ finns inte eller innehåller inga interfaces_backup_*-filer. Kör Skapa nätverksbackup minst en gång eller en guidad reparation som tar en säkerhetskopia automatiskt. Bekräfta med ls /var/backups/proxmenux/.", + "unreachTitle": "Omstart av nätverket lyckas men värden är fortfarande onåbar", + "unreachBody": "Den nya konfigurationen är fel eller tilldelar inte din IP-adress för administration. Återställ från konsolen (se ovan). Använd sedan brygganalys och konfigurationsanalys för att förstå felet innan du tillämpar den igen.", + "emptyTitle": "Förhandsvisningen för återställning visar en tom fil", + "emptyBody": "Säkerhetskopian är tom (0 byte). Det händer om en kopiering misslyckats utan felmeddelande under en tidigare körning. Välj en äldre säkerhetskopia eller kör Skapa nätverksbackup igen nu om den aktuella konfigurationen fungerar." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Brygganalys och guidad reparation", + "href": "/docs/network/bridge-analysis", + "tail": " — Den vanligaste orsaken till att en säkerhetskopia hamnar här." + }, + { + "label": "Konfigurationsanalys och guidad rensning", + "href": "/docs/network/config-analysis", + "tailRich": " - skriver också till /var/backups/proxmenux/ automatiskt." + }, + { + "label": "Diagnostik", + "href": "/docs/network/diagnostics", + "tail": " — för att verifiera det aktuella tillståndet efter en återställning." + } + ] + } +} diff --git a/web/messages/sv/docs/network/bridge-analysis.json b/web/messages/sv/docs/network/bridge-analysis.json new file mode 100644 index 00000000..24d477a6 --- /dev/null +++ b/web/messages/sv/docs/network/bridge-analysis.json @@ -0,0 +1,132 @@ +{ + "meta": { + "title": "Brygganalys och guidad reparation | ProxMenux-dokumentation", + "description": "Upptäcker vmbrX-bryggor med saknade eller ogiltiga fysiska portar, vanligen efter omnumrering av PCI-enheter. Rapporterar dem med föreslagna skalkommandon och erbjuder guidad reparation i 5 steg med obligatorisk säkerhetskopia.", + "ogTitle": "Brygganalys och guidad reparation | ProxMenux-dokumentation", + "ogDescription": "Granska och reparera Proxmox bryggportar efter maskinvaruändringar med ett guidat flöde som ger säker återställning." + }, + "header": { + "title": "Brygganalys och guidad reparation", + "description": "Granskar varje vmbrX-brygga i /etc/network/interfaces, kontrollerar att dess fysiska portar finns och erbjuder stegvis reparation om de saknas. Analysen är skrivskyddad; reparationen kräver godkännande, visar en förhandsvisning och tar en säkerhetskopia.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "Läser varje brygga i /etc/network/interfaces, granskar dess bridge-ports-rad och kontrollerar att varje deklarerad port, till exempel enp3s0, finns på värden. Om en port saknas föreslås en ersättning och ändringen tillämpas med uttryckligt samtycke i flödet säkerhetskopiera, förhandsvisa, tillämpa, verifiera." + }, + "when": { + "heading": "När du behöver detta", + "intro": "Linux tilldelar förutsägbara gränssnittsnamn från PCI topologi och slot order. Flera händelser blandar dem och lämnar /etc/network/interfaces med hänvisning till namn som inte längre finns:", + "items": [ + "Lägga till eller ta bort ett PCIe-kort (en NIC, en GPU, en HBA, en NVMe-bärare).", + "Flytta ett befintligt kort till en annan PCIe slot.", + "BIOS-/UEFI-uppdateringar som numrerar om PCI-enheter.", + "Flytta startdisken till annan maskinvara." + ], + "outro": "Efter nästa start försöker bryggan ansluta till en port som inte längre finns. Den kan inte aktiveras och värden förlorar nätverksanslutningen. Beständiga gränssnittsnamn förhindrar att detta händer igen. Om du redan är utelåst visar den här sidan hur du återställer åtkomsten." + }, + "bigPicture": { + "heading": "Överblick", + "diagram1": { + "arrowLabel": "Steg 1", + "nodes": { + "sourceLabel": "/etc/network/interfaces", + "sourceDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports enp3s0", + "bridgeLabel": "Analysera (skrivskyddat)", + "bridgeDetail": "ip link show enp3s0\n→ finns inte", + "targetLabel": "Rapport + förslag", + "targetDetail": "❌ enp3s0: HITTADES INTE\nErsätt med: eno1\n(inga ändringar ännu)" + } + }, + "diagram2": { + "arrowLabel": "tillämpa med säkerhetskopia", + "nodes": { + "sourceLabel": "Guidad reparation", + "sourceDetail": "1. Säkerhetskopiera\n2. Visa aktuell konfiguration\n3. Förhandsvisa ändringar\n4. Tillämpa\n5. Verifiera", + "targetLabel": "/etc/network/interfaces (ny)", + "targetDetail": "auto vmbr0\niface vmbr0 inet static\n bridge-ports eno1" + } + } + }, + "step1": { + "heading": "Steg 1: analys (skrivskyddad)", + "intro": "När du väljer Analysera bryggkonfiguration avbryts verktyget direkt om värden inte använder den klassiska Debian/Proxmox-stacken. Annars granskas varje brygga och följande rapporteras:", + "items": [ + "Bryggnamn, aktuell status (UP / DOWN), tilldelad IP.", + "Varje deklarerad port och om den för närvarande finns (ip link show <port>).", + "För varje ogiltig port: en föreslagen ersättning (det första tillgängliga fysiska gränssnittet) och det exakta sed-kommandot för att tillämpa ändringen manuellt.", + "Broar utan bridge-ports alls och föräldralösa auto-poster (ingen matchande iface-block)." + ], + "readonlyTitle": "Garanti för skrivskyddad analys", + "readonlyBody": "Fram till denna punkt har skriptet inte kört ett enda kommando som ändrar systemet. Du kan lämna analysen öppen, kopiera de föreslagna sed-kommandona och tillämpa dem manuellt om du vill, eller godkänna nästa fråga för att gå vidare till den guidade reparationen." + }, + "step2": { + "heading": "Steg 2: guidad reparation (5 steg)", + "intro": "Startas bara om du godkänner frågan i slutet av analysen. Varje steg visar vad som kommer att ske och ber om bekräftelse innan flödet fortsätter.", + "steps": [ + { + "title": "1. Säkerhetskopia", + "body": "Kopierar /etc/network/interfaces till /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Den exakta sökvägen visas före och efter kopieringen, tillsammans med återställningskommandot.", + "tone": "blue" + }, + { + "title": "2. Granska nuvarande konfiguration", + "body": "Öppnar live /etc/network/interfaces i en rullningsbar dialog så att du kan se exakt vad som ska ändras.", + "tone": "blue" + }, + { + "title": "3. Förhandsgranska föreslagna ändringar", + "body": "Listar exakt vilka bryggor som berörs och vilka portbyten som planeras. Om ingen reparation längre behövs eftersom porten har dykt upp igen mellan kontrollerna avslutas flödet med ”Inga ändringar behövs.”", + "tone": "blue" + }, + { + "title": "4. Tillämpa ändringarna", + "body": "För varje drabbad bro kör sed -i \"/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/\" mot /etc/network/interfaces. Om en bro inte hade någon giltig ersättning tillgänglig, är ersättningen hoppad och rapporterad i steg 5.", + "tone": "amber" + }, + { + "title": "5. Verifiering", + "body": "Läser filen igen och bekräftar att varje bryggas nya port finns. Skriver ut återställningskommandot (cp <backup> /etc/network/interfaces). Erbjuder slutligen Starta om nätverket — godkänn bara om du har konsolåtkomst som reserv.", + "tone": "emerald" + } + ], + "restartTitle": "Omstart av nätverket ≠ riskfri återställning", + "restartBody": "Reparationen skrivs till disk oavsett om du startar om tjänsten. systemctl restart networking tillämpar ändringen nu, vilket kan bryta SSH-sessionen om den nya porten är fel. Om du avböjer börjar ändringen ändå gälla vid nästa omstart — kontrollera först den nya konfigurationen eller återställ med det utskrivna kommandot." + }, + "edits": { + "heading": "Vad blir redigerat (exakt)", + "body": "Endast bridge-ports-raden för varje berörd brygga. Andra direktiv (address, netmask, gateway, bridge-stp, …) lämnas orörda. Skriptet skapar eller tar aldrig bort iface-block i detta flöde — det sköts av konfigurationsanalys & rensning." + }, + "troubleshoot": { + "heading": "Felsökning", + "unsupportedTitle": "Analysen avbryts med ”Nätverksstacken stöds inte”", + "unsupportedBody": "Värden kör netplan, systemd-networkd eller NetworkManager. Verktyget stöder endast /etc/network/interfaces. Byt först till den klassiska stacken eller redigera konfigurationen med nätverkshanterarens egna verktyg, till exempel netplan apply.", + "noSuggestTitle": "Inget förslag trycks för en ogiltig port", + "noSuggestBody": "Värden har noll fria fysiska gränssnitt (allt är antingen redan i en annan bro eller inte existerar). Analysen faller tillbaka för att föreslå bridge-ports none så att bron åtminstone kan komma upp utan en port. Lägg till en NIC eller migrera portar mellan broar manuellt innan du kör.", + "stillDownTitle": "Reparationen slutförs men bron är fortfarande DOWN", + "stillDownBody": "sed uppdaterade filen men systemctl restart networking avböjdes. Kör kommandot manuellt när den nya konfigurationen har kontrollerats eller starta om. Om omstarten godkändes och bryggan fortfarande är DOWN, kör ip link show för att kontrollera att den nya porten finns och granska sedan journalctl -u networking för det verkliga felet: urkopplad kabel, länk som inte förhandlats fram eller port som redan tillhör en annan brygga.", + "lostSshTitle": "Jag förlorade SSH-åtkomst direkt efter att ha startat om nätverk", + "lostSshIntro": "Använd konsol / IPMI / iKVM för att nå värden. Återställ backup:", + "lostSshOutro": "Kör sedan analysen igen från konsolen för att ta reda på varför förslaget inte fungerade, oftast för att ersättningsporten inte är ansluten." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Beständiga gränssnittsnamn", + "href": "/docs/network/persistent-names", + "tail": " — binder namn till MAC-adresser så att problemet inte återkommer." + }, + { + "label": "Konfigurationsanalys och guidad rensning", + "href": "/docs/network/config-analysis", + "tailRich": " — för föräldralösa iface block (den andra hälften av samma problem)." + }, + { + "label": "Säkerhetskopiering och omstart av gränssnitt", + "href": "/docs/network/backup-restore", + "tail": " — manuella ögonblicksbilder och bläddring bland säkerhetskopior för återställning." + } + ] + } +} diff --git a/web/messages/sv/docs/network/config-analysis.json b/web/messages/sv/docs/network/config-analysis.json new file mode 100644 index 00000000..213f14af --- /dev/null +++ b/web/messages/sv/docs/network/config-analysis.json @@ -0,0 +1,122 @@ +{ + "meta": { + "title": "Konfigurationsanalys och guidad rensning | ProxMenux-dokumentation", + "description": "Upptäcker iface-block i /etc/network/interfaces som refererar till fysiska nätverkskort som inte längre finns på värden. Rapporterar dem och erbjuder guidad borttagning i 5 steg med obligatorisk säkerhetskopiering och förhandsvisning av varje block.", + "ogTitle": "Konfigurationsanalys och guidad rensning | ProxMenux-dokumentation", + "ogDescription": "Hitta och ta bort föräldralösa gränssnittsblock kvar av hårdvaruförändringar, med full förhandsgranskning och återgång." + }, + "header": { + "title": "Konfigurationsanalys och guidad rensning", + "description": "Går igenom varje iface-block i /etc/network/interfaces och kontrollerar att det underliggande fysiska nätverkskortet finns kvar. Rapporterar överblivna block efter maskinvaruändringar och erbjuder guidad borttagning med säkerhetskopia, förhandsvisning och bekräftelse för varje block.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "Identifierar iface-deklarationer vars stödjande NIC inte längre finns på värden (vanligtvis efter att ett nätverkskort har bytts ut eller tagits bort). Erbjuder sedan en guidad rensning som endast tar bort de föräldralösa blocken, med en säkerhetskopia, en förhandsgranskning per sektion och en explicit slutlig bekräftelse." + }, + "differs": { + "heading": "Hur det skiljer sig från brygganalys", + "headerAspect": "Aspekt", + "headerBridge": "Brygganalys", + "headerConfig": "Konfigurationsanalys", + "rows": [ + { + "aspect": "Granskar", + "bridge": "Bryggor (vmbrX) och deras bridge-ports linje", + "config": "Varje iface-block utom loopback, bryggor och bondgränssnitt" + }, + { + "aspect": "Detekterar", + "bridge": "Bryggor som refererar till en port som inte finns", + "config": "Fristående gränssnittsblock för nätverkskort som inte finns" + }, + { + "aspect": "Reparationsåtgärder", + "bridge": "Ersätter portnamnet i bridge-ports", + "config": "Ta bort hela iface-blocket" + }, + { + "aspect": "Exkluderar", + "bridge": "Ingenting – varje bro analyseras", + "config": "Bryggor (vmbrX) och bondgränssnitt (bondX) behålls alltid eftersom de är virtuella och det kan vara normalt att de saknas under start" + } + ], + "outro": "I praktiken kör du ofta båda i denna ordning: först konfigurationsanalys för att ta bort överblivna block, sedan brygganalys för att ändra portkopplingen för bryggor som använde det borttagna gränssnittet." + }, + "step1": { + "heading": "Steg 1: analys (skrivskyddad)", + "intro": "Avbryter omedelbart om värden inte använder den klassiska stacken. Annars listas alla konfigurerade gränssnitt utom loopback med sina tillstånd:", + "virtTitle": "Virtuella gränssnitt skyddas", + "virtBody": "Bryggor (vmbrX) och bondgränssnitt (bondX) är virtuella konstruktioner. De kan av giltiga skäl saknas vid inspektionen, till exempel en brygga som ännu saknar portar. Analysen föreslår därför aldrig att de tas bort, bara fysiska nätverkskort." + }, + "step2": { + "heading": "Steg 2: guidad rensning (5 steg)", + "intro": "Startas bara om du godkänner frågan i slutet av analysen. Varje steg kräver bekräftelse; om du avbryter avslutas flödet utan att skriva.", + "steps": [ + { + "title": "1. Säkerhetskopia", + "body": "Kopierar /etc/network/interfaces till /var/backups/proxmenux/interfaces_backup_<TIMESTAMP>. Sökvägen visas före och efter.", + "tone": "blue" + }, + { + "title": "2. Bekräfta borttagningslistan", + "body": "Listar exakt vilka fysiska gränssnittsblock som ska tas bort. Om nätverkskorten har dykt upp igen mellan analys och rensning, till exempel efter att ett kort satts i igen, avslutas flödet med ”Ingen rensning behövs.”", + "tone": "blue" + }, + { + "title": "3. Förhandsvisa de exakta blocken", + "body": "Öppnar en rullningsbar vy av varje iface-block som skulle raderas, exakt som det står i filen. Här kan du till exempel upptäcka en viktig up-hook som du vill behålla.", + "tone": "blue" + }, + { + "title": "4. Tillämpa borttagningen", + "body": "För varje föräldralös gränssnitt, kör sed -i \"/^iface $iface/,/^$/d\" /etc/network/interfaces. Det tar bort från matchande iface linje upp till nästa tomma rad.", + "tone": "amber" + }, + { + "title": "5. Verifiering", + "body": "Läser filen igen, listar det som togs bort och kontrollerar återstående gränssnitt. Skriver ut återställningskommandot. Startar inte om nätverket automatiskt — borttagning av ett oanvänt block kan säkert tillämpas vid nästa omstart utan att ändra det aktuella tillståndet.", + "tone": "emerald" + } + ], + "noRestartTitle": "Startar inte om nätverket automatiskt", + "noRestartBody": "Till skillnad från bryggreparation erbjuder rensningen ingen omstart av tjänsten. Att ta bort ett oanvänt gränssnittsblock påverkar inte den aktiva konfigurationen direkt, så en omstart är onödig och skulle innebära en onödig risk för anslutningen. Ändringen börjar gälla vid nästa manuella systemctl restart networking eller vid nästa systemomstart." + }, + "caveats": { + "heading": "Viktiga förbehåll", + "boundaryTitle": "Blockgränsen är den första tomma linjen", + "boundaryBody": "sed-mönstret raderar från iface <name> till nästa tomma rad. Om /etc/network/interfaces saknar tomma rader mellan block, vilket är ovanligt men möjligt efter manuell redigering, kan även nästa block tas bort. Därför är steg 3 obligatoriskt: granska förhandsvisningen innan du bekräftar.", + "tandemTitle": "Ett överblivet iface-block som används av en brygga upptäcks här, inte i brygganalysen", + "tandemBody": "Om vmbr0 deklarerar bridge-ports enp3s0 och enp3s0 också har ett eget block iface enp3s0 inet manual lämnar borttagning av enp3s0 en ogiltig referens i vmbr0. Kör brygganalys efter rensningen för att ändra eller ta bort bryggporten. De två flödena är avsedda att användas tillsammans." + }, + "troubleshoot": { + "heading": "Felsökning", + "notFoundTitle": "Analysen visar mitt nya nätverkskort som ”HITTADES INTE”", + "notFoundBody": "Kärnan ser enheten under ett annat namn än det som står i filen. Kör ip link show för att se det verkliga namnet. Det här är precis ett sådant fall där beständiga gränssnittsnamn skulle förhindra problemet framöver.", + "tooMuchTitle": "Städningen tog bort för mycket (ett block jag ville behålla)", + "tooMuchBody": "Återställ från backupen tryckt i steg 5:", + "tooMuchOutro": "Redigera filen för hand för att lägga till en tydlig tom linje mellan block innan du kör rengöring, eller ta bort problematiskt block manuellt.", + "bridgeBreakTitle": "En bro slutar fungera efter rengöring", + "bridgeBreakBody": "Bryggan använde en port vars iface-block just togs bort. Kör brygganalys så föreslås ett befintligt fysiskt gränssnitt för bryggan." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Brygganalys och guidad reparation", + "href": "/docs/network/bridge-analysis", + "tail": " — Den naturliga uppföljningen efter en rengöring." + }, + { + "label": "Beständiga gränssnittsnamn", + "href": "/docs/network/persistent-names", + "tail": " — stoppar föräldralösa block från att ackumulera efter varje hårdvaruförändring." + }, + { + "label": "Säkerhetskopiering och omstart av gränssnitt", + "href": "/docs/network/backup-restore", + "tail": " — manuella ögonblicksbilder och bläddring bland säkerhetskopior för återställning." + } + ] + } +} diff --git a/web/messages/sv/docs/network/diagnostics.json b/web/messages/sv/docs/network/diagnostics.json new file mode 100644 index 00000000..4baf1157 --- /dev/null +++ b/web/messages/sv/docs/network/diagnostics.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Diagnostik | ProxMenux Dokumentation", + "description": "Tre enstaka skrivskyddade diagnostikkontroller: Visa routningstabell, Testa anslutning och Avancerad diagnostik. Ren inspektion — skriver aldrig till /etc/network/interfaces och kör aldrig kommandon som ändrar systemet, med ett uttryckligt undantag: fullständig borttagning av NetworkManager, om det upptäcks, kräver ditt samtycke.", + "ogTitle": "Diagnostik | ProxMenux Dokumentation", + "ogDescription": "Skrivskyddad nätverksdiagnostik för Proxmox-värden: routning, nåbarhet och avancerad statistik." + }, + "header": { + "title": "Diagnostik", + "description": "Tre enstaka skrivskyddade kontroller av nätverkets aktuella tillstånd: routningstabell, anslutningstest och avancerad statistik. Ingen skriver till /etc/network/interfaces — säkra att använda när som helst, även via SSH. För interaktiv övervakning i realtid, se Övervakningsverktyg i realtid.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "Visar information om routning, nåbarhet och samlad statistik utan att ändra något. Använd verktyget för att kontrollera att nätverket fungerar eller identifiera dubbla IP-adresser. För kontinuerlig trafikövervakning, se Övervakningsverktyg i realtid; för att visa den obearbetade konfigurationsfilen, se Säkerhetskopiering och omstart av gränssnitt." + }, + "routing": { + "heading": "Visa routningstabell", + "body": "Listar alla rutter som kärnan använder för tillfället (ip route show) och markerar standardgatewayen. Användbart för att kontrollera att internettrafiken går genom rätt gränssnitt och att inga kvarvarande gamla rutter har företräde." + }, + "connectivity": { + "heading": "Testa anslutning", + "intro": "Skickar två ICMP-sonder till vart och ett av tre mål i tur och ordning och rapporterar resultatet för varje mål. Kör sedan nslookup google.com för att verifiera DNS-uppslagning oberoende av ICMP.", + "headerTest": "Test", + "headerTarget": "Mål", + "headerConfirms": "Vad den bekräftar", + "rows": [ + { + "test": "Google DNS", + "target": "8.8.8.8", + "confirms": "Extern nåbarhet via en välkänd anycast-IP-adress" + }, + { + "test": "Cloudflare DNS", + "target": "1.1.1.1", + "confirms": "Oberoende andra åsikt om Google blockerar ICMP" + }, + { + "test": "Gateway", + "target": "(standardrutt)", + "confirms": "L2 / L3-anslutning till den lokala routern" + }, + { + "test": "DNS-upplösning", + "target": "google.com", + "confirms": "DNS server är tillgänglig och svarar" + } + ], + "readingTitle": "Läsa resultatet", + "readingBody": "Om gatewaytestet lyckas men de offentliga DNS-målen inte svarar har du lokal anslutning men inget internet — kontrollera gatewayens anslutning uppströms. Om DNS-uppslagning misslyckas men ping till 8.8.8.8 fungerar ligger problemet i DNS-resolvern, inte i nätverket: kontrollera /etc/resolv.conf." + }, + "advanced": { + "heading": "Avancerad diagnostik", + "intro": "Sammanställer statistik för hela nätverket och utför en serie kontroller av vanliga problem. Rapporterar antalet aktiva anslutningar, lyssnande portar och totalt antal gränssnitt samt pekar uttryckligen ut två problematiska konfigurationer:", + "items": [ + "NetworkManager körs på en Proxmox-värd. NetworkManager och Proxmox ifupdown står i konflikt: båda försöker hantera gränssnitten, vilket gör att bryggor växlar mellan aktivt och inaktivt tillstånd eller inte kan aktiveras. Om NetworkManager upptäcks erbjuder verktyget en interaktiv bekräftelse för att stoppa, inaktivera och ta bort det helt (detta är den enda diagnostik som kan ändra systemet, och bara efter uttryckligt samtycke).", + "Dubbla IP-adresser på olika gränssnitt. Två gränssnitt med samma IPv4-adress orsakar intermittenta anslutningsproblem som är mycket svåra att felsöka inifrån en VM. Upptäcks via ip -4 addr show | sort | uniq -d." + ], + "nmTitle": "NetworkManager-prompten är den enda modifierande åtgärden här", + "nmBody": "Alla andra kontroller i Avancerad diagnostik är skrivskyddade. Borttagning av NetworkManager kräver en ja/nej-bekräftelse och kör systemctl stop / disable + apt-get purge -y network-manager. Om du avböjer ändras ingenting." + }, + "troubleshoot": { + "heading": "Felsökning", + "gwTitle": "Testa anslutning rapporterar ett gatewayfel men värden har internetåtkomst", + "gwBody": "Vissa routrar svarar inte på ICMP via sitt interna gränssnitt även om de vidarebefordrar trafiken korrekt. Testa med traceroute 8.8.8.8 från en konsol: om det andra hoppet är din internetleverantör och det offentliga DNS-testet lyckades fungerar gatewayen trots att ping misslyckades.", + "dupTitle": "Avancerad diagnostik fortsätter att rapportera dubbla IP-adresser efter att jag tagit bort en", + "dupBody": "Kontrollen granskar kärnans aktuella tillstånd, inte konfigurationsfilen. Kör ip -4 addr show för att kontrollera. Om dubbletten finns kvar har ett gränssnitt fortfarande adressen. Använd ip addr del <IP>/<mask> dev <iface> för att ta bort den från det aktuella tillståndet och redigera sedan /etc/network/interfaces om den också är beständigt konfigurerad." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Övervakningsverktyg i realtid", + "href": "/docs/network/monitoring", + "tail": " — interaktiva iftop / iptraf-ng / iperf3-startare." + }, + { + "label": "Brygganalys och guidad reparation", + "href": "/docs/network/bridge-analysis", + "tail": " — när en vmbrX saknar sin fysiska port." + }, + { + "label": "Konfigurationsanalys och guidad rensning", + "href": "/docs/network/config-analysis", + "tail": " — när ett gammalt nätverkskort fortfarande deklareras men inte längre finns." + }, + { + "label": "Säkerhetskopiering och omstart av gränssnitt", + "href": "/docs/network/backup-restore", + "tail": " — för att ta en ögonblicksbild av konfigurationen före varje ändring." + } + ] + } +} diff --git a/web/messages/sv/docs/network/index.json b/web/messages/sv/docs/network/index.json new file mode 100644 index 00000000..f8a424ea --- /dev/null +++ b/web/messages/sv/docs/network/index.json @@ -0,0 +1,107 @@ +{ + "meta": { + "title": "Nätverkshantering i Proxmox — Bryggor, bondning, diagnostik, reparation | ProxMenux", + "description": "Skrivskyddad diagnostik, analyser med efterföljande förslag och guidade reparationer för nätverksstacken i Debian / Proxmox (/etc/network/interfaces). Inspektera bryggor och bondgränssnitt, starta övervakningsverktyg i realtid, ge gränssnitt beständiga namn, säkerhetskopiera och starta om nätverket säkert — med obligatoriska säkerhetskopior och förhandsvisning steg för steg.", + "ogTitle": "Nätverkshantering i Proxmox — Bryggor, bondning, diagnostik, reparation", + "ogDescription": "Diagnostik, analys och guidade reparationer för Proxmox nätverksstack med obligatoriska säkerhetskopior och förhandsvisning steg för steg.", + "twitterTitle": "Nätverkshantering i Proxmox | ProxMenux", + "twitterDescription": "Bryggor, bondning, diagnostik och guidade reparationer för Proxmox nätverksstack med obligatoriska säkerhetskopior." + }, + "header": { + "title": "Nätverkshantering", + "description": "Skrivskyddad diagnostik, analysrapporter med efterföljande förslag och guidade reparationer för den klassiska nätverksstacken i Debian/Proxmox. Varje destruktiv åtgärd tar först en säkerhetskopia och visar de exakta ändringarna innan de tillämpas.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad menyn används till", + "body": "Inspektera, diagnostisera och vid behov reparera Proxmox-värdens nätverkskonfiguration utan att förlora fjärråtkomsten. Verktygen följer en princip: läs först, föreslå sedan och tillämpa endast med uttryckligt samtycke och en säkerhetskopia. Det finns ingen knapp som automatiskt åtgärdar allt." + }, + "openingMenu": { + "heading": "Öppna menyn", + "intro": "Välj Nätverk i ProxMenux huvudmeny. Då visas följande:", + "imageAlt": "Nätverkshanteringsmenyn med alternativ för diagnostik, övervakning, analys, reparation, beständiga namn och säkerhetskopiering" + }, + "safety": { + "heading": "Säkerhetsmodellen", + "body": "Att redigera nätverkskonfigurationen på en fjärransluten Proxmox-värd är ett av de enklaste sätten att låsa sig ute. ProxMenux tar hänsyn till det vid varje åtgärd. Verktygen delas in i tre nivåer efter hur de fungerar — välj det kort som motsvarar ditt mål:" + }, + "tiers": { + "readOnly": { + "title": "Skrivskyddad", + "body": "Ren inspektion. Kan inte ändra systemet under några omständigheter.", + "items": [ + "Routningstabell, anslutningstester, avancerad statistik", + "Trafikövervakning i realtid (iftop, iptraf-ng)", + "Bandbreddstest (iperf3)" + ] + }, + "analyze": { + "title": "Analysera, sedan föreslå", + "body": "Upptäcker problem, skriver ut en rapport med föreslagna skalkommandon och stannar sedan. Du avgör om du vill gå vidare till den guidade reparationen.", + "items": [ + "Analys av bryggkonfiguration", + "Nätverkskonfigurationsanalys" + ] + }, + "apply": { + "title": "Tillämpa med säkerhetskopia", + "body": "Ändrar systemet. Tar alltid först en tidsstämplad säkerhetskopia av den berörda filen och visar en förhandsvisning innan den skriver.", + "items": [ + "Beständiga gränssnittsnamn (.link-filer)", + "Manuell säkerhetskopiering, återställning och omstart av tjänsten" + ] + } + }, + "classicTitle": "Endast den klassiska stacken", + "classicBody": "Varje analys- och reparationsfunktion kontrollerar den aktiva nätverkshanteraren innan något ändras. Om värden kör netplan, systemd-networkd eller NetworkManager avbryts verktyget omedelbart med ett tydligt meddelande — menyn stöder endast den klassiska Debian/Proxmox-stacken i /etc/network/interfaces. Det är avsiktligt: att redigera en netplan-fil enligt regler för ifupdown skulle skada konfigurationen utan att det märks.", + "backups": { + "heading": "Var säkerhetskopiorna sparas", + "intro": "Varje guidad reparation, återställning eller manuell säkerhetskopia skriver en tidsstämplad kopia av /etc/network/interfaces till /var/backups/proxmenux/:", + "rollbackIntro": "Så återställer du manuellt från en konsol:" + }, + "readOnlySection": { + "heading": "Skrivskyddad inspektion", + "body": "Börja här när något verkar fel. Ren inspektion — skriver aldrig till /etc/network/interfaces och kör aldrig kommandon som ändrar systemet, med ett uttryckligt undantag: fullständig borttagning av NetworkManager, om det upptäcks, kräver ditt samtycke. Kan användas säkert via SSH när som helst.", + "options": [ + { + "title": "Diagnostik", + "description": "Tre enstaka skrivskyddade kontroller: Visa routningstabell, Testa anslutning och Avancerad diagnostik. Ren inspektion — skriver aldrig till systemet." + }, + { + "title": "Övervakningsverktyg i realtid", + "description": "Tre interaktiva verktygsstartare: iftop (bandbredd i realtid per värdpar), iptraf-ng (trafikövervakning med flera lägen) och iperf3 (bandbreddstest i server- eller klientläge)." + } + ] + }, + "analyzeSection": { + "heading": "Analysera, sedan föreslå", + "body": "Används när en inspektion eller ett verkligt avbrott pekar på ett konfigurationsproblem. Varje verktyg granskar den relevanta delen av /etc/network/interfaces, skriver ut en detaljerad rapport med det exakta skalkommando som skulle åtgärda varje problem och stannar. Om du därefter godkänner den valfria guidade reparationen säkerhetskopieras och förhandsvisas varje ändring innan den skrivs.", + "options": [ + { + "title": "Brygganalys och guidad reparation", + "description": "Upptäcker vmbrX-bryggor med saknade eller ogiltiga portar, vilket är vanligt efter omnumrering av PCI-enheter. Visar först en rapport och reparerar endast om du godkänner det guidade flödet med 5 steg." + }, + { + "title": "Konfigurationsanalys och guidad rensning", + "description": "Hittar fysiska gränssnitt som deklareras i /etc/network/interfaces men inte längre finns, alltså överblivna konfigurationer efter maskinvaruändringar. Rapporterar dem och erbjuder guidad borttagning." + } + ] + }, + "applySection": { + "heading": "Tillämpa med säkerhetskopia", + "body": "Dessa verktyg är avsedda att skriva till disk. Varje verktyg tar först en tidsstämplad säkerhetskopia av den berörda filen, och destruktiva alternativ som återställning och omstart kräver ett uttryckligt ja/nej-svar. Beständiga gränssnittsnamn börjar gälla vid nästa omstart, inte omedelbart, så inställningen kan planeras säkert även på en fjärransluten värd.", + "options": [ + { + "title": "Beständiga gränssnittsnamn", + "description": "Binder gränssnittsnamn (eno1, enp3s0, …) till MAC-adresser via systemds .link-filer. Namnen består när PCI-kort byter plats, kärnan uppgraderas och andra nätverkskort läggs till eller tas bort." + }, + { + "title": "Säkerhetskopiering och omstart av gränssnitt", + "description": "Ta en manuell ögonblicksbild av /etc/network/interfaces, bläddra bland och återställ tidigare säkerhetskopior, visa den aktuella konfigurationen och starta om nätverkstjänsten vid behov." + } + ] + }, + "consoleTitle": "Ha konsolåtkomst redo", + "consoleSubTitle": "Före varje reparation på en fjärransluten värd", + "consoleBody": "Om du är ansluten via SSH och bara har en åtkomstväg till värden bör du ha en reservväg redo innan du ändrar nätverket: fysisk konsol / IPMI / iKVM eller en annan dator i samma LAN. De guidade reparationerna är säkra och erbjuder alltid ett kommando för att återställa ändringarna, men en felkonfigurerad brygga eller en bruten länk kan ändå låsa dig ute tills du kommer åt konsolen." +} diff --git a/web/messages/sv/docs/network/monitoring.json b/web/messages/sv/docs/network/monitoring.json new file mode 100644 index 00000000..6ef3b059 --- /dev/null +++ b/web/messages/sv/docs/network/monitoring.json @@ -0,0 +1,136 @@ +{ + "meta": { + "title": "Övervakningsverktyg i realtid | ProxMenux-dokumentation", + "description": "Tre interaktiva startare för nätverksverktyg: iftop (bandbredd i realtid per värdpar), iptraf-ng (trafikövervakning med flera lägen) och iperf3 (bandbreddstest i server- eller klientläge). Varje verktyg installeras vid första användningen.", + "ogTitle": "Övervakningsverktyg i realtid | ProxMenux-dokumentation", + "ogDescription": "Interaktiv nätverksövervakning och bandbreddstestning för Proxmox värd: iftop, iptraf-ng, iperf3." + }, + "header": { + "title": "Övervakningsverktyg i realtid", + "description": "Tre interaktiva verktygsstartare för nätverksövervakning i realtid och bandbreddsmätning: iftop, iptraf-ng och iperf3. Varje verktyg installeras automatiskt via apt vid första användningen, körs i förgrunden och avslutas med den angivna tangenten. De ändrar inte värdens nätverkskonfiguration; iperf3 genererar testtrafik.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "Tre interaktiva övervakningsverktyg, var och en bakom sin egen meny. Första gången du startar ett installeras paketet tyst via apt-get. Efterföljande lanseringar börjar omedelbart. Inget av dessa verktyg ändrar värdnätverkskonfigurationen." + }, + "when": { + "heading": "När du ska använda vilken", + "headerQuestion": "Fråga", + "headerUse": "Använd", + "rows": [ + { + "question": "Vem mättar länken just nu?", + "use": "iftop" + }, + { + "question": "Hur ser fördelningen per protokoll, paketstorlekarna och TCP-flödena ut?", + "use": "iptraf-ng" + }, + { + "question": "Hur mycket bandbredd är faktiskt tillgänglig mellan två värdar?", + "use": "iperf3" + } + ] + }, + "iftop": { + "heading": "Nätverksanvändning i realtid (iftop)", + "body": "Bandbredd i realtid per värdpar (källa ↔ destination) — som top för trafik. Visar de största flödena överst med glidande medelvärden över 2/10/40 sekunder. Bästa verktyget för frågan ”Varför är min upplänk mättad?”.", + "exit": "Avsluta: tryck på q. ProxMenux visar en påminnelse före start.", + "keysTitle": "Användbara tangenter i iftop", + "keysBody": "n växlar DNS-uppslagning, p visar portnummer, P pausar visningen, t växlar radens riktning (skickat / mottaget / båda), 1/2/3 sorterar efter medelvärdet över 2 s / 10 s / 40 s. Se man iftop för hela listan." + }, + "iptraf": { + "heading": "Nätverksövervakningsverktyg (iptraf-ng)", + "intro": "En menystyrd trafikövervakare med flera lägen. iftop svarar på ”vem”, medan iptraf-ng svarar på ”vad”: antal byte och paket per protokoll, spårning av TCP-anslutningars tillstånd, histogram över paketstorlekar och LAN-aktivitet per station.", + "menuIntro": "Vid start visas en meny med fem alternativ:", + "headerMode": "Läge", + "headerUseFor": "Använd den för", + "rows": [ + { + "mode": "IP trafikövervakning", + "useFor": "Lista i realtid över TCP-/UDP-/ICMP-/övriga IP-flöden med byteräknare och anslutningstillstånd" + }, + { + "mode": "Allmän gränssnittsstatistik", + "useFor": "Samlat antal IPv4-/IPv6-/TCP-/UDP-/ICMP-/icke-IP-paket per nätverkskort" + }, + { + "mode": "Detaljerad gränssnittsstatistik", + "useFor": "Samma som ovan men för ett gränssnitt, med paketstorlek och hastighetsdetalj" + }, + { + "mode": "Statistisk fördelning", + "useFor": "Histogram över paketstorleksfördelningen per TCP-/UDP-port" + }, + { + "mode": "Övervakning av LAN-stationer", + "useFor": "Per-MAC trafikstatistik för den lokala sändningsdomänen" + } + ], + "exit": "Avsluta: tryck på x i valfri vy eller Q i huvudmenyn. ProxMenux visar en påminnelse före start.", + "logTitle": "Logga insamlade data till fil", + "logBody": "Varje läge erbjuder loggning av insamlad statistik till /var/log/iptraf-ng/. Om du behöver dokumentera en trafikspik kan du låta verktyget köra, logga till fil och granska filen efteråt i stället för att försöka läsa skärmen i realtid." + }, + "iperf3": { + "heading": "Bandbreddstest (iperf3)", + "intro1": "Mäter den faktiska TCP-genomströmningen mellan två värdar. Till skillnad från iftop / iptraf-ng, som observerar befintlig trafik, genererar iperf3 syntetisk trafik för att belastningstesta länken. Oumbärligt för frågor som ”Når min 10 GbE-länk verkligen 10 GbE?” eller ”Är nätverkskortet, switchen eller lagringen flaskhalsen?”.", + "intro2": "iperf3 är ett verktyg för två värdar: den ena kör som server och lyssnar på TCP-port 5201, den andra som klient som ansluter, skickar data och skriver ut hastigheten. ProxMenux-startaren frågar vilket läge du vill använda:", + "headerMode": "Läge", + "headerBehaviour": "Beteende", + "headerCli": "Motsvarande CLI-kommando", + "rows": [ + { + "mode": "Server", + "behaviour": "Lyssnar på TCP 5201 och skriver ut resultat för varje inkommande test. Slutar på Ctrl+C.", + "cli": "iperf3 -s" + }, + { + "mode": "Klient", + "behaviour": "Frågar efter serverns IP-adress eller värdnamn, ansluter, kör standardtestet på 10 sekunder och skriver ut rapporten.", + "cli": "iperf3 -c " + } + ], + "workflowIntro": "Typiskt arbetsflöde för att testa en 10 GbE länk mellan två Proxmox värdar:", + "workflow": [ + "Öppna nätverksmenyn på värd A → Bandbreddstest (iperf3) → välj Server.", + "Öppna samma menyalternativ på värd B → välj Klient → ange IP-adressen för värd A.", + "Vänta 10 sekunder. Jämför den rapporterade hastigheten med länkens teoretiska maximum." + ], + "sample": "Exempel på klientutdata:", + "flagsTitle": "Användbara manuella flaggor (kör från ett skal)", + "flagsBody": "-t 60 längre test (60 s i stället för 10 s), -P 4 4 parallella strömmar (mättar länken snabbare), -R omvänd riktning (server → klient), -u -b 100M UDP-test med 100 Mbit/s (mätning av jitter och paketförlust), -p 5202 annan port (flera tester parallellt). Se man iperf3 för hela listan.", + "firewallTitle": "Öppna brandväggsporten på servern", + "firewallBody": "Servern lyssnar på TCP 5201 som standard. Om du kör servern i en Proxmox-värd med en strikt brandvägg (datacenter eller värdnivå), låt inkommande TCP 5201 från klientens IP för testets varaktighet, stäng sedan den igen. Samma gäller för nftables / iptables på bara värdar." + }, + "install": { + "heading": "Första lanseringen installerar paketet", + "body": "Alla tre startare kontrollerar om programmet finns och kör annars apt-get update -qq && apt-get install -y <pkg>. Installationen sker utan utdata: menyn kan verka frusen i 10–30 sekunder första gången. Senare starter sker omedelbart." + }, + "troubleshoot": { + "heading": "Felsökning", + "hangTitle": "Installationen fastnar vid första start", + "hangBody": "Värden har inget internet eller apt repos är oåtkomliga. Avbryt med Ctrl+C, kör apt-get update manuellt för att se det faktiska felet (DNS, repo signatur, proxy ...), sedan komma tillbaka till menyn.", + "refusedTitle": "iperf3-klient: ”unable to connect to server: Connection refused”", + "refusedBody": "Antingen körs inte servern eller så blockerar dess brandvägg TCP 5201. Kontrollera på servern med ss -tlnp | grep 5201 — det ska visa att iperf3 lyssnar. Om den lyssnar men klienten fortfarande misslyckas, kontrollera brandväggarna på vägen mellan de två värdarna.", + "slowTitle": "iperf3 rapporterar mycket lägre hastighet än förväntat för länken", + "slowBody": "Vanliga orsaker i sannolikhetsordning: (1) en av värdarnas CPU begränsar hastigheten — prova iperf3 -c <target> -P 4 för att använda flera kärnor; (2) trafiken passerar en långsammare länk, till exempel en gigabitswitch mellan två 10 GbE-kort; (3) olika MTU — kontrollera ip link show i båda ändarna; (4) avstängd NIC offloading — se communityskriptet Disable NIC Offloading om du har ett Intel e1000e-kort.", + "noTrafficTitle": "iftop / iptraf-ng visar ingen trafik för en upptagen värd", + "noTrafficBody": "Standardfångst är på det första upptäckta gränssnittet. Ange rätt från skalet: iftop -i vmbr0 eller iptraf-ng -i vmbr0. Menyn launcher använder standarden; för icke-standardgränssnitt, kör från en konsol." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Diagnostik", + "href": "/docs/network/diagnostics", + "tail": " — de enstaka skrivskyddade kontrollerna (routning, anslutning och avancerad statistik)." + }, + { + "label": "Brygganalys och guidad reparation", + "href": "/docs/network/bridge-analysis", + "tail": " — när övervakningen visar att ett gränssnitt eller en brygga inte fungerar som det ska." + } + ] + } +} diff --git a/web/messages/sv/docs/network/persistent-names.json b/web/messages/sv/docs/network/persistent-names.json new file mode 100644 index 00000000..adaf92cf --- /dev/null +++ b/web/messages/sv/docs/network/persistent-names.json @@ -0,0 +1,130 @@ +{ + "meta": { + "title": "Beständiga gränssnittsnamn | ProxMenux-dokumentation", + "description": "Bind Proxmox-gränssnittsnamn (eno1, enp3s0, …) till MAC-adresser via systemds .link-filer så att namnen består vid omnumrering av PCI-enheter, kärnuppgraderingar och när andra nätverkskort läggs till eller tas bort.", + "ogTitle": "Beständiga gränssnittsnamn | ProxMenux-dokumentation", + "ogDescription": "Förhindra att Proxmox gränssnittsnamn ändras när du ändrar maskinvaran. Använder systemds .link-filer med matchning på MAC-adress." + }, + "header": { + "title": "Beständiga gränssnittsnamn", + "description": "Skapar en .link-fil för systemd per fysiskt nätverkskort som binder det kärntilldelade namnet till kortets MAC-adress. När detta tillämpats efter nästa omstart förblir gränssnittsnamnen stabila när du lägger till, tar bort eller flyttar PCIe-kort.", + "section": "Nätverk" + }, + "intro": { + "title": "Vad detta gör", + "body": "För varje fysisk NIC på värden, skriver en liten fil under /etc/systemd/network/10-<iface>.link som säger: \"enheten med denna MAC måste alltid kallas detta namn\" . systemd-udevd tillämpar regeln vid varje start, innan ifupdown läser /etc/network/interfaces." + }, + "problem": { + "heading": "Problemet detta löser", + "intro": "Linux härleder standardnamn för gränssnitt från PCI-topologin — eno1 = inbyggt nätverkskort, enp3s0 = kortet på PCI-buss 3, plats 0 osv. Namnschemat är deterministiskt vid samma maskinvaruplacering. Ändras placeringen kan namnen ändras:", + "items": [ + "Lägg till en GPU före ett befintligt nätverkskort → nätverkskortets bussnummer kan ändras → namnet ändras.", + "Flytta ett kort till en annan PCIe slot → namnändringar.", + "BIOS-/UEFI-uppdatering som numrerar om enheter → namnen kan ändras.", + "Ersätt ett felaktigt kort med samma modell i en annan slot → namnändringar." + ], + "outro": "Efter en sådan ändring refererar /etc/network/interfaces fortfarande till det gamla namnet. Bryggan kan inte aktiveras och värden förlorar nätverksanslutningen. Beständiga namn förhindrar att detta händer igen." + }, + "howWorks": { + "heading": "Hur det fungerar", + "arrowLabel": "per nätverkskort", + "nodes": { + "detectLabel": "Identifiera fysiska nätverkskort", + "detectDetail": "ls /sys/class/net/\nuteslut vmbr / bond /\ndocker / veth / wireguard …", + "readLabel": "Läs varje MAC", + "readDetail": "cat /sys/class/net/\n '<'iface'>'/address", + "writeLabel": "Skriv .link-fil", + "writeDetail": "/etc/systemd/network/\n 10-'<'iface'>'.link" + }, + "minimalIntro": "Varje genererad fil är avsiktligt minimal:", + "minimalOutro": "Vid start matchar systemd-udevd enheten på MAC-adress och tilldelar det begärda namnet innan andra komponenter (ifupdown, kärnans standardnamngivning) kommer åt den. Filprefixet 10- ser till att reglerna läses in tidigt, före standardfilen 99-default.link." + }, + "scope": { + "heading": "Vad som skrivs och vad som hoppas över", + "headerType": "Typ", + "headerBehaviour": "Beteende", + "headerWhy": "Varför", + "rows": [ + { + "type": "Inbyggt nätverkskort / PCIe-nätverkskort", + "behaviour": ".link-fil skrivs", + "why": "Motsvarar en verklig PCI-enhet — det fall verktyget är avsett för" + }, + { + "type": "Wi-Fi (phy80211)", + "behaviour": ".link-fil skrivs", + "why": "Har en verklig MAC-adress och gynnas av ett stabilt namn" + }, + { + "type": "Bryggor (vmbrX)", + "behaviour": "Överhoppad", + "why": "Virtuell; namn kommer från /etc/network/interfaces" + }, + { + "type": "Bondgränssnitt (bondX)", + "behaviour": "Överhoppad", + "why": "Virtuellt; bondgränssnittets namn anges av ifupdown" + }, + { + "type": "veth / docker0 / br-XXXX", + "behaviour": "Överhoppad", + "why": "Skapas vid behov av Docker / LXC — ingen beständig maskinvara" + }, + { + "type": "tap / fwpr / fwln", + "behaviour": "Överhoppad", + "why": "Skapas vid behov av Proxmox för varje VM/CT" + }, + { + "type": "WireGuard / Cilium / Tailscale", + "behaviour": "Överhoppad", + "why": "Programvarugränssnitt som hanteras av sina egna daemoner" + } + ] + }, + "safety": { + "heading": "Säkerhetsnät: tidigare .link-filer backas upp", + "intro": "Om /etc/systemd/network/ redan innehåller .link-filer från en tidigare körning eller andra verktyg kopieras de till en tidsstämplad säkerhetskopieringskatalog innan de nya skapas:", + "outro": "För att rulla tillbaka: kopiera filerna tillbaka från backupkatalogen och starta om.", + "rebootTitle": "Börjar gälla vid nästa omstart, inte omedelbart", + "rebootBody": "Ändringar i .link-filer tillämpas endast vid start, när udev räknar upp enheterna på nytt. Därför rapporterar verktyget ”Ändringarna tillämpas efter omstart”. Namnbyte på ett aktivt gränssnitt är riskabelt och försöks inte: en aktiv vmbr0 med medlemsgränssnitt skulle behöva konfigureras om atomärt. Åtgärden skjuts därför upp till nästa rena start." + }, + "afterReboot": { + "heading": "Efter omstarten", + "intro": "När namnen är beständigt bundna är arbetsflödet för framtida maskinvaruändringar enkelt:", + "items": [ + "Stäng av, ändra maskinvaran (lägg till kort, byt plats, …) och starta.", + "Varje NIC håller sitt tidigare namn eftersom dess MAC matchar en .link-fil.", + "Om ett nätverkskort ersätts med ett som har en annan MAC-adress får det ett standardnamn från kärnan (enp<bus>s<slot>). Kör menyn igen för att lägga till en .link-post för det nya kortets MAC-adress." + ] + }, + "troubleshoot": { + "heading": "Felsökning", + "emptyTitle": "\"Inga fysiska gränssnitt hittades\" efter att ha kört verktyget", + "emptyBody": "Kärnan ser inga gränssnitt som bygger på PCI / phy80211 på värden. Kontrollera med ls -l /sys/class/net/ — varje post ska ha antingen en symbolisk länk device (PCI) eller en post phy80211 (Wi-Fi). Om båda saknas för ett verkligt nätverkskort är drivrutinen inte inläst.", + "noChangeTitle": "Efter omstarten ändrades inte namnen som förväntat.", + "noChangeBody": "Kontrollera att filen finns och är korrekt formaterad: cat /etc/systemd/network/10-<iface>.link. Kontrollera sedan udev-loggarna: journalctl -u systemd-udevd -b | grep -i link. En vanlig orsak är en kvarvarande kärnparameter net.ifnames=0 som helt inaktiverar förutsägbara namn — ta bort den från /etc/default/grub, kör update-grub och starta om.", + "undoTitle": "Jag vill ångra beständig namngivning", + "undoBody": "Radera antingen .link-filerna (rm /etc/systemd/network/10-*.link) eller återställ dem från säkerhetskopieringskatalogen som skapades vid föregående körning. Starta om för att tillämpa ändringen." + }, + "related": { + "heading": "Relaterat", + "items": [ + { + "label": "Brygganalys och guidad reparation", + "href": "/docs/network/bridge-analysis", + "tailRich": " — återställningsvägen när namnen redan har ändrats." + }, + { + "label": "Konfigurationsanalys och guidad rensning", + "href": "/docs/network/config-analysis", + "tail": " — för att ta bort föräldralösa block kvar efter namnskiften." + }, + { + "label": "Diagnostik", + "href": "/docs/network/diagnostics", + "tailRich": " — bekräfta de nya namnen med Visa routningstabell efter omstart." + } + ] + } +} From d15876d94021ed850b27cf8c9dc3341a9e871951 Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:08:53 +0200 Subject: [PATCH 15/53] docs: review OCI manager documentation in all seven translations --- .github/workflows/test-i18n.yml | 10 + .../de/docs/oci-manager/architecture.json | 171 ++++++ .../de/docs/oci-manager/custom-image.json | 278 ++++++++++ .../de/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/de/docs/oci-manager/index.json | 213 ++++++++ .../de/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/de/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/de/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../de/docs/oci-manager/storage-network.json | 305 +++++++++++ .../en/docs/oci-manager/custom-image.json | 2 +- .../en/docs/oci-manager/lifecycle.json | 4 +- .../en/docs/oci-manager/storage-network.json | 2 +- .../es/docs/oci-manager/architecture.json | 111 +++- .../es/docs/oci-manager/custom-image.json | 191 ++++--- .../es/docs/oci-manager/hardware.json | 247 +++++++-- web/messages/es/docs/oci-manager/index.json | 165 ++++-- .../es/docs/oci-manager/lifecycle.json | 8 +- web/messages/es/docs/oci-manager/monitor.json | 8 +- web/messages/es/docs/oci-manager/stacks.json | 246 +++++++-- .../es/docs/oci-manager/storage-network.json | 229 ++++++-- .../fr/docs/oci-manager/architecture.json | 171 ++++++ .../fr/docs/oci-manager/custom-image.json | 278 ++++++++++ .../fr/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/fr/docs/oci-manager/index.json | 213 ++++++++ .../fr/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/fr/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/fr/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../fr/docs/oci-manager/storage-network.json | 305 +++++++++++ .../it/docs/oci-manager/architecture.json | 171 ++++++ .../it/docs/oci-manager/custom-image.json | 278 ++++++++++ .../it/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/it/docs/oci-manager/index.json | 213 ++++++++ .../it/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/it/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/it/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../it/docs/oci-manager/storage-network.json | 305 +++++++++++ .../pt/docs/oci-manager/architecture.json | 171 ++++++ .../pt/docs/oci-manager/custom-image.json | 278 ++++++++++ .../pt/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/pt/docs/oci-manager/index.json | 213 ++++++++ .../pt/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/pt/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/pt/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../pt/docs/oci-manager/storage-network.json | 305 +++++++++++ .../sk/docs/oci-manager/architecture.json | 171 ++++++ .../sk/docs/oci-manager/custom-image.json | 278 ++++++++++ .../sk/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/sk/docs/oci-manager/index.json | 213 ++++++++ .../sk/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/sk/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/sk/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../sk/docs/oci-manager/storage-network.json | 305 +++++++++++ .../sv/docs/oci-manager/architecture.json | 171 ++++++ .../sv/docs/oci-manager/custom-image.json | 278 ++++++++++ .../sv/docs/oci-manager/hardware.json | 392 ++++++++++++++ web/messages/sv/docs/oci-manager/index.json | 213 ++++++++ .../sv/docs/oci-manager/lifecycle.json | 499 ++++++++++++++++++ web/messages/sv/docs/oci-manager/monitor.json | 213 ++++++++ web/messages/sv/docs/oci-manager/stacks.json | 404 ++++++++++++++ .../sv/docs/oci-manager/storage-network.json | 305 +++++++++++ 60 files changed, 15788 insertions(+), 285 deletions(-) create mode 100644 web/messages/de/docs/oci-manager/architecture.json create mode 100644 web/messages/de/docs/oci-manager/custom-image.json create mode 100644 web/messages/de/docs/oci-manager/hardware.json create mode 100644 web/messages/de/docs/oci-manager/index.json create mode 100644 web/messages/de/docs/oci-manager/lifecycle.json create mode 100644 web/messages/de/docs/oci-manager/monitor.json create mode 100644 web/messages/de/docs/oci-manager/stacks.json create mode 100644 web/messages/de/docs/oci-manager/storage-network.json create mode 100644 web/messages/fr/docs/oci-manager/architecture.json create mode 100644 web/messages/fr/docs/oci-manager/custom-image.json create mode 100644 web/messages/fr/docs/oci-manager/hardware.json create mode 100644 web/messages/fr/docs/oci-manager/index.json create mode 100644 web/messages/fr/docs/oci-manager/lifecycle.json create mode 100644 web/messages/fr/docs/oci-manager/monitor.json create mode 100644 web/messages/fr/docs/oci-manager/stacks.json create mode 100644 web/messages/fr/docs/oci-manager/storage-network.json create mode 100644 web/messages/it/docs/oci-manager/architecture.json create mode 100644 web/messages/it/docs/oci-manager/custom-image.json create mode 100644 web/messages/it/docs/oci-manager/hardware.json create mode 100644 web/messages/it/docs/oci-manager/index.json create mode 100644 web/messages/it/docs/oci-manager/lifecycle.json create mode 100644 web/messages/it/docs/oci-manager/monitor.json create mode 100644 web/messages/it/docs/oci-manager/stacks.json create mode 100644 web/messages/it/docs/oci-manager/storage-network.json create mode 100644 web/messages/pt/docs/oci-manager/architecture.json create mode 100644 web/messages/pt/docs/oci-manager/custom-image.json create mode 100644 web/messages/pt/docs/oci-manager/hardware.json create mode 100644 web/messages/pt/docs/oci-manager/index.json create mode 100644 web/messages/pt/docs/oci-manager/lifecycle.json create mode 100644 web/messages/pt/docs/oci-manager/monitor.json create mode 100644 web/messages/pt/docs/oci-manager/stacks.json create mode 100644 web/messages/pt/docs/oci-manager/storage-network.json create mode 100644 web/messages/sk/docs/oci-manager/architecture.json create mode 100644 web/messages/sk/docs/oci-manager/custom-image.json create mode 100644 web/messages/sk/docs/oci-manager/hardware.json create mode 100644 web/messages/sk/docs/oci-manager/index.json create mode 100644 web/messages/sk/docs/oci-manager/lifecycle.json create mode 100644 web/messages/sk/docs/oci-manager/monitor.json create mode 100644 web/messages/sk/docs/oci-manager/stacks.json create mode 100644 web/messages/sk/docs/oci-manager/storage-network.json create mode 100644 web/messages/sv/docs/oci-manager/architecture.json create mode 100644 web/messages/sv/docs/oci-manager/custom-image.json create mode 100644 web/messages/sv/docs/oci-manager/hardware.json create mode 100644 web/messages/sv/docs/oci-manager/index.json create mode 100644 web/messages/sv/docs/oci-manager/lifecycle.json create mode 100644 web/messages/sv/docs/oci-manager/monitor.json create mode 100644 web/messages/sv/docs/oci-manager/stacks.json create mode 100644 web/messages/sv/docs/oci-manager/storage-network.json diff --git a/.github/workflows/test-i18n.yml b/.github/workflows/test-i18n.yml index 2b3e4685..7f4dfb51 100644 --- a/.github/workflows/test-i18n.yml +++ b/.github/workflows/test-i18n.yml @@ -9,15 +9,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' push: branches: [main, develop] paths: @@ -27,15 +32,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' workflow_dispatch: permissions: diff --git a/web/messages/de/docs/oci-manager/architecture.json b/web/messages/de/docs/oci-manager/architecture.json new file mode 100644 index 00000000..63cb18c1 --- /dev/null +++ b/web/messages/de/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "So wird ein OCI-Image umgesetzt | ProxMenux", + "description": "Vom Image-Repository und der Compose-Datei über eine überprüfbare Vorlage und einen Bereitstellungsplan zu einem nativen LXC unter Proxmox VE – ohne Docker im Container." + }, + "header": { + "title": "So wird ein OCI-Image umgesetzt", + "description": "Vom Image-Repository und der Compose-Datei über eine überprüfbare Vorlage und einen Bereitstellungsplan zu einem nativen LXC, ohne Docker darin zu installieren.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "Der Umsetzungsablauf", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Image-Repository", + "converter": "Konverter", + "template": "JSON-Vorlage", + "blockers": "Keine Hindernisse?", + "no": "Nein", + "yes": "Ja", + "review": "Prüfung / Overlay", + "plan": "Bereitstellungsplan", + "lxc": "Nativer LXC" + } + } + }, + { + "p": "Der Konverter liest das vom Projekt veröffentlichte Image und die Compose-Datei und erzeugt eine JSON-Vorlage. Lassen sich Anforderungen noch nicht umsetzen und verhindern dadurch die Installation, wird die Vorlage vor der Veröffentlichung im Katalog geprüft. Eine Datei mit geprüften Anpassungen, das Overlay, löst diese Hindernisse. Nur Vorlagen ohne verbleibende Hindernisse werden zur Installation angeboten." + } + ] + }, + { + "id": "template", + "title": "Was die Vorlage bewahrt", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Image-Identität", + "body": "Repository, fortlaufend aktualisiertes Tag, Architektur, ermittelter Digest und Quellrevision." + }, + { + "icon": "braces", + "title": "Container-Vertrag", + "body": "Entrypoint, Cmd, Umgebung, Benutzer, Arbeitsverzeichnis, Stoppsignal, Ports und Volumes." + }, + { + "icon": "layers", + "title": "Umsetzung für Proxmox VE", + "body": "Ressourcen, Sicherheit, Mountpoints, Geräte, sysctls und Zustandsprüfungen sowie die jeweils nötigen Anpassungen mit ihrer Begründung." + }, + { + "icon": "shield", + "title": "Kompatibilität", + "body": "Unterstützte Schlüssel, noch nicht umgesetzte Anforderungen, die die Installation verhindern, und der Status jeder Validierung." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "OCI liefert den Prozess, Compose die Umgebung", + "blocks": [ + { + "table": { + "headers": [ + "Quelle", + "Beispiel", + "Natives Ergebnis" + ], + "rows": [ + [ + "OCI-Metadaten", + "Entrypoint, Cmd, User", + "Proxmox VE importiert sie beim Erstellen des CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "LXC-Umgebungseinträge, mpN und devN" + ], + [ + "ProxMenux-Profil", + "GPU, Zustandsprüfung, Zugangsdaten", + "Rückfragen und geprüfte Anpassungen" + ], + [ + "Benutzer", + "VMID, Speicher, Netzwerk", + "Der Instanzvertrag" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Was während der Installation passiert", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Referenz auflösen", + "body": "Die Registry wird abgefragt, die Architektur des Hosts ausgewählt und der konkrete Digest festgehalten, auf den das fortlaufend aktualisierte Tag verweist." + }, + { + "title": "Herunterladen und prüfen", + "body": "Skopeo lädt das Image als OCI-Archiv herunter. Bevor etwas erstellt wird, wird jede Schicht anhand ihres Digests geprüft und entpackt. Ist der Download beschädigt, wird er einmal wiederholt, bevor die Installation abbricht." + }, + { + "title": "Dateisystem erstellen", + "body": "pct create erstellt das rootfs aus dem Archiv und übernimmt die offiziellen Prozessmetadaten des Images." + }, + { + "title": "Ressourcen anbinden", + "body": "Die deklarierten Volumes, Netzwerkeinstellungen, Umgebung, Geräte und Sicherheitsprofile werden eingebunden." + }, + { + "title": "Konsole", + "body": "Die Konsolenausgabe des Containers wird auf dem Host gespeichert. Die Proxmox-VE-Konsole öffnet eine Shell, sofern das Image eine enthält." + }, + { + "title": "Prüfen", + "body": "Beim ersten Start wird gewartet, bis der Container eine Adresse hat und der Dienst antwortet. Schlägt dies fehl, wird die Installation nicht als erfolgreich gemeldet." + }, + { + "title": "Konfiguration erfassen", + "body": "Die tatsächlich verwendete Konfiguration wird im Instanzvertrag gespeichert, den Aktualisierungen und Neuerstellungen verwenden." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Beispiel: ein Image mit /config und /downloads", + "intro": "Eine typische Compose-Definition und die daraus erzeugte Proxmox-VE-Konfiguration. Die von der Anwendung erwarteten Pfade bleiben unverändert.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (Auszug)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "mp0 ist ein zweiter Datenträger des Containers, der im gewählten Speicher vm-VMID-disk-N heißt. Er wird unter /config eingehängt und mit backup=1 in die Containersicherung aufgenommen. mp1 erstellt keinen Datenträger, sondern bindet das Host-Verzeichnis /srv/downloads unter /downloads im LXC ein. Port 8080 wird nicht weitergeleitet: Der LXC hat eine eigene Adresse, unter der der Dienst antwortet." + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/custom-image.json b/web/messages/de/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..225e88dd --- /dev/null +++ b/web/messages/de/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Image außerhalb des Katalogs installieren | ProxMenux", + "description": "Übernehmen Sie ein OCI-Image aus einer Compose-Datei, einer URL, einem docker run-Befehl oder einer Image-Referenz und prüfen Sie vor der Installation, welche Konfiguration ProxMenux umsetzen kann." + }, + "header": { + "title": "Image außerhalb des Katalogs installieren", + "description": "Ein eigenes Image wird anhand seiner Compose-Datei, eines docker run-Befehls oder allein seiner Referenz für LXC umgesetzt. Das Ergebnis wird zur Prüfung angezeigt, bevor ein LXC erstellt wird.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Derselbe Konverter und Installer", + "body": "Die Option Image außerhalb des Katalogs installieren nutzt denselben Konverter und Installer wie die Katalogvorlagen. Der Unterschied: Der Vertrag wird erst in diesem Moment aus der angegebenen Definition erzeugt und vor dem Erstellen eines LXC validiert." + } + } + ] + }, + { + "id": "sources", + "title": "Wie das Image beschrieben wird", + "intro": "Die erste Ansicht fragt, wie das Image beschrieben ist. Vier Möglichkeiten liefern eine Definition, die umgesetzt wird. Eine fünfte verwendet nur den Image-Namen. Drei weitere lesen eine bereits geschriebene oci-compose-Datei.", + "blocks": [ + { + "table": { + "headers": [ + "Option", + "Was eingelesen wird" + ], + "rows": [ + [ + "Compose-Datei im Terminal einfügen", + "Fügen Sie den YAML-Inhalt im Terminal ein und schließen Sie mit Ctrl+D ab." + ], + [ + "Compose-Datei auf diesem Host lesen", + "Ein Pfad auf dem Knoten, standardmäßig /root/docker-compose.yml. Die Datei muss existieren und darf höchstens 256 KiB groß sein." + ], + [ + "Compose-Datei von einer Adresse herunterladen", + "Eine http://- oder https://-Adresse, die den unverarbeiteten YAML-Inhalt bereitstellt. Es werden höchstens 256 KiB heruntergeladen." + ], + [ + "docker run-Befehl im Terminal einfügen", + "Der vom Projekt veröffentlichte Befehl. Ports, Umgebung, Volumes, Geräte, Capabilities, Benutzer, gemeinsam genutzter Speicher und die weiteren unterstützten Optionen werden umgesetzt." + ], + [ + "Nur die Image-Referenz, ohne Compose-Datei", + "Eine Referenz wie ghcr.io/user/application:latest. Die Registry wird abgefragt und die OCI-Metadaten des Images werden übernommen." + ], + [ + "oci-compose im Terminal einfügen", + "Fügen Sie den JSON-Inhalt im Terminal ein und schließen Sie mit Ctrl+D ab. Er wird nicht konvertiert, sondern geprüft und so installiert, wie er geschrieben ist." + ], + [ + "oci-compose-Datei auf diesem Host lesen", + "Eine JSON-Datei auf dem Knoten, standardmäßig /root/oci-compose.json, mit höchstens 2 MiB." + ], + [ + "oci-compose von einer Adresse herunterladen", + "Eine http://- oder https://-Adresse, die die JSON-Datei bereitstellt. Es werden höchstens 2 MiB heruntergeladen." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Menü zur Auswahl der Beschreibung eines Images außerhalb des Katalogs", + "caption": "Möglichkeiten zur Beschreibung eines Images außerhalb des Katalogs." + } + } + ] + }, + { + "id": "reference-only", + "title": "Was die Image-Referenz allein liefert", + "intro": "Das Image enthält seine Prozessmetadaten, aber nicht die gesamte Umgebung, die eine Compose-Datei gewöhnlich ergänzt.", + "blocks": [ + { + "table": { + "headers": [ + "Aus dem Image gelesen", + "Nicht im Image enthalten", + "Folge" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir und die enthaltene Umgebung", + "Variablen, die nur in der Dokumentation stehen", + "Werden bei der Installation oder über Ändern ergänzt" + ], + [ + "Vom Image deklarierte Volumes", + "Host-Verzeichnisse, die nur in der Compose-Datei angegeben sind", + "Weitere Pfade werden vor der Installation ergänzt" + ], + [ + "EXPOSE-Ports", + "URL, Protokoll oder funktionale Zustandsprüfung", + "Port und Prüfverfahren des Dienstes werden bestätigt" + ], + [ + "Architekturen und Digest in der Registry", + "Geräte, Privilegien oder externe Abhängigkeiten", + "Ohne eine entsprechende Anforderung in der Definition werden sie nicht aktiviert" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Vor der Installation prüfen", + "intro": "Nach dem Umsetzen der Definition zeigt ProxMenux seine Interpretation, bevor VMID, Ressourcen oder Speicher abgefragt werden.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analysieren", + "body": "Dienste, Image, Ports, Pfade, Umgebung, Geräte, Sicherheit und Zustandsprüfung werden eingelesen." + }, + { + "title": "Registry abfragen", + "body": "Das Image muss in seiner Registry vorhanden sein; die veröffentlichten Architekturen werden aufgelistet. Bei einer privaten Registry wird das dafür gespeicherte Konto verwendet." + }, + { + "title": "Nicht angewendete Optionen auflisten", + "body": "Labels, Docker-Netzwerke, Swarm-Einstellungen und andere Schlüssel ohne Wirkung in einem LXC werden aufgelistet." + }, + { + "title": "Optionen ohne sichere Entsprechung blockieren", + "body": "Schlüssel ohne sichere Entsprechung erscheinen unter Nicht umsetzbare Optionen; die Installation wird dann nicht fortgesetzt. Nichts wird stillschweigend verworfen." + }, + { + "title": "Geheime Werte abfragen", + "body": "Variablen, deren Namen auf Passwort, Token oder Schlüssel hinweisen, werden bei der Installation als sensible Werte abgefragt." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Zusammenfassung der Interpretation einer Compose-Datei durch ProxMenux", + "caption": "Zusammenfassung, die vor der Abfrage von VMID und Ressourcen erscheint." + } + }, + { + "p": "Nach Bestätigung der Zusammenfassung folgt derselbe Ablauf wie im Katalog: Name, Standard- oder erweiterter Modus, VMID, CPU, Arbeitsspeicher, Netzwerk, Start mit dem Knoten, persistente und zusätzliche Pfade, kompatible Geräte und abschließende Zusammenfassung." + } + ] + }, + { + "id": "examples", + "title": "Eingabebeispiele", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "Eine von Hand geschriebene oci-compose-Datei", + "intro": "oci-compose ist die JSON-Datei, aus der eine Anwendung installiert wird. Der Katalog enthält eine solche Datei pro Anwendung. Dasselbe Format kann von Hand geschrieben und über diese Option installiert werden.", + "blocks": [ + { + "p": "Bei einer handgeschriebenen Datei legt der Autor Image, Ports, Variablen, Volumes und Installationsfragen fest. ProxMenux ergänzt den Rest beim Einlesen. Das Format kann Dinge ausdrücken, die Compose nicht enthält: Fragetexte, bei der Installation erzeugte Passwörter, GPU-Profile, Eigentümer einzelner Volumes, die Prüfung des ersten Starts und abschließend angezeigte Zugangsdaten." + }, + { + "steps": { + "items": [ + { + "title": "Prüfen", + "body": "Dateien mit Fehlern werden nicht installiert. Jeder Fehler nennt das betroffene Feld. Bei ungültigem JSON werden Zeile und Spalte angegeben." + }, + { + "title": "Überprüfen", + "body": "Eine gültige Datei zeigt, was sie installiert: Image, Webadresse, Ports, persistente Pfade, abgefragte Variablen und geschriebene Dateien." + }, + { + "title": "Installieren", + "body": "Die Installation erfolgt wie bei jeder Kataloganwendung mit Standard- oder erweiterten Einstellungen." + } + ] + } + }, + { + "calloutWarning": { + "title": "Eine Datei, die Privilegien festlegt", + "body": "oci-compose legt fest, was im Container eingehängt wird und mit welchen Privilegien. Die Prüfung zeigt dies vor der Installation. Installieren Sie nur Dateien aus vertrauenswürdigen Quellen." + } + }, + { + "p": "Die README der OCI-Engine beschreibt das Format Block für Block und enthält zwei vollständige Beispiele. Auch ein Installationsdatensatz kann so eingelesen werden: Verwendet wird nur die ursprüngliche Vorlage, keine der damaligen Antworten." + } + ] + }, + { + "id": "private-registries", + "title": "Images aus privaten Registries", + "intro": "Ein Image aus einer privaten Registry, etwa ein privates GitHub-Paket oder ein privates Docker-Hub-Repository, wird mit einem in ProxMenux gespeicherten Konto gelesen.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Die Registry verweigert den Image-Zugriff", + "body": "Meldet die Registry, dass ein Konto benötigt wird, bietet die Prüfung eine Anmeldung an und liest das Image danach erneut." + }, + { + "title": "Anmelden", + "body": "Benutzername und Passwort oder Zugriffstoken mit Download-Berechtigung. Die Registry prüft sie; gespeichert werden sie nur bei erfolgreicher Anmeldung." + }, + { + "title": "Installieren und aktuell halten", + "body": "Dieses Konto wird beim Herunterladen während der Installation und ebenso für Aktualisieren, Ändern, Neuerstellen, Watchdog und die Update-Prüfung des Monitors verwendet – auf jedem Cluster-Knoten." + } + ] + } + }, + { + "p": "Registry-Konten im Hauptmenü von OCI manager Apps zeigt gespeicherte Konten nach Registry und Benutzer, niemals ihre geheimen Zugangsdaten. Pro Konto können Images geprüft, heruntergeladen oder installiert sowie Zugangsdaten aktualisiert oder entfernt werden." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io): Als Benutzer dient das GitHub-Konto, als Passwort ein persönliches Zugriffstoken mit read:packages-Berechtigung.", + "Docker Hub (docker.io): Als Benutzer dient das Docker-Hub-Konto, als Passwort ein Zugriffstoken mit Leseberechtigung.", + "Andere Registries, auch mit eigenem Port wie registry.example.com:5000: Benutzername und Passwort oder Token dieser Registry." + ] + } + }, + { + "calloutInfo": { + "title": "Wo die Konten gespeichert werden", + "body": "Unter /etc/pve/priv/proxmenux/registry-auth.json, nur für root lesbar, im selben Ordner wie die Speicherpasswörter von Proxmox. /etc/pve wird von allen Cluster-Knoten gemeinsam genutzt. Ein auf einem Knoten hinzugefügtes Konto gilt daher auf allen. Die Host-Sicherung enthält die Datei; die Deinstallation von ProxMenux entfernt sie auf allen Knoten." + } + } + ] + }, + { + "id": "limits", + "title": "Nicht installierbare Definitionen", + "blocks": [ + { + "list": { + "items": [ + "Ein Dockerfile ohne veröffentlichtes Image: Das Image muss zuerst gebaut und in einer OCI-Registry veröffentlicht werden.", + "Eine Compose-Datei mit mehreren Diensten: Diese Option installiert jeweils einen Dienst in einem Container, auch wenn mehrere Dienste dasselbe Image verwenden.", + "configs, externe Geheimnisse oder Geräteformate, die sich nicht eindeutig umsetzen lassen.", + "Shell-Ersetzungen wie $(command): Stattdessen muss der resultierende Wert angegeben werden.", + "Nicht erkannte docker run-Optionen und --env-file: Verwenden Sie stattdessen eine Compose-Datei mit expliziten Werten." + ] + } + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/hardware.json b/web/messages/de/docs/oci-manager/hardware.json new file mode 100644 index 00000000..6f07b1aa --- /dev/null +++ b/web/messages/de/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Geräte und Beschleunigung | ProxMenux", + "description": "Wie OCI manager Apps GPU, NVIDIA, Coral, USB, FUSE und weitere Geräte als geprüfte native Proxmox-VE-Ressourcen für einen OCI-Container bereitstellt." + }, + "header": { + "title": "Geräte und Beschleunigung", + "description": "GPU, NVIDIA, Coral, USB, FUSE und Blockgeräte werden validierte native Proxmox VE-Ressourcen des Containers.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "Das Gerät, das die Anwendung benötigt, nicht der gesamte Host", + "blocks": [ + { + "p": "Ein in der Compose-Datei oder im Anwendungsprofil angefordertes Gerät wird als konkreter devN-Eintrag oder LXC-Mount eingerichtet. Das Anfordern einer GPU, eines USB-Geräts oder eines Coral-Beschleunigers macht den Container nicht privilegiert." + }, + { + "flow": { + "nodes": [ + { + "label": "Host-Inventar", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "Hersteller und\nBerechtigungen geprüft" + }, + { + "label": "LXC", + "detail": "dasselbe Gerät\neffektive GID" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "Woher die Geräteanforderung kommt", + "blocks": [ + { + "table": { + "headers": [ + "Quelle", + "Was gelesen wird", + "Was der Installateur macht" + ], + "rows": [ + [ + "Docker Compose", + "devices, group_add, deploy.resources und NVIDIA Anforderungen", + "Jede Anforderung wird zu einer Geräteanforderung, die zur Überprüfung angezeigt wird" + ], + [ + "Katalogprofil", + "Die tatsächliche GPU-, Coral-, OpenCL-, USB- oder FUSE-Unterstützung der Anwendung", + "Es werden nur die für dieses Image validierten Optionen angeboten" + ], + [ + "Bildmetadaten und Dokumentation", + "VA-API, Selkies, LinuxServer Mods oder die NVIDIA Laufzeit", + "Die dokumentierten Variablen und die Vorbereitung werden hinzugefügt" + ], + [ + "Auswahl der Nutzer", + "Nur CPU, Intel/AMD, OpenCL, NVIDIA oder ein optionales Gerät", + "Die Auswahl wird im Instanzvertrag gespeichert" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Erkannte Geräte werden nicht automatisch eingebunden", + "body": "Der Host wird inventarisiert, aber nur Geräte, die in der Compose-Datei oder in einem kompatiblen Profil deklariert sind, werden angeboten und angehängt. Ein GPU, USB-Dongle oder Coral auf dem Host ist nicht jedem LXC ausgesetzt." + } + } + ] + }, + { + "id": "identify", + "title": "Wie das Host-Gerät identifiziert wird", + "intro": "Bevor der LXC geändert wird, wird das Gerät auf dem Host gelesen und mit dem gewählten Profil verglichen.", + "blocks": [ + { + "table": { + "headers": [ + "Typ", + "Identität", + "Validierung" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* und /sys/class/drm/NODE/device/vendor", + "Ein Zeichengerät mit dem Anbieter 0x8086 (Intel) oder 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "Der Render-Knoten plus /dev/kfd, wenn das Profil es benötigt", + "Existenz, Typ, Anbieter, Berechtigungen und deklarierte Kompatibilität" + ], + [ + "AMD ROCm", + "Der Renderknoten, /dev/kfd und die Generation, die der Compute-Treiber in /sys/class/kfd/kfd/topology/nodes/*/properties meldet", + "Eine GPU-Generation, für die das ROCm-Image der Anwendung passende Rechenkernel enthält — die dort genannten RDNA2-GPUs und neuere — wird unverändert angeboten. Andere GPUs dieser Familien, etwa Radeon 680M und 780M, werden als experimentell angeboten und nie vorausgewählt: ROCm unterstützt sie nicht offiziell, die Anwendung meldet sie als Generation ihrer Familie (HSA_OVERRIDE_GFX_VERSION), und vor dem Download des größeren Images ist eine Bestätigung erforderlich. Ältere GPUs, etwa die integrierte Grafik eines Ryzen 5000U, werden nicht angeboten; Immich führt die Erkennung weiter auf der CPU aus." + ], + [ + "NVIDIA", + "nvidia-smi und nvidia-container-cli", + "GPU, UUID, PCI-Bus, Treiberversion, Toolkit, /dev/nvidia*-Knoten, Binärdateien und Bibliotheken" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N und sein Link in /sys/dev/char/MAJOR:MINOR", + "Zeichengeräteknoten, Major-/Minor-Nummern, Eigentümer, GID und Berechtigungen" + ], + [ + "USB und serielle", + "/dev/ttyUSB*, /dev/ttyACM* oder /dev/bus/usb/BBB/DDD", + "Zeichenknoten; für USB auch Anbieter, Produkt und Seriennummer, wenn sysfs sie veröffentlicht" + ], + [ + "KVM, TUN, FUSE, Video und generische SCSI", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN oder /dev/sgN", + "Unterstützter Pfad, Knotentyp und effektive Berechtigungen" + ], + [ + "Optisches Laufwerk", + "/dev/srN", + "Ein Blockgerät" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Was passiert während der Installation", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Das Template bietet seine Profile", + "body": "Nur CPU, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL oder NVIDIA. Die Optionen gehören zum Image, nicht zu einem gemeinsamen Menü." + }, + { + "title": "Ein Profil wird gewählt", + "body": "Es definiert die Geräteknoten, die Umgebung, die Mods oder die Laufzeit, die die Anwendung benötigt." + }, + { + "title": "Ein Pfad wird vorgeschlagen", + "body": "Für DRM /dev/dri/renderD128, das auf einem Host mit mehreren Renderknoten geändert werden kann. Für USB oder seriell wird der konkrete Knoten ausgewählt." + }, + { + "title": "Validierung", + "body": "Existenz, Typ, erlaubter Anbieter, Berechtigungen und GID werden überprüft. Eine Abweichung stoppt die Operation." + }, + { + "title": "Der Vertrag ist geschrieben", + "body": "Pfad, Modus, GID, Schreibzugriff und Profil werden für Updates und Neuerstellungen aufgezeichnet." + }, + { + "title": "Einbinden und testen", + "body": "pct set fügt den devN-Eintrag hinzu und der Zugriff wird dann im LXC überprüft. LinuxServer-Images werden auch als Benutzer abc überprüft." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Wie es in der LXC-Konfiguration erscheint", + "intro": "Beispiele: dev0 und dev1 sind die freien Slots, die Proxmox VE zuweist, und renderD128, apex_0 und die GID hängen von der Hardware des Knotens ab.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Ein spezifisches USB-Gerät", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "Die GID wird mit stat auf dem Host gelesen und in den devN-Eintrag geschrieben; es wird nicht angenommen, dass die Gruppen render und video eine feste Nummer haben. Das Gerät behält den gleichen /dev-Pfad innerhalb des LXC, wo die anwendungseigenen Mechanismen danach suchen." + } + ] + }, + { + "id": "profiles", + "title": "Profile, die ein Image bieten kann", + "blocks": [ + { + "table": { + "headers": [ + "Profil", + "Umsetzung", + "Angeboten, wenn" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "/dev/dri Renderknoten", + "Die Anwendung unterstützt Videobeschleunigung" + ], + [ + "OpenCL", + "Renderknoten, /dev/kfd bei Bedarf und der offizielle Mod", + "Das Image oder Profil dokumentiert es" + ], + [ + "NVIDIA", + "Treibergeräte und Bibliotheken des Hosts", + "Der Host hat einen funktionierenden Treiber und das NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 oder USB-Bus", + "Das Profil erklärt Coral-Unterstützung (Frigate)" + ], + [ + "USB, seriell, FUSE", + "Ein einzelnes Gerät, ein validierter Baum oder eine LXC-Funktion", + "Der Vertrag verlangt es" + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Voraussetzung auf dem Knoten: NVIDIA Container Toolkit", + "body": "Ein funktionierender Treiber auf Proxmox VE reicht nicht aus, um einem OCI-Image eine NVIDIA-GPU zu geben. OCI Manager Apps verwendet nvidia-container-cli aus dem NVIDIA Container Toolkit, um die Geräte zu identifizieren und die Binärdateien und Bibliotheken zu erhalten, die dem geladenen Treiber entsprechen." + } + }, + { + "p": "ProxMenux NVIDIA installer installiert das NVIDIA Container Toolkit aus dem offiziellen NVIDIA-Repository zusammen mit dem Treiber. Es überprüft seine vier Pakete, validiert nvidia-container-cli und zeichnet das Ergebnis im Change Journal von Audit & Report auf." + }, + { + "p": "Diese beiden Befehle auf dem Host zeigen an, ob der Treiber und das Toolkit verfügbar sind:" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "Auf einem Host, auf dem der Treiber auf andere Weise installiert wurde, wird das Toolkit aus dem offiziellen stabilen Repository installiert:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "Das von OCI manager Apps verwendete Inventar stammt aus der Ausgabe von:" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "Docker Laufzeitkonfiguration ist nicht beteiligt", + "body": "Die Container sind native LXCs ohne Docker-Daemon. Daher spielt nvidia-ctk runtime configure --runtime=docker keine Rolle: ProxMenux fragt nvidia-container-cli direkt ab und trägt Geräte und Mounts in die LXC-Konfiguration ein. Die aktuellen Befehle und unterstützten Plattformen stehen in der Installationsanleitung zum NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventar aus dem Treiber", + "body": "nvidia-container-cli listet Geräteknoten, Binärdateien, Firmware und Bibliotheken des installierten Treibers auf." + }, + { + "icon": "refresh", + "title": "Keine feste Version", + "body": "Die Vorlage nennt keine Bibliotheksdateien. Das Profil wird vom aktuellen Host generiert." + }, + { + "icon": "shield", + "title": "Nur lesbare Mounts", + "body": "Die Hostbibliotheken werden schreibgeschützt montiert, anstatt in den Container kopiert zu werden." + }, + { + "icon": "hardDrive", + "title": "Treiberänderungen", + "body": "Nach einem Treiberwechsel wird das Inventar erneut generiert, bevor die betroffenen LXCs starten." + } + ] + } + }, + { + "code": { + "title": "NVIDIA Ergebnis (vereinfacht)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Nur /dev/nvidia0 zu übergeben ist nicht genug. Die Benutzerraumkomponenten des geladenen Treibers werden schreibgeschützt montiert, und nvidia-smi läuft dann innerhalb des LXC, um GPU, UUID, PCI-Bus und Version mit dem Host-Inventar zu vergleichen." + } + ] + }, + { + "id": "usb", + "title": "USB, serielle und USB Coral", + "blocks": [ + { + "calloutWarning": { + "title": "USB-Nummerierung kann sich ändern", + "body": "Ein Pfad wie /dev/bus/usb/003/004 kann sich ändern, wenn das Gerät wieder angeschlossen wird oder der Host neu gestartet wird. Das Profil zeichnet Anbieter, Produkt und Seriennummer auf, wenn sie verfügbar sind, aber eine neue Busadresse wird nicht automatisch neu zugeordnet." + } + }, + { + "p": "Ein Peripheriegerät wird durch seinen konkreten Knoten angegeben: /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 oder /dev/bus/usb/BBB/DDD. Die Weitergabe des gesamten /dev wird nicht akzeptiert. Coral wird nur Anwendungen angeboten, deren Profil es deklariert." + } + ] + }, + { + "id": "trees", + "title": "Gerätebäume und LXC-Funktionen", + "blocks": [ + { + "table": { + "headers": [ + "Anforderung", + "Umsetzung", + "Umfang" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd oder /dev/bus/usb", + "Jeder Zeichenknoten des Baumes erhält seinen eigenen devN-Eintrag mit dem Host-Modus und GID", + "Nur der angeforderte Baum, nicht der Rest von /dev" + ], + [ + "/dev/fuse", + "Der Knoten und, wenn das Profil es benötigt, die fuse=1-Funktion", + "FUSE allein veröffentlicht keine Mounts für andere LXCs" + ], + [ + "/dev/net/tun", + "Ein devN-Eintrag auf demselben Pfad innerhalb des LXC", + "Die VPN- oder Netzwerkkonfiguration bleibt in der Anwendung" + ], + [ + "/dev/kvm", + "Ein validierter devN-Eintrag", + "Angeboten nur, wenn der Vertrag es verlangt" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Bestätigungen nach Risikograd", + "blocks": [ + { + "p": "Konkrete Geräte, optionale Privilegien, erforderliche Privilegien, AppArmor- oder seccomp-Entspannung und der Zugriff auf den Host-PID-Namespace werden als separate Fälle behandelt, nicht unter einem generischen privilegierten Label. Jede Option mit einem Risiko wird während der Installation erklärt und bestätigt." + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/index.json b/web/messages/de/docs/oci-manager/index.json new file mode 100644 index 00000000..c46433f7 --- /dev/null +++ b/web/messages/de/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Führen Sie offizielle OCI-Images als native LXC-Container unter Proxmox VE aus – mit persistenten Daten, Gerätezugriff, Anwendungen aus mehreren Containern und transaktionalen Aktualisierungen." + }, + "header": { + "title": "OCI manager Apps", + "description": "Offizielle OCI-Images laufen als native LXC-Container unter Proxmox VE. Das vom Maintainer veröffentlichte Image bleibt unverändert; ProxMenux bildet die Umgebung nach, die Docker Compose dafür eingerichtet hätte.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "Ein OCI-Image, ein nativer LXC", + "body": "In den Containern läuft keine Docker Engine. Proxmox VE importiert das Dateisystem und die OCI-Metadaten des Images. Die Anwendung wird zum Hauptprozess eines nativen LXC und lässt sich mit denselben Werkzeugen verwalten wie andere Container auf dem Knoten." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Quelle", + "detail": "OCI-Image\nCompose und Dokumentation" + }, + { + "label": "ProxMenux", + "detail": "JSON-Vertrag\nValidierung und Planung" + }, + { + "label": "Proxmox VE", + "detail": "Nativer LXC\nVolumes und Geräte" + } + ], + "caption": "Die Anwendung wird nicht neu gebaut. Ihre Umgebung wird deklarativ beschrieben und nachgebildet." + } + } + ] + }, + { + "id": "where", + "title": "Wo Sie es finden", + "intro": "Öffnen Sie auf dem Proxmox-Knoten als root den Eintrag OCI manager Apps (Technology Preview) im ProxMenux-Hauptmenü. Die erste Ansicht bietet:", + "blocks": [ + { + "list": { + "items": [ + "Anwendungen suchen – den gesamten Katalog nach Namen durchsuchen.", + "Alle Anwendungen – den vollständigen Katalog samt Anzahl der Anwendungen anzeigen.", + "Installierte OCI-Anwendungen verwalten – vorhandene Installationen aktualisieren, ändern, entfernen oder wiederherstellen.", + "Registry-Konten – Konten zum Installieren und Aktualisieren von Images aus privaten Registries verwalten.", + "Image außerhalb des Katalogs installieren – eine Compose-Datei, einen docker run-Befehl oder eine Image-Referenz übernehmen und anpassen.", + "Die Kategorien des Katalogs, jeweils mit der Anzahl ihrer Anwendungen." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Hauptansicht von OCI manager Apps mit Suche, Gesamtkatalog, Anwendungsverwaltung, eigenen Images und Katalogkategorien.", + "caption": "Hauptansicht von OCI manager Apps." + } + }, + { + "p": "Nach der Auswahl einer Anwendung sehen Sie deren Beschreibung, das verwendete Image und zwei Installationsarten. Mit Standardeinstellungen installieren fragt nur die grundlegenden Entscheidungen ab: Speicher, Datenablage, Adresse und Start. Mit erweiterten Einstellungen installieren führt nacheinander durch alle Optionen: VMID und Ressourcen, Speicher für jeden Pfad, Netzwerk-Bridge und Geräte." + }, + { + "p": "Tatsächlich getestete Anwendungen zeigen in dieser Ansicht Geprüft und sind in den Listen mit ✓ gekennzeichnet. Das gilt für Tests im ProxMenux-Labor ebenso wie für Tests durch die Community." + } + ] + }, + { + "id": "beta", + "title": "Warum es als Technology Preview gekennzeichnet ist", + "blocks": [ + { + "p": "OCI manager Apps verwendet die Bezeichnung Technology Preview, die Proxmox VE den aus OCI-Images erstellten Anwendungscontainern zuweist. Deren Unterstützung wurde mit Proxmox VE 9.1 eingeführt. Die Bezeichnung betrifft die zugrunde liegende Proxmox-Technik, nicht die ProxMenux-Engine: Das Verhalten dieser Container kann sich zwischen Proxmox-VE-Versionen noch ändern. Die Kennzeichnung bleibt bestehen, bis Proxmox VE diese Unterstützung als stabil einstuft." + }, + { + "p": "Die Funktionen von OCI manager Apps werden im ProxMenux-Labor getestet: Installation, Aktualisierung, Änderung, Neuerstellung, Sicherung, Wiederherstellung, Migration und Watchdog. Jede Anwendung wird separat geprüft. Tatsächlich getestete Anwendungen tragen die Kennzeichnung Geprüft und ein ✓ in den Listen." + } + ] + }, + { + "id": "translation", + "title": "Wie Docker Anforderungen beschreibt und OCI manager Apps sie umsetzt", + "blocks": [ + { + "table": { + "headers": [ + "Anforderung", + "Darstellung in Docker", + "Umsetzung durch OCI manager Apps" + ], + "rows": [ + [ + "Anwendung ausführen", + "image, entrypoint, command", + "OCI-rootfs und Hauptprozess des LXC" + ], + [ + "Konfiguration erhalten", + "volumes: /config", + "Ein persistentes mpN-Volume, das in die Sicherung aufgenommen wird, oder ein Verzeichnis auf dem Host." + ], + [ + "Dienst bereitstellen", + "ports", + "Eine eigene Adresse für den LXC und die Zugriffs-URL des Dienstes." + ], + [ + "Hardware verwenden", + "devices, group_add", + "devN-Einträge mit der tatsächlich verwendeten Gruppen-ID (GID) des Hosts und einem validierten Profil." + ], + [ + "Abhängigkeiten verbinden", + "networks, depends_on", + "Eine private Netzwerk-Bridge, feste Adressen, Startreihenfolge und Zustandsprüfungen." + ], + [ + "Aktualisieren", + "Mit pull herunterladen und neu erstellen", + "Ein neues rootfs mit demselben Persistenzvertrag." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "Was erhalten bleibt und was sich ändert", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Bleibt erhalten", + "body": "Das offizielle Image, seine internen Pfade, seine Umgebung, sein Startprozess und seine Nutzungsdokumentation." + }, + { + "icon": "boxes", + "title": "Wird angepasst", + "body": "Die Laufzeitumgebung: Netzwerk, Persistenz, Geräte, Berechtigungen und Abhängigkeiten werden mit den nativen LXC-Funktionen von Proxmox VE umgesetzt." + }, + { + "icon": "shield", + "title": "Ohne Entsprechung", + "body": "Eine Compose-Option ohne sichere Entsprechung wird als Installationshindernis aufgeführt. Vorlagen mit solchen Hindernissen werden nicht zur Installation angeboten. Sensible Optionen müssen während der Installation bestätigt werden." + }, + { + "icon": "refresh", + "title": "Wird gespeichert", + "body": "Image-Digest, Ressourcen, Pfade, Netzwerk, Geräte und Zugehörigkeit zu einem Stack werden im Instanzvertrag gespeichert. Aktualisierungen und Neuerstellungen bilden diesen Vertrag wieder ab." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Seiten in diesem Abschnitt", + "blocks": [ + { + "next": { + "items": [ + { + "label": "So wird ein OCI-Image umgesetzt", + "href": "/docs/oci-manager/architecture", + "tail": "Vom Image und seiner Compose-Datei zu einem nativen LXC." + }, + { + "label": "Image außerhalb des Katalogs installieren", + "href": "/docs/oci-manager/custom-image", + "tail": "Compose-Datei, docker run oder eine Image-Referenz." + }, + { + "label": "Daten, Pfade und Netzwerk", + "href": "/docs/oci-manager/storage-network", + "tail": "Container-Volumes, Host-Verzeichnisse, rclone-Mounts und Adressen." + }, + { + "label": "Geräte und Beschleunigung", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB und weitere Geräte." + }, + { + "label": "Anwendungen mit mehreren Containern", + "href": "/docs/oci-manager/stacks", + "tail": "Anwendung, Datenbank und Cache als koordinierte LXCs." + }, + { + "label": "Installieren, aktualisieren und ändern", + "href": "/docs/oci-manager/lifecycle", + "tail": "Der Instanzvertrag und seine Vorgänge." + }, + { + "label": "In ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versionen, Aktualisierungen, Konsolenausgabe und Terminal." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/lifecycle.json b/web/messages/de/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..7fb24421 --- /dev/null +++ b/web/messages/de/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Installieren, Aktualisieren und Ändern | ProxMenux", + "description": "Der Instanzvertrag eines OCI-Containers und die darauf beruhenden Vorgänge: Aktualisieren, Ändern, Entfernen, Fortsetzen eines unterbrochenen Vorgangs und Wiederherstellen des Betriebs nach einer Wiederherstellung auf diesem oder einem anderen Host." + }, + "header": { + "title": "Installation, Aktualisierung und Änderung", + "description": "Jede Instanz behält einen reproduzierbaren Vertrag, sodass ihr rootfs ersetzt werden kann, ohne Konfiguration oder persistente Daten zu verlieren.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "Der Instanzvertrag", + "blocks": [ + { + "p": "Nach einer Installation wird die effektive Konfiguration in /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json gespeichert. Es ist keine Kopie der ursprünglichen Compose-Datei: Es ist der reproduzierbare Vertrag des LXC, der auf diesem Host existiert." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "Eine Kopie des Vertrags liegt im Container unter /.proxmenux/oci-record.json und ist nur für dessen Benutzer root lesbar. Eine Containersicherung enthält daher immer den zu diesem Zeitpunkt gültigen Vertrag. Eine zweite Kopie liegt unter /etc/pve/priv/proxmenux/oci; dieses Verzeichnis ist auf allen Clusterknoten verfügbar und nur für root des Hosts lesbar. Beide Kopien werden nach jeder Installation, Aktualisierung und Änderung geschrieben." + } + ] + }, + { + "id": "manage", + "title": "Verwalten installierter OCI-Anwendungen", + "intro": "Diese Option auf dem Hauptbildschirm listet die registrierten Instanzen mit ihrer Anwendung und ihrem Image auf. Vor jeder Aktion wird jede Instanz mit ihrem Vertrag abgeglichen; ein CT, der nicht mehr zu seinem Datensatz passt, wird weder geändert noch gelöscht. Dieselbe Aktualisierungsfunktion und derselbe Editor stehen auf der Registerkarte Aktualisierungen von ProxMenux Monitor bereit.", + "blocks": [ + { + "table": { + "headers": [ + "Option", + "Was sich ändert", + "Was erhalten bleibt" + ], + "rows": [ + [ + "Aktualisieren Sie das Image mit der gespeicherten Konfiguration", + "Das rootfs wird durch das Image ersetzt, das der gespeicherte Kanal aktuell bereitstellt", + "Vertrag, Containerplatten, Hostverzeichnisse, Netzwerk und Geräte" + ], + [ + "Ändern: Ressourcen, Netzwerk, Pfade und GPU bearbeiten", + "Der Editor öffnet sich mit dem aktuellen Vertrag; der CT wird mit den Änderungen neu erstellt", + "Die Daten von Container-Disks und Host-Verzeichnissen" + ], + [ + "Watchdog: Neustart der Anwendung, wenn sie abstürzt", + "Ob die Anwendung nach einem selbstständigen Beenden erneut gestartet wird. Es wird nichts neu erstellt", + "Die gesamte Installation; nur die Wahl ist im Vertrag gespeichert" + ], + [ + "Entfernen: Löschen Sie die Anwendung und ihre Container", + "Der LXC oder jedes Mitglied eines Stacks und seine Verträge werden gelöscht", + "Host-Verzeichnisse mit ihren Inhalten" + ] + ] + } + }, + { + "p": "Für eine Anwendung mit mehreren Containern bietet das Menü Alle Container der Anwendung aktualisieren, Zusätzliche Pfade und Geräte ändern, Alle Container mit ihrer gespeicherten Konfiguration neu erstellen, den Watchdog und die Entfernung. Ändern fügt die zusätzlichen Pfade und Geräte des Anwendungscontainers hinzu oder entfernt sie, ohne etwas neu zu erstellen. Bei Immich lässt sich damit auch ändern, womit die Erkennung ausgeführt wird. Neu erstellen erstellt jeden Container anhand des ursprünglich installierten Images neu, ohne nach einem neueren zu suchen." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Liste der installierten OCI-Anwendungen und der Optionen zum Aktualisieren, Ändern und Entfernen", + "caption": "Verwalten Sie installierte OCI-Anwendungen." + } + } + ] + }, + { + "id": "update", + "title": "Transaktionsaktualisierung", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Benutzer", + "registry": "OCI-Register", + "update": "Instanz aktualisieren", + "resolve": "Tag auflösen", + "verify": "Archiv prüfen und Vorabprüfungen durchführen", + "backup": "Stoppen und sichern Sie den CT", + "import": "Neues rootfs importieren", + "reapply": "Den Vertrag erneut anwenden", + "healthy": "in Ordnung", + "failure": "Ausfall", + "commit": "Vertrag mit dem neuen Digest veröffentlicht", + "restored": "Frühere Instanz wiederhergestellt" + } + } + }, + { + "list": { + "items": [ + "Wenn die Registry weiterhin den installierten Digest bedient, wird nichts heruntergeladen und die Instanz nicht berührt.", + "Das neue Image wird Schicht für Schicht verifiziert, bevor der CT stoppt. Ein Download, der beschädigt ankommt, wird ein zweites Mal abgerufen; ein bereits zwischengespeichertes Image, das den Check nicht besteht, wird erneut heruntergeladen.", + "Nach der Installation in Proxmox VE geänderte Einstellungen (Arbeitsspeicher, Swap, Kerne, CPU-Limit, CPU-Priorität, Start mit dem Knoten) bleiben erhalten und werden in den neuen Vertrag übernommen.", + "Die LAN-Schnittstelle, die die Arr-Suite und Multi-Container-Anwendungen hinzufügen (net1) und die Startreihenfolge (startup) werden an den neuen Container zurückgegeben.", + "Jeder andere Unterschied zwischen dem CT und seinem Vertrag stoppt das Update, bevor der CT gestoppt wird." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Vorher", + "detail": "rootfs A\n/config mp0\n/media Hostverzeichnis" + }, + { + "label": "Aktualisierung", + "detail": "ersetzt nur\ndas rootfs" + }, + { + "label": "Nachher", + "detail": "rootfs B\n/config mp0\n/media Hostverzeichnis" + } + ] + } + }, + { + "p": "Bevor etwas gestoppt wird, prüft der Vorgang, ob ausreichend Platz für die Sicherung vorhanden ist: die belegten Daten der zu sichernden Volumes plus eine Reserve. Die Sicherung wird neben dem Anwendungsdatensatz auf dem Systemlaufwerk des Hosts gespeichert. Reicht der Platz dort nicht aus, fragt das Menü nach einem anderen Host-Speicher, der Sicherungen unterstützt. Dort wird die Sicherung erstellt und nach Abschluss des Vorgangs gelöscht. Die Auswahl wird für diese Anwendung auch für geplante Aktualisierungen gespeichert. Hat kein Speicher ausreichend Platz, wird der Vorgang abgelehnt und die Anwendung bleibt unverändert. Der Platzbedarf des neuen Images wird vor dem Herunterladen ebenso geprüft." + } + ] + }, + { + "id": "recovery", + "title": "Wiederherstellung eines unterbrochenen Vorgangs", + "intro": "Ein Update und eine Änderung verwenden eine persistente Transaktion. Wenn der Prozess, das Terminal oder der Knoten nach dem Stoppen des CT unterbrochen wird, wird die Operation nicht als abgeschlossen angesehen.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Ausstehender Marker", + "body": "Beim Öffnen von „Installierte OCI-Anwendungen verwalten“ zeigt der gespeicherte Zustand, dass der Ersatz nie veröffentlicht wurde." + }, + { + "title": "Status anzeigen", + "body": "Zeigt die Phase an, die erreicht wurde, ohne Container oder Daten zu ändern." + }, + { + "title": "Wiederherstellen der vorherigen Installation", + "body": "Stellt die vor dem Ersatz erstellte und geprüfte native Sicherung sowie den vorherigen Vertrag wieder her." + }, + { + "title": "Stacks als Einheit", + "body": "Für eine Multi-Container-Anwendung wird jedes Mitglied aus dem gleichen Transaktionspunkt wiederhergestellt, nicht nur das ausgewählte." + } + ] + } + }, + { + "calloutWarning": { + "title": "Host-Verzeichnisse liegen außerhalb des Rollbacks", + "body": "Die Sicherung umfasst das rootfs und die darin eingeschlossenen Containerlaufwerke. Ein Hostverzeichnis wird nicht zurückgesetzt, da andere LXCs dessen Daten verwenden können." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: Neustart einer Anwendung, die stoppt", + "intro": "Proxmox hat keine Neustartrichtlinie: Wenn der Prozess eines Anwendungscontainers endet, bleibt der Container gestoppt. Mit dem Watchdog wird eine Anwendung, die von selbst aufhört, erneut gestartet. Es tut für eine OCI-Anwendung, was restart: unless-stopped in einer Compose-Datei tut.", + "blocks": [ + { + "table": { + "headers": [ + "Was passiert", + "Mit aktiviertem Watchdog" + ], + "rows": [ + [ + "Die Anwendung stürzt ab oder ihr Prozess endet", + "Der Container wird innerhalb von Sekunden wieder gestartet" + ], + [ + "Es wird von Proxmox, vom Monitor oder mit pct gestoppt oder heruntergefahren", + "Er bleibt gestoppt, bis ihn jemand startet" + ], + [ + "Ein Backup, eine Migration oder eine ProxMenux-Operation arbeitet daran", + "Nichts wird getan, bis das endet" + ], + [ + "Er war beim Start des Hosts bereits gestoppt", + "Er bleibt gestoppt; dafür ist Mit Proxmox starten zuständig" + ], + [ + "Es stoppt direkt nach dem Start", + "Bei einer Anwendung, die Sekunden nach dem Start wieder stoppt oder gar nicht startet, werden fünf Startversuche unternommen. Dazwischen liegen Wartezeiten von 30 Sekunden, 1, 2 und 5 Minuten. Danach bleibt sie gestoppt und eine Benachrichtigung wird gesendet" + ] + ] + } + }, + { + "p": "Das Installationsprogramm fragt in beiden Installationsmodi danach und schlägt den im Anwendungsrezept angegebenen Wert vor. Später lässt sich die Einstellung unter Installierte OCI-Anwendungen verwalten oder mit dem Schalter Watchdog des Containers in ProxMenux Monitor ändern. Sie gilt für alle Container einer Anwendung mit mehreren Containern. Ein einziger Host-Dienst, proxmenux-oci-watchdog.service, überwacht alle Anwendungen, für die dies aktiviert wurde; seine Installation erscheint auf der Registerkarte Änderungen von Audit & Report." + }, + { + "calloutInfo": { + "title": "Was der Host nicht sagen kann", + "body": "Der Exit-Code der Anwendung erreicht den Host nicht. Daher sind ein reguläres Beenden und ein Absturz nicht unterscheidbar; in beiden Fällen wird neu gestartet, wie bei always und unless-stopped in Docker. Ein außerhalb von Proxmox mit lxc-stop oder systemctl angeforderter Stopp hinterlässt keinen Stopp-Task und wird als selbstständiges Beenden gewertet." + } + } + ] + }, + { + "id": "restore", + "title": "Backup, Wiederherstellung und ein weiterer Host", + "intro": "Eine mit vzdump oder Proxmox Backup Server erstellte Sicherung enthält den Container, die für die Sicherung ausgewählten Containerlaufwerke und die Kopie seines Vertrags. Was die Installation auf dem Host speichert, ist nicht darin enthalten. Installierte OCI-Anwendungen verwalten erkennt Container, die auf einem Host ohne ihren Vertrag wiederhergestellt wurden, etwa nach einer Proxmox-Neuinstallation oder auf einem anderen Host, und bietet ihre erneute Registrierung an.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Wiederherstellen der Container in Proxmox", + "body": "Vom Sicherungsspeicher, mit der ursprünglichen ID oder einer beliebigen freien ID. Bei einer Anwendung mit mehreren Containern werden alle ihre Container benötigt." + }, + { + "title": "Installierte OCI-Anwendungen verwalten öffnen", + "body": "Die wiederhergestellten Container werden aufgelistet und die Wiederherstellung ihrer Registrierung wird angeboten. Die Schaltfläche Wiederherstellen auf der Registerkarte Aktualisierungen von ProxMenux Monitor öffnet denselben Vorgang." + }, + { + "title": "Überprüfen Sie vor dem Wechsel", + "body": "Die Wiederherstellung überprüft, ob der Host alles hat, was jede Anwendung benötigt. Eine Anwendung, die nicht als Ganzes wiederhergestellt werden kann, bleibt so, wie sie gefunden wurde, mit dem Grund." + }, + { + "title": "Registrieren und starten", + "body": "Der Vertrag wird auf diesem Host registriert und was die Installation darauf aufbewahrt, wird erneut geschrieben. Das Starten der Anwendungen ist eine separate Frage, die mit Nein beantwortet wird, wenn die ursprünglichen Container noch auf einem anderen Host ausgeführt werden." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Dialog, der die wiederhergestellten Container auflistet und anbietet, sie wiederherzustellen", + "caption": "Die beim Öffnen von „Installierte OCI-Anwendungen verwalten“ angebotene Wiederherstellung." + } + }, + { + "table": { + "headers": [ + "Was der Host behalten hat", + "Nach der Wiederherstellung" + ], + "rows": [ + [ + "Instanzvertrag", + "Wird aus der Kopie im Container registriert und mit der von Proxmox wiederhergestellten Konfiguration abgeglichen" + ], + [ + "Privates Netzwerk einer Multicontainer-Anwendung", + "Wird mit derselben Bridge und demselben Subnetz neu erstellt; die festen Adressen der Container ändern sich nicht" + ], + [ + "Startreihenfolge einer Anwendung mit mehreren Containern", + "Das Abhängigkeits-Hookscript und sein Vertrag werden wieder installiert; Snippets sind auf dem local-Speicher aktiviert, wenn kein Speicher sie akzeptiert" + ], + [ + "Netzwerk sysctls und Host-Monitor-Datei", + "Nochmals geschrieben in /etc/pve/proxmenux" + ], + [ + "NVIDIA Laufzeit", + "Der Hook eines unprivilegierten Containers wird erneut installiert. Ein privilegierter Container erhält die Treiberdateien dieses Hosts statt der Dateien seines ursprünglichen Hosts" + ], + [ + "Rclone mount", + "Das Hookscript und die Programme, die das Mount veröffentlichen, werden erneut geschrieben, mit den gleichen Ansichten auf dem Host" + ], + [ + "Host-Firewallregel eines Host-Monitors", + "Wird erneut abgefragt, für seinen Web-Port und das Subnetz der Bridge auf diesem Host" + ], + [ + "lost+found jeder wiederhergestellten Festplatte", + "Wird entfernt, wenn es leer ist; bei einer Wiederherstellung wird es angelegt, und manche Anwendungen starten nicht, wenn es in ihrem Datenverzeichnis liegt" + ], + [ + "Disks auf einem anderen Speicher wiederhergestellt", + "Der Vertrag wird auf den Speicher aktualisiert, auf dem sie sich jetzt befinden" + ] + ] + } + }, + { + "p": "Ein mit einer anderen ID wiederhergestellter Container behält seine Anwendung. Der Vertrag, sein Konsolenprotokoll, seine Netzwerk-sysctls, das Hookscript einer Rclone-Einhängung und die Startreihenfolge einer Anwendung mit mehreren Containern werden mit den IDs registriert, die die Container auf diesem Host haben." + }, + { + "table": { + "headers": [ + "Was eine Wiederherstellung verhindert", + "Was zu tun ist" + ], + "rows": [ + [ + "Ein Container einer Multicontainer-Anwendung fehlt", + "Stellen Sie es auch wieder her; Die Anwendung wird als Ganzes wiederhergestellt" + ], + [ + "Das private Subnetz wird bereits auf diesem Host verwendet", + "Die Wiederherstellung wird abgebrochen und nichts wird geändert: Die Adressen der Container sind fixiert und werden nicht in ein anderes Subnetz verschoben" + ], + [ + "Ein Hostverzeichnis existiert nicht", + "Mounten oder erstellen Sie es mit seinen Daten; ein Backup des Containers enthält keine Hostverzeichnisse" + ], + [ + "Ein Gerät existiert nicht auf diesem Host", + "Verbinden Sie es oder entfernen Sie es aus dem Container in Proxmox" + ], + [ + "Eine Anwendung mit NVIDIA auf einem Host ohne Treiber", + "Installieren Sie den NVIDIA-Treiber und das Container Toolkit" + ], + [ + "Das Backup wurde erstellt, bevor die Kopie des Vertrages existierte", + "Der Container bleibt als gewöhnlicher LXC und wird nicht wieder angeboten" + ] + ] + } + }, + { + "calloutInfo": { + "title": "Ein Container, der zurückkommt", + "body": "Ein Container, der von einem älteren Backup wiederhergestellt, zu einem Snapshot zurückgerollt oder von einem anderen Knoten zurückgegeben wurde, trägt den Vertrag des Zustands, in dem er sich befindet. Wenn es für eine Operation ausgewählt wird und sein Vertrag von dem dieses Hosts abweicht, startet die Operation nicht und der Container wird zur Wiederherstellung angeboten." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Ergebnis der Wiederherstellung mit privatem Netzwerk, Startreihenfolge und registrierten Containern", + "caption": "Das Ergebnis einer Wiederherstellung, Schritt für Schritt." + } + } + ] + }, + { + "id": "remove", + "title": "Entfernen einer OCI-Anwendung", + "blocks": [ + { + "p": "Vor der Bestätigung wird eine Zusammenfassung aus der tatsächlichen Konfiguration erstellt: die zu entfernenden Container, die damit gelöschten Daten, das freigegebene private Netzwerk und die erhaltenen Hostverzeichnisse. Die Bestätigung steht standardmäßig auf Nein, da die Daten gelöschter Laufwerke anschließend nicht wiederhergestellt werden können." + }, + { + "table": { + "headers": [ + "Ressource", + "Beim Entfernen", + "Grund" + ], + "rows": [ + [ + "rootfs und Container-Festplatten", + "Gelöscht", + "Sie gehören nur zum Container" + ], + [ + "Hostverzeichnis", + "Bleibt mit seinem Inhalt erhalten", + "Andere Anwendungen können es verwenden" + ], + [ + "Host-Dateien des Containers", + "Gelöscht", + "Sein Konsolenprotokoll, Netzwerk-Sysctls, Rclone Mount Hookscript und seine Registrierung in der App-Registerkarte von ProxMenux Monitor dienen keinem anderen Container" + ], + [ + "Von mehreren Installationen gemeinsam genutzte Dateien", + "Gelöscht mit der letzten Installation, die sie verwendet", + "Die Host-Monitor-Datei und das Dependency-Hookscript von Multi-Container-Anwendungen" + ], + [ + "Instanzvertrag", + "Nach erfolgreicher Entfernung gelöscht", + "Kein CT ist mehr damit verbunden" + ], + [ + "Private Bridge eines Stacks", + "Mit dem Stack freigegeben", + "Es sind keine Mitglieder mehr zu verbinden" + ], + [ + "Von der Arr-Suite gemeinsam genutztes Netzwerk", + "Wird beim Entfernen der letzten Anwendung der Suite freigegeben", + "Jede Anwendung der Suite ist unabhängig und wird einzeln entfernt" + ], + [ + "Ein einzelnes Mitglied eines Stapels", + "Wird nicht einzeln entfernt", + "Die gesamte Anwendung wird entfernt, so dass kein Stapel unvollständig bleibt" + ], + [ + "Ein Container auf einem anderen Knoten des Clusters", + "Nicht entfernt", + "Seine Datensatz- und Hostdateien befinden sich auf dem Knoten, auf dem es installiert wurde: Es wird dort entfernt, nachdem es zurück migriert wurde." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Zusammenfassung zum Entfernen mit den betroffenen Containern, Daten und Hostverzeichnissen", + "caption": "Die Entfernungszusammenfassung, aufgebaut aus der realen Konfiguration." + } + } + ] + }, + { + "id": "archives", + "title": "Heruntergeladene Images und Speicherplatz auf dem Host", + "blocks": [ + { + "p": "Das OCI-Archiv dient zum Erstellen des rootfs; der laufende CT liest es nicht. Nach einer Installation oder Aktualisierung werden die dabei heruntergeladenen Archive mit ihrer Größe aufgelistet und zur Löschung angeboten. Ihre Löschung gibt Speicherplatz frei, ohne den Container oder seine Daten zu beeinträchtigen." + }, + { + "calloutInfo": { + "title": "Updates benötigen das Archiv nicht", + "body": "Ohne Archiv löst ein Update den gespeicherten Kanal auf und lädt den neuen Digest herunter. Mit ihm wird ein Archiv nur dann wiederverwendet, wenn seine Referenz und Integrität mit dem übereinstimmen, was angefordert wird." + } + } + ] + }, + { + "id": "registry", + "title": "Einträge und Bereinigung", + "blocks": [ + { + "p": "Beim Öffnen von Installierte OCI-Anwendungen verwalten werden zuerst Reste von Containern entfernt, die auf keinem Clusterknoten mehr existieren: der als Verlauf aufbewahrte Datensatz eines über die Proxmox-Oberfläche gelöschten Containers sowie Host-Dateien, die bei einer Entfernung zurückblieben. Ein Datensatz mit einem unterbrochenen Vorgang bleibt erhalten, da seine Sicherung noch benötigt werden könnte. Volumes und Hostverzeichnisse werden niemals aufgrund bloßer Schlussfolgerungen gelöscht." + } + ] + }, + { + "id": "cluster", + "title": "In einem Cluster: Migration und hohe Verfügbarkeit", + "intro": "Ein OCI-Container ist ein gewöhnlicher Proxmox-LXC: Er kann wie jeder andere migriert oder von HA verwaltet werden, mit denselben Einschränkungen. Alles, was er zum Starten benötigt, wird dort gespeichert, wo jeder Clusterknoten darauf zugreifen kann.", + "blocks": [ + { + "table": { + "headers": [ + "Teil", + "Auf einem anderen Knoten des Clusters" + ], + "rows": [ + [ + "Konsolenlogbuch", + "Der Container erstellt /var/log/proxmenux/oci vor dem Start auf dem jeweiligen ausführenden Knoten. Jeder Knoten behält das Protokoll der von ihm ausgeführten Starts." + ], + [ + "Netzwerk sysctls und Host-Monitor", + "Werden in /etc/pve/proxmenux gespeichert, das alle Clusterknoten gemeinsam nutzen, sodass ein migrierter Container sie findet." + ], + [ + "Containerfestplatten", + "Proxmox verschiebt sie mit dem Container. Für HA müssen ihre Daten auf dem Zielknoten verfügbar sein, über gemeinsamen Speicher oder eine unterstützte Replikationskonfiguration." + ], + [ + "Host-Verzeichnisse und Geräte", + "Die gleichen Regeln wie bei jedem LXC: Ein Host-Verzeichnis muss auf dem Zielknoten vorhanden sein und als gemeinsam genutzt markiert werden, und ein GPU, Coral oder NPU muss dort vorhanden sein." + ], + [ + "ProxMenux-Datensatz", + "Der Vertrag bleibt auf dem Knoten, auf dem die Anwendung installiert wurde; alle Knoten lesen die Kopie unter /etc/pve/priv/proxmenux/oci. Eine migrierte Anwendung mit einem einzelnen Container wird auf dem Zielknoten registriert, sobald Installierte OCI-Anwendungen verwalten geöffnet oder eine Aktion für sie gestartet wird. Eine Anwendung mit mehreren Containern wird dort zur Wiederherstellung angeboten, da ihr privates Netzwerk auf diesem Knoten erstellt werden muss." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Nicht für hohe Verfügbarkeit", + "body": "Eine Multicontainer-Anwendung erreicht ihre Mitglieder über eine private Brücke des Knotens, auf dem sie installiert wurde, so dass ihre Mitglieder auf diesem Knoten bleiben. Ein Hostmonitor, z. B. Glances im Hostmodus oder Netdata, überwacht den Knoten, auf dem er ausgeführt wird; das Bewegen würde einen anderen Knoten überwachen." + } + }, + { + "p": "Ein Backup, das außerhalb des Clusters wiederhergestellt wird, erhält die Dateien von /etc/pve/proxmenux, die der Container verwendet, wenn die Anwendung wiederhergestellt wird." + } + ] + }, + { + "id": "channel", + "title": "Ein Rolling Tag ist kein unbeaufsichtigtes Update", + "blocks": [ + { + "p": "Der Katalog installiert das vom Maintainer veröffentlichte Rolling-Tag. Der tatsächliche Digest wird aufgelöst und aufgezeichnet und nur durch eine explizite Aktualisierung mit Vorabprüfung und Rollback geändert. ProxMenux Monitor vergleicht den installierten Digest mit dem von der Registry veröffentlichten Digest und zeigt und meldet, wenn ein neues Image verfügbar ist." + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/monitor.json b/web/messages/de/docs/oci-manager/monitor.json new file mode 100644 index 00000000..9f02cd8b --- /dev/null +++ b/web/messages/de/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI Container in ProxMenux Monitor | ProxMenux", + "description": "Was ProxMenux Monitor für einen mit OCI manager Apps installierten Container zeigt: Anwendungs- und Image-Versionen, neue Images, Konsolenausgabe und Proxmox-VE-Terminal." + }, + "header": { + "title": "In ProxMenux Monitor", + "description": "Ein mit OCI manager Apps installierter Container erscheint wie jeder andere LXC unter VMs & LXCs. Sein Detaildialog liest den Installationsdatensatz: Anwendungs- und Image-Versionen, neue Images, Konsolenausgabe und Terminal.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Die Aufzeichnung ist die Quelle", + "body": "OCI manager Apps kennt die installierte Anwendung und ihre Herkunft. Der Monitor liest diesen Installationsdatensatz, statt die Anwendung im Container zu untersuchen. Diese Informationen stehen deshalb sowohl bei laufendem als auch bei gestopptem Container zur Verfügung." + } + }, + { + "table": { + "headers": [ + "Tab.", + "Was ändert sich für einen OCI Container" + ], + "rows": [ + [ + "Status", + "Ein Watchdog Switch unter Start beim Booten: Die Anwendung wird wieder gestartet, wenn sie von selbst stoppt" + ], + [ + "App", + "Die Anwendung wird aus dem Datensatz identifiziert und Updates werden nach Image verfolgt" + ], + [ + "Aktualisierungen", + "Das Image wird aktualisiert oder die Anwendung geändert, mit dem gleichen Ablauf wie das OCI-Menü" + ], + [ + "Mounts", + "Containerfestplatten und Hostverzeichnisse mit ihrer Belegung" + ], + [ + "Protokolle", + "Nur für OCI-Container: die Konsolenausgabe der Anwendung" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "App", + "intro": "Die Registerkarte App bietet die installierte Anwendung als Erkennung mit dem Namen, dem Logo, dem Port und dem Schema des Datensatzes. Die Registrierung öffnet den Editor mit der Methode OCI-Image (installiert von ProxMenux), die keine Konfiguration benötigt.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Anwendung", + "body": "Die Version der Anwendung im Image, gelesen aus dem Image selbst: seine Umgebung oder seine org.opencontainers.image.version Label. Es ist informativ." + }, + { + "icon": "archive", + "title": "Image", + "body": "Das Build-Datum und der Digest des installierten Images. Das Update wird hier entschieden: Der installierte Digest wird mit dem verglichen, den die Registry heute für dasselbe Tag veröffentlicht." + } + ] + } + }, + { + "list": { + "items": [ + "Wenn die Registry einen neuen Digest veröffentlicht, zeigt die Karte Neues Image mit seinem Datum und Digest an, auch wenn sich die Anwendungsversion im Inneren nicht geändert hat: Ein Neuaufbau auf einer aktualisierten Basis ist ein Update.", + "Wenn die Digests übereinstimmen, liest die Karte Version.", + "Die Karte verlinkt auf das Repository des Images: das GitHub-Projekt oder die Docker-Hub-Seite für ein offizielles Image.", + "Der Zugangslink verwendet die LAN-Adresse des Containers, auch für das Hauptmitglied einer Multicontainer-Anwendung, die eine zweite Adresse in ihrem privaten Netzwerk hat.", + "Die Schaltfläche Daten aktualisieren liest den Datensatz und die Registry erneut. Die Optionen zum Suchen oder Registrieren weiterer Anwendungen werden nicht angeboten: Der Container enthält genau die Anwendung seines Datensatzes." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "App-Tab eines OCI-Containers mit Anwendungsversion, Image und Repository-Link", + "caption": "Registerkarte App eines mit OCI manager Apps installierten Containers." + } + }, + { + "p": "Der Check läuft einmal täglich mit den Update-Checks des Monitors und Daten aktualisieren führt ihn sofort aus. Ein neues Image wird als Benachrichtigung über die in Benachrichtigungen konfigurierten Kanäle gesendet. Das Update wird über den Tab Updates angewendet." + } + ] + }, + { + "id": "updates", + "title": "Aktualisierungen", + "intro": "Bei einem mit OCI manager Apps installierten Container zeigt die Registerkarte Updates die Anwendung mit ihrem installierten Image und gegebenenfalls dem neuen Image aus der Registry, im selben Format wie andere Anwendungen. Die Paket- und Anwendungs-Updater eines gewöhnlichen LXC werden nicht angeboten.", + "blocks": [ + { + "table": { + "headers": [ + "Schaltfläche", + "Was es öffnet" + ], + "rows": [ + [ + "Aktualisierung", + "Die Aktualisierung aus Installierte OCI-Anwendungen verwalten für diesen Container im Monitor-Terminal. Sie kann mit oder ohne neues Image ausgeführt werden; ohne neues Image wird nichts verändert. Bei einer Multicontainer-Anwendung werden alle Mitglieder aktualisiert." + ], + [ + "Ändern", + "Der Editor der Anwendung (Ressourcen, Netzwerk, Pfade und GPU). In einer Multicontainer-Anwendung werden die zusätzlichen Pfade und Geräte des Anwendungscontainers hinzugefügt oder entfernt und in Immich wird geändert, was die Erkennung ausführt." + ], + [ + "Wiederherstellen", + "Ersetzt Update, wenn eine Operation auf dem Container unterbrochen wurde, und öffnet seine Wiederherstellung." + ], + [ + "Wiederherstellen (wiederhergestellter Container)", + "Wird für einen aus einem Backup wiederhergestellten Container ohne Vertrag auf diesem Host unter Wiederhergestellte OCI-Anwendung angezeigt. Öffnet die Wiederherstellung im Monitor-Terminal; Aktualisieren und Ändern erscheinen nach der Registrierung." + ] + ] + } + }, + { + "p": "Externe Änderungen, Hostverzeichnisse und Multicontainer-Anwendungen werden wie im OCI-Menü behandelt. In einer Multicontainer-Anwendung erscheinen die Buttons und die Optionen nur auf dem Hauptcontainer; die anderen Mitglieder zeigen ihr Image, eine Notiz, dass sie vom Hauptcontainer aktualisiert werden, und eine Schaltfläche, die es öffnet. Wenn das Terminal schließt, werden das Image, der Datensatz und die Mounts erneut gelesen." + }, + { + "table": { + "headers": [ + "Option", + "Verhalten" + ], + "rows": [ + [ + "Das vor dem Update erstellte Backup behalten", + "Jedes Update sichert den Container, um ihn wiederherzustellen, wenn das Update fehlschlägt. Mit dieser Option wird dasselbe Backup im ausgewählten Speicher gespeichert und erscheint unter den Backups des CT; auf Proxmox Backup Server wird vor dem Update ein Backup geschrieben." + ], + [ + "Geplante Updates", + "Das Image wird zum gewählten Zeitpunkt nur aktualisiert, wenn die Registry ein neues veröffentlicht, und optional nur, wenn es 1, 3, 7 oder 14 Tage alt ist. Ein Container mit Änderungen außerhalb von ProxMenux wird übersprungen und gemeldet." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Protokolle", + "intro": "Die Registerkarte Logs erscheint nur bei mit OCI manager Apps installierten Containern, zwischen Mounts und Backups. Sie zeigt Standardausgabe und Standardfehler des Hauptprozesses des Images, entsprechend der Ausgabe von docker logs bei Docker-Containern.", + "blocks": [ + { + "list": { + "items": [ + "Die Ausgabe wird auf dem Host in /var/log/proxmenux/oci/VMID.console.log (Modus 0600) vom ersten Start und über Neustarts gehalten, so dass sie mit gestopptem Container gelesen werden kann.", + "Die Datei wird bei 10 MB gedreht und behält drei komprimierte Kopien (/etc/logrotate.d/proxmenux-oci).", + "Jeder Start des Containers wird in der Ausgabe markiert. Die Registerkarte zeigt die Ausgabe seit dem letzten Start standardmäßig; Vollständiger Verlauf zeigt auch die vorherigen.", + "Wenn die Anwendung entfernt wird, wird ihre Ausgabe mit ihr entfernt.", + "Die letzten 100-, 500- oder 1000-Zeilen werden gezeigt. Während der Container läuft, werden neue Zeilen live verfolgt; das Scrollen nach oben pausiert das Folgen und Follow nimmt es wieder auf.", + "Ein Filter zeigt nur die Zeilen an, die einen Text enthalten, und Herunterladen speichert die geladenen Zeilen.", + "Farbcodes werden entfernt und eine Zeile, die eine Fortschrittsleiste neu zeichnet, wird in ihrem endgültigen Zustand angezeigt.", + "Die Registerkarte liest die Datei bei jedem Öffnen; es wird nicht zwischengespeichert." + ] + } + }, + { + "calloutInfo": { + "title": "Zugangsdaten beim ersten Start", + "body": "Images, die ein generiertes Passwort beim ersten Start ausdrucken, lassen es in dieser Ausgabe, wie docker logs. Der Installateur liest es von dort aus, um es in seiner Zusammenfassung zu zeigen." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Logs Tab mit dem Konsolenausgang eines OCI-Containers, dem Zeilenwähler, dem Filter und der Follow-Taste", + "caption": "Konsolenausgabe eines OCI-Containers." + } + } + ] + }, + { + "id": "terminal", + "title": "Proxmox VE Konsole", + "blocks": [ + { + "p": "Ein OCI-Image startet seinen eigenen Prozess als PID 1 und keinen Anmeldedienst. Die übliche LXC-Konsole würde deshalb ein Terminal ohne antwortenden Login-Prozess öffnen. Mit OCI manager Apps installierte Container erhalten cmode: shell: Die Proxmox-VE-Konsole öffnet über lxc-attach eine Shell, entsprechend docker exec. Die laufende Anwendung bleibt dabei unverändert." + }, + { + "list": { + "items": [ + "Die Shell ist diejenige, die /etc/passwd dem Root im Image gibt. Ein Image, dessen Root keine Shell hat oder keine ausliefert, behält die Standardkonsole.", + "Die Shell ist root im Container, ohne Passwort. Wer es öffnen kann, wird durch die VM.Console-Berechtigung von Proxmox VE entschieden.", + "Das Terminal des Monitors betritt den Container mit pct enter, was auf die gleiche Weise funktioniert." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Mounts", + "blocks": [ + { + "p": "Die Registerkarte Mounts listet die Festplatten und Hostverzeichnisse des Containers auf. Die Belegung einer Containerfestplatte auf Blockspeicher wie LVM-thin wird aus deren Dateisystem gelesen, das nur im Container eingehängt ist." + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/stacks.json b/web/messages/de/docs/oci-manager/stacks.json new file mode 100644 index 00000000..9a00532a --- /dev/null +++ b/web/messages/de/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Multicontainer-Anwendungen | ProxMenux", + "description": "Wie OCI manager Apps eine Anwendung mit Datenbank und Cache als koordinierte native LXCs umsetzt: Planung, privates Netzwerk, Abhängigkeits-Hook, Validierung und transaktionale Aktualisierungen." + }, + "header": { + "title": "Anwendungen mit mehreren Containern", + "description": "Eine Anwendung mit Datenbank, Cache und anderen Diensten wird zu mehreren koordinierten nativen LXCs, die als eine installiert und aktualisiert werden.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Eine Anwendung für den Benutzer, mehrere LXCs für Proxmox VE", + "body": "Eine Multicontainer-Definition ist ein Eintrag des Katalogs. Der Installer erstellt einen nativen LXC pro Dienst und behält seine Abhängigkeiten explizit. Immich, Nextcloud, Paperless-ngx und Tandoor werden auf diese Weise installiert." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Stack-Plan", + "app": "Anwendungs-LXC", + "appNet": "LAN + privat", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "privat" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. Der Plan, bevor ein Container existiert", + "blocks": [ + { + "p": "Es wird kein LXC erstellt, während die Compose-Definition interpretiert wird. Ein vollständiger Plan steht an erster Stelle, mit jedem Mitglied, Image, VMID, Netzwerk, Pfad, Geheimnis, Ordnung und Gesundheitscheck. Wenn der Plan nicht konsistent ist, wird die Installation nicht gestartet." + }, + { + "table": { + "headers": [ + "Planelement", + "Inhalt", + "Vor dem Erstellen geprüft" + ], + "rows": [ + [ + "Mitglieder", + "Hauptanwendung, Datenbank, Cache, maschinelles Lernen und andere Abhängigkeiten", + "Einzigartige VMIDs, bekannte Rollen und genau ein Hauptmitglied" + ], + [ + "Images", + "Rollende Referenz, Architektur und aufgelöster Digest jedes Dienstes", + "Alle existieren, unterstützen die Architektur und bestehen die OCI-Integritätsprüfung" + ], + [ + "Netzwerk", + "Private Bridge, Subnetz, eine feste Adresse pro Dienst und LAN-Zugang für das Hauptmitglied", + "Keine Kollision mit vorhandenen Brücken oder Subnetzen und keine wiederholte Adresse" + ], + [ + "Persistenz", + "Container-Disks, Host-Verzeichnisse, Besitzer und Backup", + "Keine überlappenden Pfade, Speicher verfügbar und deklarierte Berechtigungen" + ], + [ + "Geheimnisse", + "Datenbankpasswort, Anwendungsschlüssel und erste Anmeldeinformationen", + "Einmal generiert und nur an die Mitglieder gegeben, die sie verwenden" + ], + [ + "Lebenszyklus", + "Start-Order, Stop-Order und Gesundheitscheck pro Mitglied", + "Die Hauptanwendung startet zuletzt und stoppt zuerst" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Erstellung, Mitglied für Mitglied", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Reservieren Sie jeden VMID", + "body": "Das Proxmox-VE-Inventar und das Instanzverzeichnis werden geprüft. Ein vorhandener CT wird nicht übernommen; ein Vertrag, der noch zu einer anderen Instanz gehört, wird nicht wiederverwendet." + }, + { + "title": "Jedes Image vorbereiten", + "body": "Alle Images werden aufgelöst, heruntergeladen und verifiziert, bevor der erste Container erstellt wird." + }, + { + "title": "Erstellen Sie alle Rootfs", + "body": "Die offiziellen OCI-Metadaten werden importiert und die ProxMenux-Instanzidentität und die Mitgliedsrolle hinzugefügt." + }, + { + "title": "Privates Netzwerk", + "body": "Eine Brücke und ein Subnetz werden erstellt und jedes Mitglied erhält seine feste Adresse; nur das Hauptmitglied erhält auch die LAN-Schnittstelle." + }, + { + "title": "Persistenz", + "body": "Jede Datenbank und Konfiguration erhält eine eigene Containerplatte; nur Daten, die freigegeben werden sollen, verwenden Hostverzeichnisse." + }, + { + "title": "Umgebung", + "body": "Interne Endpunkte, Shared Secrets und Servicevariablen werden geschrieben. Die Anwendung erreicht ihre Abhängigkeiten an ihren reservierten Privatadressen." + }, + { + "title": "Aufzeichnung", + "body": "Jedes Mitglied zeichnet seine native Konfiguration, die Rootfs-Änderungen, die Updates reproduzieren müssen, und seine Beziehung zum Stack auf." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Starten und Überprüfen Sie jeden Container", + "intro": "Ein erstellter LXC bedeutet noch keinen betriebsbereiten Dienst. Die Abhängigkeiten starten der Reihe nach und durchlaufen jeweils eine dienstspezifische Prüfung.", + "blocks": [ + { + "table": { + "headers": [ + "Dienst", + "Überprüfung", + "Was es zeigt" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready im CT mit erwartetem Host, Benutzer und Datenbank", + "Der Server akzeptiert Verbindungen für die konfigurierte Datenbank" + ], + [ + "Redis / Valkey", + "redis-cli oder valkey-cli PING gegen seine Privatadresse", + "Der Broker hört zu und antwortet" + ], + [ + "Immich Maschinelles Lernen", + "HTTP GET /ping, plus eine Überprüfung der ausgewählten GPU-Laufzeit", + "Der Service antwortet und die angeforderte Beschleunigung ist nicht auf CPU zurückgefallen" + ], + [ + "Nextcloud", + "GET /status.php mit installed=true, maintenance=false und needsDbUpgrade=false", + "Initialisierung ohne anstehende Migration beendet" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP über die LAN-Adresse und den realen Port des Dienstes", + "Das Frontend und seine Abhängigkeiten stellen die Anwendung bereit" + ], + [ + "Hauptanwendung", + "Der Endpunkt seines Templates, zum Beispiel /api/server/ping in Immich", + "Der gesamte Stapel arbeitet durch die Anwendung, die die Abhängigkeiten verwendet" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Laufend bedeutet nicht funktionsfähig", + "body": "Der laufende Zustand sagt nur, dass der LXC-Prozess existiert. Wenn der Dienst eine bessere Prüfung bietet, wird eine Exec- oder HTTP-Prüfung mit einem Timeout verwendet. Wenn ein Mitglied stoppt oder seine Prüfung nicht besteht, wird der Stapel nicht als installiert deklariert." + } + } + ] + }, + { + "id": "hook", + "title": "4. Der Abhängigkeits-Hook", + "blocks": [ + { + "p": "Der Haken wird nur auf den Hauptcontainer eines abhängigen Stapels gesetzt. Proxmox VE behält das Skript als Snippet und führt es als hookscript dieses CT aus. Das Stack-Rezept ist nicht im Skript geschrieben: Es befindet sich in einem separaten privaten Vertrag." + }, + { + "codeGrid": { + "items": [ + { + "title": "Hauptkonfiguration CT", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Privatvertrag (Beispiel)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Vollständige Quelle von proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "Das Skript ist für jeden Stack gleich. VMIDs, Namen, Prüftypen und Timeouts stammen aus dem privaten Vertrag /etc/pve/priv/proxmenux-stack-VMID.json jedes Stacks." + }, + { + "steps": { + "items": [ + { + "title": "Proxmox VE ruft pre-start auf", + "body": "Bevor der Haupt-CT startet, läuft der Hook mit seinem VMID und der Lebenszyklusphase." + }, + { + "title": "Eine Sperre pro Stack", + "body": "flock auf /run/lock/proxmenux-stack-VMID.lock verhindert zwei Startsequenzen gleichzeitig." + }, + { + "title": "Der Vertrag wird gelesen und validiert", + "body": "Ein bekanntes Schema, numerische Abhängigkeiten, Labels, ein exec, http oder Running Check und ein positives Timeout sind erforderlich." + }, + { + "title": "Abhängigkeiten in der Reihenfolge", + "body": "Jeder CT muss vorhanden sein; ein gestoppter wird gestartet und ein bereits laufender wird nicht neu gestartet." + }, + { + "title": "Auf Betriebsbereitschaft warten", + "body": "Der Check läuft alle zwei Sekunden, und der CT wird ebenfalls bestätigt, dass er noch läuft." + }, + { + "title": "Der Haupt-CT startet", + "body": "Wenn jede Abhängigkeit bereit ist, endet der Vorstart und Proxmox VE startet die Anwendung." + } + ] + } + }, + { + "calloutInfo": { + "title": "Der Haken stoppt Abhängigkeiten nicht", + "body": "Die Post-Start-, Pre-Stop- und Post-Stop-Phasen tun nichts. Das Stoppen des Haupt-CT lässt PostgreSQL, Redis, Valkey oder Machine Learning laufen. Der Hook ordnet den Start an; er verwandelt nicht mehrere LXCs in einen Prozess." + } + }, + { + "table": { + "headers": [ + "Typ", + "Beispiel", + "Spätere Starts" + ], + "rows": [ + [ + "Abhängiger Stapel", + "Immich, Nextcloud, Anwendung mit PostgreSQL", + "Der Haken des Haupt-CT startet die Abhängigkeiten und wartet auf sie" + ], + [ + "Anwendungssuite", + "Arr Suite", + "Kein Hauptmitglied: jeder LXC folgt seinem eigenen onboot" + ], + [ + "Einzelne Anwendung", + "Jellyfin", + "Proxmox VE startet diesen LXC direkt" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Kontrollen am Stapel als Ganzes", + "blocks": [ + { + "list": { + "items": [ + "Jede VMID existiert, ist einzigartig und behält die erwartete Instanzidentität.", + "Jeder Vertrag gehört zum selben Stack, behält seine Rolle und seine aufgezeichnete native Konfiguration.", + "Das Hauptmitglied ist das letzte in start_order und das erste in stop_order.", + "Es fehlt keine Member-, Volume- oder Rootfs-Adaption.", + "Der Haken zeigt auf das offizielle Snippet, sein Inhalt ist unverändert und sein Vertrag entspricht dem Stapelrezept.", + "Die beobachteten Images und Digests stimmen mit den vorbereiteten OCI-Archiven überein.", + "Geräte, GPU-Profile, Mounts, Geheimnisse und Endpunkte stimmen immer noch mit den Verträgen überein.", + "Nachdem alle Abhängigkeiten ihre Prüfung bestanden haben, prüft der Endpunkt der Hauptanwendung die Integration zwischen den Mitgliedern." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Verwalten eines installierten Stapels", + "intro": "Unter Installierte OCI-Anwendungen verwalten führt jedes Mitglied zum gesamten Stack. Das Menü bietet Alle Container der Anwendung aktualisieren, Zusätzliche Pfade und Geräte ändern, Alle Container mit ihrer gespeicherten Konfiguration neu erstellen und Entfernen: Anwendung und Container löschen. Die Neuerstellung baut jeden Container anhand des gespeicherten Digests aus seinem ursprünglichen Image neu auf, ohne nach einem neueren zu suchen. Sie verwendet dieselben Schritte wie ein Update und wird für aktualisierbare Stacks angeboten.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Jedes Mitglied", + "body": "Der Vertrag des Mitglieds nennt die Haupt-VMID und die vollständige Liste der Mitglieder." + }, + { + "title": "Der Stapel ist reproduzierbar", + "body": "Identitäten, Verträge, Hook, Anpassungen und eine koordinierte Wiederholung für dieses Rezept werden validiert." + }, + { + "title": "Images zuerst", + "body": "Die Anwendung wird nicht gestoppt, bis jeder Digest gelöst, heruntergeladen und verifiziert wurde." + }, + { + "title": "Stoppen und sichern Sie das Set", + "body": "Verifizierte native Backups werden mit gestopptem Stack übernommen, sodass Anwendung und Datenbanken zum selben Zeitpunkt gehören." + }, + { + "title": "Aktualisieren und überprüfen Sie jedes Mitglied", + "body": "Anpassung, Netzwerk, Mounts, Geheimnisse und Geräte werden vor dem Gesundheitscheck jedes Mitglieds erneut angewendet." + }, + { + "title": "Alles veröffentlichen oder wiederherstellen", + "body": "Die neuen Verträge werden nur veröffentlicht, wenn jedes Mitglied besteht. Wenn einer scheitert, wird jedes Mitglied wiederhergestellt." + } + ] + } + }, + { + "table": { + "headers": [ + "Ändern", + "Was sich ändert", + "Was erhalten bleibt" + ], + "rows": [ + [ + "Extra Pfade und Geräte", + "Sie werden dem Anwendungscontainer hinzugefügt oder daraus entfernt, der neu gestartet wird. Es wird nichts neu erstellt", + "Die Daten der Anwendung, ihrer Datenbank und der anderen Container" + ], + [ + "Was die Erkennung ausführt (Immich)", + "Der Machine Learning Container nimmt das Image und die Geräte des CPU oder eines GPU des Hosts auf. Die gesamte Anwendung wird gestoppt und aktualisiert, wie in einem Update", + "Der Modell-Cache, die Bibliothek und die Datenbank. Wenn etwas fehlschlägt, werden die vorherigen Container und die vorherige Auswahl wiederhergestellt" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Ein Stack ohne koordinierte Wiederholung wird nicht aktualisiert", + "body": "Wenn die Vorbereitung eines Stapels nicht reproduziert werden kann, wird die Aktualisierung abgelehnt, bevor der Stapel gestoppt wird. Die Anwendung läuft so, wie sie ist." + } + } + ] + }, + { + "id": "failure", + "title": "6. Wenn etwas versagt", + "blocks": [ + { + "p": "Während einer Installation stoppt und entfernt ein Fehler die unvollständigen Container, die die Operation erstellt hat, und die private Bridge, wenn die Operation sie erstellt hat. Ein Stack wird nicht als gültig veröffentlicht, bis die gesamte Sequenz beendet ist." + }, + { + "p": "Ein Update ist transaktional: Alle Images werden zuerst vorbereitet, dann stoppt der Stapel, jedes Mitglied wird gesichert und das Backup verifiziert, und nur dann werden alle Rootfs ersetzt. Mitglieder beginnen eins nach dem anderen mit ihrem Gesundheitscheck; Wenn einer fehlschlägt, wird jedes Mitglied aus dem gleichen Backup-Satz wiederhergestellt, so dass die Datenbank und die Anwendung niemals zu verschiedenen Momenten gehören." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Jedes Image vorbereiten", + "stop": "Stop, Hauptelement zuerst", + "backup": "Verifiziertes Backup jedes CT", + "replace": "rootfs ersetzen", + "healthy": "Alle funktionsfähig?", + "yes": "Ja", + "no": "Nein", + "commit": "Verträge veröffentlichen", + "rollback": "Jedes Mitglied wiederherstellen" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "Was ein Stack nicht beinhaltet", + "blocks": [ + { + "list": { + "items": [ + "Eine in einem Fluss installierte Suite, wie die Arr-Suite, ist kein abhängiger Stapel: Ihre Container haben keine Startreihenfolge zwischen ihnen.", + "Das private Netzwerk ersetzt nicht die Authentifizierung, TLS oder die Konfiguration jeder Anwendung.", + "Das Backup eines LXC enthält nicht die Brücke, den Haken oder die Verträge des Rests des Stapels.", + "Prowlarr, Sonarr oder Radarr erhalten von ProxMenux keine Indexer, Profile oder Anbieter." + ] + } + } + ] + } + ] +} diff --git a/web/messages/de/docs/oci-manager/storage-network.json b/web/messages/de/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..181d3bf3 --- /dev/null +++ b/web/messages/de/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Daten, Pfade und Netzwerk | ProxMenux", + "description": "So bewahrt OCI manager Apps die Daten eines OCI-Containers: Container-Datenträger, Host-Verzeichnisse, rclone-Mounts, Adressen und private Netzwerke." + }, + "header": { + "title": "Daten, Pfade und Netzwerk", + "description": "Was im rootfs liegt, was beim Ersetzen erhalten bleibt, wie Container Daten gemeinsam nutzen und wie jeder Container seine Adresse erhält.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Persistenz wird vor dem Erstellen des LXC festgelegt", + "body": "Vor dem Erstellen des Containers werden die vom Image und seiner Compose-Datei deklarierten Volumes ausgelesen. Jeder Pfad, der eine Aktualisierung überstehen muss, erhält einen vom rootfs unabhängigen Mountpoint. Das rootfs enthält damit nur die zum Image gehörenden Dateien und kann ersetzt werden." + } + } + ] + }, + { + "id": "questions", + "title": "Was der Installer abfragt", + "intro": "Die Vorlage gibt die von der Anwendung benötigten Pfade vor. Jeder wird auf einem Container-Datenträger oder in einem Host-Verzeichnis abgelegt. Vor der Zusammenfassung können weitere Pfade ergänzt werden.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Benötigte Pfade", + "body": "/config, /data, Bibliotheken, Downloads und alle von der Anwendung deklarierten Volumes werden aufgelistet." + }, + { + "title": "Speicherort", + "body": "Jeder Pfad wird auf einem Container-Datenträger oder in einem vorhandenen Host-Verzeichnis abgelegt." + }, + { + "title": "Speicher und Größe", + "body": "Ein Container-Datenträger wird mit der angegebenen Größe auf einem Proxmox-VE-Speicher erstellt. Die Standardinstallation verwendet local-lvm. Die erweiterte Installation fragt für jeden Pfad zuerst den Speicher und dann die Größe ab; vorgeschlagen wird der Speicher des rootfs. Der Datenträger erscheint als vm-VMID-disk-N und wird als mpN eingebunden." + }, + { + "title": "Host-Verzeichnis", + "body": "Das Host-Verzeichnis wird über seinen Pfad angegeben. Existiert es noch nicht, wird es angelegt und dem Host-Benutzer zugeordnet, auf den der Container-Benutzer abgebildet wird." + }, + { + "title": "Weitere Pfade", + "body": "Vor der Installation können weitere Zuordnungen von Host-Pfad oder Volume zu einem Pfad im Container ergänzt werden." + }, + { + "title": "Zusammenfassung", + "body": "Vor dem Erstellen des CT wird die vollständige Pfadzuordnung angezeigt und im Instanzvertrag gespeichert." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "Die beiden persistenten Speicherorte", + "blocks": [ + { + "table": { + "headers": [ + "Eigenschaft", + "Container-Datenträger", + "Host-Verzeichnis" + ], + "rows": [ + [ + "In der Konfiguration", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Containersicherung (vzdump)", + "Mit backup=1 enthalten", + "Nicht enthalten" + ], + [ + "Größe", + "Festgelegt; durch Vergrößern des Mountpoints erweiterbar", + "Der freie Platz im Dateisystem oder Dataset des Hosts" + ], + [ + "Andere Container", + "Nur vom zugehörigen Container eingebunden", + "Dasselbe Verzeichnis kann in mehrere Container eingebunden werden" + ], + [ + "Snapshots und Wiederherstellung", + "Von Proxmox VE zusammen mit dem CT verwaltet", + "Im Speicher des Hosts verwaltet" + ], + [ + "Beim Entfernen der Anwendung", + "Wird mit dem Container gelöscht", + "Bleibt mit seinem Inhalt erhalten" + ], + [ + "Beim Verschieben des CT auf einen anderen Knoten", + "Wird mit dem CT verschoben", + "Derselbe Pfad muss auf dem anderen Knoten vorhanden sein" + ] + ] + } + }, + { + "p": "Das rootfs ist den Binärdateien und dem Inhalt des Images vorbehalten. Eine Aktualisierung oder Neuerstellung ersetzt es, ohne eine der beiden Arten von Mountpoints anzutasten." + } + ] + }, + { + "id": "example", + "title": "Beispiel: ein Container mit beiden Speicherorten", + "blocks": [ + { + "code": { + "title": "Jellyfin als CT 151 auf local-lvm installiert (Auszug)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "Eine Aktualisierung oder Neuerstellung ersetzt nur das rootfs: mp0 bewahrt Benutzer, Bibliotheken und Einstellungen, während mp1 weiterhin dieselben Medien bereitstellt. Die Wiederherstellung der CT-Sicherung stellt /config wieder her. Das Medienverzeichnis wird bei Bedarf aus der Sicherung des Host-Speichers wiederhergestellt." + }, + { + "flow": { + "nodes": [ + { + "label": "Vertrag", + "detail": "/config" + }, + { + "label": "Speicherort", + "detail": "Container-Datenträger\noder Host-Verzeichnis" + }, + { + "label": "LXC", + "detail": "Für die Anwendung\nimmer /config" + } + ], + "caption": "Die Anwendung sieht den vom Image vorgegebenen Pfad. Nur der Speicherort ändert sich." + } + } + ] + }, + { + "id": "shared", + "title": "Ein Host-Verzeichnis, mehrere Container", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Host-Verzeichnis", + "config": "Eigene Konfiguration" + } + } + }, + { + "p": "Jeder Container speichert seine Konfiguration auf einem eigenen Datenträger. Bibliothek oder Downloads liegen in einem gemeinsamen Host-Verzeichnis, das in allen Containern unter demselben internen Pfad eingebunden wird. So ist der Pfad, in den eine Anwendung schreibt, derselbe, aus dem eine andere liest." + } + ] + }, + { + "id": "rclone", + "title": "Cloud-Speicher über die rclone-Anwendung", + "intro": "Der rclone-Eintrag im Katalog bietet neben der Installation auch Mount in einem vorhandenen rclone-OCI-Container aktivieren. Dabei wird ein bereits in der rclone-Weboberfläche angelegtes und autorisiertes Remote eingehängt und auf dem Host bereitgestellt. Andere Container können es als Host-Verzeichnis nutzen.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Container und Remote", + "body": "Die VMID des rclone-Containers, der genaue Name des Remotes und optional ein Pfad darin." + }, + { + "title": "Mount-Name und Cache", + "body": "Der Name des Mounts und der VFS-Cache-Modus: off, minimal, writes oder full (Standard)." + }, + { + "title": "Bereitgestellte Ansichten", + "body": "Ein gemeinsames Stammverzeichnis, standardmäßig /mnt/oci-shared, enthält eine Ansicht mit Lese- und Schreibzugriff unter /mnt/oci-shared/remotes/NAME sowie eine schreibgeschützte Ansicht unter /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Aktivierung", + "body": "Nach der Bestätigung wird der CT gestoppt. Sein Startbefehl und ein Proxmox-VE-Hookscript werden eingerichtet, dann wird er wieder gestartet. Der Vorgang wartet, bis beide Ansichten auf dem Host eingehängt sind." + } + ] + } + }, + { + "calloutInfo": { + "title": "Wenn der Mount nicht zustande kommt", + "body": "Die vorherige Container-Konfiguration wird wiederhergestellt und der Container erneut gestartet. Eine fehlgeschlagene Aktivierung lässt rclone damit im bisherigen Zustand." + } + } + ] + }, + { + "id": "network", + "title": "Adressen und Netzwerke", + "intro": "Eine einzelne Anwendung benötigt eine Adresse. Eine Anwendung aus mehreren Containern braucht zusätzlich ein stabiles Netzwerk zwischen ihren Mitgliedern.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Anwendung mit einem Container", + "body": "Gewählt werden die Netzwerk-Bridge sowie DHCP oder eine feste Adresse im CIDR-Format. Der LXC hat eine eigene Adresse, und die Zusammenfassung zeigt die vollständigen Dienst-URLs." + }, + { + "icon": "waypoints", + "title": "Anwendung mit mehreren Containern", + "body": "Ein freies Subnetz wird ermittelt und eine dauerhafte private Bridge erstellt. Jedes Mitglied – Anwendung, Datenbank und Cache – erhält dort eine feste Adresse." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Erreichbare Adresse\nnur für den Hauptdienst" + }, + { + "label": "Haupt-LXC", + "detail": "Web / API\nLAN + privates Netzwerk" + }, + { + "label": "Privates Netzwerk", + "detail": "PostgreSQL · Valkey · ML\nfeste Adressen" + } + ], + "caption": "Abhängigkeiten kommunizieren über das private Netzwerk und haben keine Adresse im LAN." + } + }, + { + "p": "Der Stack-Vertrag speichert Bridge, Subnetz, Adressen und Beziehungen zwischen den Diensten. Bei einer Aktualisierung der Anwendung wird dieselbe Topologie für alle Mitglieder wiederverwendet. ProxMenux Monitor öffnet den Hauptcontainer über seine LAN-Adresse, nicht über seine Adresse im privaten Netzwerk." + } + ] + }, + { + "id": "ownership", + "title": "Datei- und Verzeichnisbesitz", + "blocks": [ + { + "list": { + "items": [ + "Neue Verzeichnisse werden mit der UID und GID angelegt, auf die der unprivilegierte LXC abgebildet wird.", + "Bei vorhandenen Verzeichnissen wird der Eigentümer nicht rekursiv geändert.", + "Sockets, Systemdateien und sensible Pfade werden nicht als allgemeine Host-Verzeichnisse angeboten." + ] + } + } + ] + }, + { + "id": "backup", + "title": "Was eine Containersicherung enthält", + "blocks": [ + { + "table": { + "headers": [ + "Bestandteil", + "In der vzdump-Sicherung des CT", + "Aufbewahrungsort" + ], + "rows": [ + [ + "OCI-rootfs", + "Ja", + "In der CT-Sicherung; außerdem aus Image und Vertrag neu erstellbar" + ], + [ + "Container-Datenträger mit backup=1", + "Ja", + "Die CT-Sicherung" + ], + [ + "Host-Verzeichnis", + "Nein", + "Die Sicherung des Host-Speichers" + ], + [ + "Instanzvertrag", + "Ja, die im CT mitgeführte Kopie", + "/.proxmenux/oci-record.json im CT; weitere Host- und Cluster-Kopien werden separat gespeichert" + ], + [ + "Anwendung mit mehreren Containern", + "Jedes Mitglied in einer eigenen Sicherung", + "Die Sicherungen aller Mitglieder sowie der Stack-Vertrag und seine Netzwerk-Bridge auf dem Host" + ] + ] + } + } + ] + } + ] +} diff --git a/web/messages/en/docs/oci-manager/custom-image.json b/web/messages/en/docs/oci-manager/custom-image.json index 7b3fc108..5ae7cb95 100644 --- a/web/messages/en/docs/oci-manager/custom-image.json +++ b/web/messages/en/docs/oci-manager/custom-image.json @@ -192,7 +192,7 @@ "list": { "items": [ "A Dockerfile without a published image: the image has to be built and published in an OCI registry first.", - "A Compose file with several services: it is blocked because it describes more than one image. This option installs a single container.", + "A Compose file with several services is blocked: this option installs one service in one container at a time, even when several services use the same image.", "configs, external secrets or device formats that cannot be translated unambiguously.", "Shell substitutions such as $(command): the resulting value has to be written instead.", "docker run options that are not recognised, and --env-file: an explicit Compose file is read instead." diff --git a/web/messages/en/docs/oci-manager/lifecycle.json b/web/messages/en/docs/oci-manager/lifecycle.json index 96ccaf84..cdb3f103 100644 --- a/web/messages/en/docs/oci-manager/lifecycle.json +++ b/web/messages/en/docs/oci-manager/lifecycle.json @@ -215,7 +215,7 @@ { "id": "restore", "title": "Backup, restore and another host", - "intro": "A backup made with vzdump or Proxmox Backup Server includes the container, its disks and the copy of its contract. What the installation keeps on the host is not part of it. Manage installed OCI applications detects the containers restored on a host that has no contract for them, a new Proxmox installation or another host, and offers to register them again.", + "intro": "A backup made with vzdump or Proxmox Backup Server includes the container, the container disks selected for backup and the copy of its contract. What the installation keeps on the host is not part of it. Manage installed OCI applications detects the containers restored on a host that has no contract for them, a new Proxmox installation or another host, and offers to register them again.", "blocks": [ { "steps": { @@ -462,7 +462,7 @@ ], [ "Container disks", - "Proxmox moves them with the container. HA needs them on shared storage." + "Proxmox moves them with the container. HA needs their data available on the target node, through shared storage or a supported replication setup." ], [ "Host directories and devices", diff --git a/web/messages/en/docs/oci-manager/storage-network.json b/web/messages/en/docs/oci-manager/storage-network.json index 5c4fee47..4665d890 100644 --- a/web/messages/en/docs/oci-manager/storage-network.json +++ b/web/messages/en/docs/oci-manager/storage-network.json @@ -179,7 +179,7 @@ ["OCI rootfs", "Yes", "The CT backup; it can also be rebuilt from the image and the contract"], ["Container disk with backup=1", "Yes", "The CT backup"], ["Host directory", "No", "The backup of the host storage"], - ["Instance contract", "No", "/usr/local/share/proxmenux/oci/instances/VMID/ on the host"], + ["Instance contract", "Yes, the copy carried inside the CT", "/.proxmenux/oci-record.json inside the CT; additional host and cluster copies are kept separately"], ["Multi-container application", "Each member in its own backup", "The backups of every member, plus the stack contract and its bridge on the host"] ] } diff --git a/web/messages/es/docs/oci-manager/architecture.json b/web/messages/es/docs/oci-manager/architecture.json index b9b22769..75094840 100644 --- a/web/messages/es/docs/oci-manager/architecture.json +++ b/web/messages/es/docs/oci-manager/architecture.json @@ -1,17 +1,17 @@ { "meta": { - "title": "Cómo se traduce una imagen OCI | ProxMenux", - "description": "Del repositorio de la imagen y su archivo Compose a una plantilla revisable, un plan de despliegue y un LXC nativo de Proxmox VE, sin Docker dentro." + "title": "Cómo se adapta una imagen OCI | ProxMenux", + "description": "Del repositorio de la imagen y su archivo Compose a una plantilla que puedes revisar, un plan de despliegue y un LXC nativo de Proxmox VE, sin Docker dentro." }, "header": { - "title": "Cómo se traduce una imagen OCI", - "description": "Del repositorio de la imagen y su archivo Compose a una plantilla revisable, un plan de despliegue y un LXC nativo, sin instalar Docker dentro.", + "title": "Cómo se adapta una imagen OCI", + "description": "Del repositorio de la imagen y su archivo Compose a una plantilla que puedes revisar, un plan de despliegue y un LXC nativo, sin instalar Docker dentro.", "section": "OCI manager Apps" }, "sections": [ { "id": "pipeline", - "title": "La cadena de traducción", + "title": "El proceso de adaptación", "blocks": [ { "mermaid": { @@ -20,7 +20,7 @@ "repo": "Repositorio de la imagen", "converter": "Conversor", "template": "Plantilla JSON", - "blockers": "¿Sin bloqueos?", + "blockers": "¿Sin impedimentos?", "no": "No", "yes": "Sí", "review": "Revisión / overlay", @@ -30,7 +30,7 @@ } }, { - "p": "El conversor lee la imagen y el archivo Compose que publica su proyecto y escribe una plantilla JSON. Una plantilla con bloqueos sin traducir pasa por revisión, donde un overlay curado los resuelve, antes de publicarse en el catálogo. Solo las plantillas sin bloqueos se ofrecen para instalar." + "p": "El conversor lee la imagen y el archivo Compose publicados por el proyecto y genera una plantilla JSON. Si quedan requisitos que no se han podido adaptar y que impiden la instalación, la plantilla pasa por una revisión: un archivo de ajustes revisados, llamado overlay, los resuelve antes de publicarla en el catálogo. Solo se ofrecen para instalar las plantillas que no tienen impedimentos pendientes." } ] }, @@ -41,10 +41,26 @@ { "cards": { "items": [ - { "icon": "archive", "title": "Identidad de la imagen", "body": "Repositorio, etiqueta rolling, arquitectura, digest resuelto y revisión del origen." }, - { "icon": "braces", "title": "Contrato del contenedor", "body": "Entrypoint, Cmd, entorno, usuario, directorio de trabajo, señal de parada, puertos y volúmenes." }, - { "icon": "layers", "title": "Traducción a Proxmox VE", "body": "Recursos, seguridad, puntos de montaje, dispositivos, sysctls, healthchecks y las adaptaciones que necesita cada uno, con su motivo." }, - { "icon": "shield", "title": "Compatibilidad", "body": "Claves admitidas, bloqueos sin traducir y el estado de cada validación." } + { + "icon": "archive", + "title": "Identidad de la imagen", + "body": "Repositorio, etiqueta de actualización continua, arquitectura, digest resuelto y revisión de origen." + }, + { + "icon": "braces", + "title": "Contrato del contenedor", + "body": "Entrypoint, Cmd, entorno, usuario, directorio de trabajo, señal de parada, puertos y volúmenes." + }, + { + "icon": "layers", + "title": "Adaptación a Proxmox VE", + "body": "Recursos, seguridad, puntos de montaje, dispositivos, sysctls, comprobaciones de estado y las adaptaciones necesarias en cada caso, con su justificación." + }, + { + "icon": "shield", + "title": "Compatibilidad", + "body": "Claves compatibles, requisitos pendientes de adaptar que impiden la instalación y estado de cada validación." + } ] } } @@ -56,12 +72,32 @@ "blocks": [ { "table": { - "headers": ["Origen", "Ejemplo", "Resultado nativo"], + "headers": [ + "Origen", + "Ejemplo", + "Resultado nativo" + ], "rows": [ - ["Metadatos OCI", "Entrypoint, Cmd, User", "Proxmox VE los importa al crear el CT"], - ["Docker Compose", "environment, volumes, devices", "entradas de entorno del LXC, mpN y devN"], - ["Perfil de ProxMenux", "GPU, healthcheck, credenciales", "preguntas y adaptaciones revisadas"], - ["Usuario", "VMID, almacenamiento, red", "el contrato de la instancia"] + [ + "Metadatos OCI", + "Entrypoint, Cmd, User", + "Proxmox VE los importa al crear el CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Entradas de entorno de LXC, mpN y devN" + ], + [ + "Perfil de ProxMenux", + "GPU, comprobación de estado, credenciales", + "Preguntas y adaptaciones revisadas" + ], + [ + "Usuario", + "VMID, almacenamiento, red", + "El contrato de la instancia" + ] ] } } @@ -69,18 +105,39 @@ }, { "id": "install", - "title": "Qué ocurre durante una instalación", + "title": "Qué ocurre durante la instalación", "blocks": [ { "steps": { "items": [ - { "title": "Resolver", "body": "Se consulta el registro, se selecciona la arquitectura del host y se fija el digest efectivo de la etiqueta rolling." }, - { "title": "Descargar y verificar", "body": "Skopeo descarga la imagen como archivo OCI, y cada capa se comprueba contra su digest y se descomprime antes de crear nada. Una descarga dañada se repite una vez antes de detener la instalación." }, - { "title": "Construir", "body": "pct create construye el rootfs a partir del archivo y conserva los metadatos oficiales de proceso de la imagen." }, - { "title": "Conectar", "body": "Se añaden los volúmenes, la red, el entorno, los dispositivos y los perfiles de seguridad declarados." }, - { "title": "Consola", "body": "La salida de consola del contenedor se guarda en el host, y la consola de Proxmox VE abre un shell cuando la imagen incluye uno." }, - { "title": "Comprobar", "body": "El primer arranque espera una dirección y la respuesta del servicio; un fallo no se presenta como instalación correcta." }, - { "title": "Registrar", "body": "La configuración efectiva se escribe en el contrato de la instancia que usan las actualizaciones y las recreaciones." } + { + "title": "Resolver la referencia", + "body": "Se consulta el registro, se selecciona la arquitectura del host y se fija el digest concreto al que apunta la etiqueta de actualización continua." + }, + { + "title": "Descargar y verificar", + "body": "Skopeo descarga la imagen como un archivo OCI. Antes de crear nada, se verifica el digest de cada capa y se descomprimen todas las capas. Si la descarga está dañada, se vuelve a descargar una vez antes de detener la instalación." + }, + { + "title": "Crear el sistema de archivos", + "body": "pct create crea el rootfs a partir del archivo y conserva los metadatos oficiales del proceso de la imagen." + }, + { + "title": "Conectar los recursos", + "body": "Se aplican los volúmenes, la red, el entorno, los dispositivos y los perfiles de seguridad declarados." + }, + { + "title": "Consola", + "body": "La salida de consola del contenedor se guarda en el host. La consola de Proxmox VE abre una shell si la imagen incluye una." + }, + { + "title": "Comprobar", + "body": "En el primer arranque se espera a que el contenedor tenga una dirección y el servicio responda. Si falla, la instalación no se da por completada correctamente." + }, + { + "title": "Registrar la configuración", + "body": "La configuración efectiva se guarda en el contrato de la instancia que utilizan las actualizaciones y las recreaciones." + } ] } } @@ -89,7 +146,7 @@ { "id": "example", "title": "Ejemplo: una imagen con /config y /downloads", - "intro": "Una definición habitual de Compose y la configuración de Proxmox VE en la que se convierte. Las rutas que espera la aplicación no cambian.", + "intro": "Una definición Compose habitual y la configuración de Proxmox VE que se genera a partir de ella. Las rutas que espera la aplicación no cambian.", "blocks": [ { "codeGrid": { @@ -99,14 +156,14 @@ "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" }, { - "title": "/etc/pve/lxc/VMID.conf (extracto)", + "title": "/etc/pve/lxc/VMID.conf (fragmento)", "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" } ] } }, { - "p": "mp0 es un segundo disco que pertenece al contenedor, con el nombre vm-VMID-disk-N en el almacenamiento elegido. Se monta en /config y, con backup=1, forma parte del backup del contenedor. mp1 no crea ningún disco: enlaza el directorio del host /srv/downloads con /downloads dentro del LXC. El puerto 8080 no se mapea: el LXC tiene una dirección propia y el servicio responde en ella." + "p": "mp0 es un segundo disco que pertenece al contenedor, con el nombre vm-VMID-disk-N en el almacenamiento seleccionado. Se monta en /config y, con backup=1, se incluye en la copia de seguridad del contenedor. mp1 no crea ningún disco: monta el directorio del host /srv/downloads en /downloads dentro del LXC. El puerto 8080 no se mapea: el LXC tiene su propia dirección y el servicio responde en ella." } ] } diff --git a/web/messages/es/docs/oci-manager/custom-image.json b/web/messages/es/docs/oci-manager/custom-image.json index 5a9316c7..c1f9642d 100644 --- a/web/messages/es/docs/oci-manager/custom-image.json +++ b/web/messages/es/docs/oci-manager/custom-image.json @@ -1,11 +1,11 @@ { "meta": { - "title": "Instalar una imagen que no está en el catálogo | ProxMenux", - "description": "Traducción de una imagen OCI desde un archivo Compose, una URL, un comando docker run o una referencia de imagen, con revisión de lo que ProxMenux puede reproducir antes de instalarla." + "title": "Instalar una imagen fuera del catálogo | ProxMenux", + "description": "Adapta una imagen OCI a partir de un archivo Compose, una URL, un comando docker run o una referencia de imagen, y revisa qué puede reproducir ProxMenux antes de instalarla." }, "header": { - "title": "Instalar una imagen que no está en el catálogo", - "description": "Una imagen propia se traduce desde su archivo Compose, un comando docker run o solo su referencia, y el resultado se presenta para revisión antes de crear ningún LXC.", + "title": "Instalar una imagen que no esté en el catálogo", + "description": "Tu imagen se adapta a partir de su archivo Compose, de un comando docker run o de su referencia. El resultado se muestra para que lo revises antes de crear ningún LXC.", "section": "OCI manager Apps" }, "sections": [ @@ -14,8 +14,8 @@ "blocks": [ { "calloutInfo": { - "title": "El mismo conversor, el mismo instalador", - "body": "La opción Instalar una imagen que no está en el catálogo utiliza el conversor y el instalador de las plantillas del catálogo. La diferencia es que el contrato se genera en ese momento a partir de la definición aportada y se valida antes de crear ningún LXC." + "title": "El mismo conversor y el mismo instalador", + "body": "La opción Instalar una imagen que no esté en el catálogo utiliza el mismo conversor y el mismo instalador que las plantillas del catálogo. La diferencia es que el contrato se genera en ese momento a partir de la definición proporcionada y se valida antes de crear ningún LXC." } } ] @@ -23,45 +23,92 @@ { "id": "sources", "title": "Cómo se describe la imagen", - "intro": "La primera pantalla pregunta cómo se describe la imagen. Cuatro formas aportan una definición que se traduce, una quinta usa solo el nombre de la imagen y tres reciben un oci-compose ya escrito.", + "intro": "La primera pantalla pregunta cómo vas a describir la imagen. Cuatro opciones reciben una definición que se adapta, una quinta usa solo el nombre de la imagen y otras tres leen un oci-compose ya escrito.", "blocks": [ { "table": { - "headers": ["Opción", "Qué lee"], + "headers": [ + "Opción", + "Qué lee" + ], "rows": [ - ["Pegar su archivo Compose en la terminal", "El YAML se pega en la terminal y termina con Ctrl+D."], - ["Leer su archivo Compose desde un archivo de este host", "Una ruta del nodo, /root/docker-compose.yml por defecto. El archivo debe existir y ocupar como máximo 256 KiB."], - ["Descargar su archivo Compose desde una dirección", "Una dirección http:// o https:// que sirva el YAML sin procesar. Se descargan como máximo 256 KiB."], - ["Pegar su comando docker run en la terminal", "El comando que publica el proyecto. Se traducen puertos, entorno, volúmenes, dispositivos, capacidades, usuario, memoria compartida y el resto de opciones admitidas."], - ["Solo la referencia de la imagen, sin archivo Compose", "Una referencia como ghcr.io/user/application:latest. Se consulta el registro y se conservan los metadatos OCI de la imagen."], - ["Pegar un oci-compose en la terminal", "El JSON se pega en la terminal y termina con Ctrl+D. No se traduce: se comprueba y se instala tal como está escrito."], - ["Leer un oci-compose desde un archivo de este host", "Un archivo JSON del nodo, /root/oci-compose.json por defecto, de 2 MiB como máximo."], - ["Descargar un oci-compose desde una dirección", "Una dirección http:// o https:// que sirva el archivo JSON. Se descargan como máximo 2 MiB."] + [ + "Pegar su archivo Compose en la terminal", + "Pega el YAML en la terminal y termina con Ctrl+D." + ], + [ + "Leer su archivo Compose desde este host", + "Una ruta del nodo; la predeterminada es /root/docker-compose.yml. El archivo debe existir y no superar los 256 KiB." + ], + [ + "Descargar su archivo Compose desde una dirección", + "Una dirección http:// o https:// que sirva el YAML sin procesar. Se descargan como máximo 256 KiB." + ], + [ + "Pegar su comando docker run en la terminal", + "El comando publicado por el proyecto. Se adaptan los puertos, el entorno, los volúmenes, los dispositivos, las capabilities, el usuario, la memoria compartida y las demás opciones compatibles." + ], + [ + "Solo la referencia de imagen, sin archivo Compose", + "Una referencia como ghcr.io/user/application:latest. Se consulta el registro y se conservan los metadatos OCI de la imagen." + ], + [ + "Pegar un oci-compose en la terminal", + "Pega el JSON en la terminal y termina con Ctrl+D. No se convierte: se valida y se instala tal como está escrito." + ], + [ + "Leer un oci-compose desde este host", + "Un archivo JSON del nodo, /root/oci-compose.json por defecto, de un máximo de 2 MiB." + ], + [ + "Descargar un oci-compose desde una dirección", + "Una dirección http:// o https:// que sirva el archivo JSON. Se descargan como máximo 2 MiB." + ] ] } }, { "figure": { "src": "/oci-manager/custom-source-menu.png", - "alt": "Menú que pregunta cómo se describe una imagen que no está en el catálogo", - "caption": "Las formas de describir una imagen que no está en el catálogo." + "alt": "Menú para elegir cómo describir una imagen que no está en el catálogo", + "caption": "Formas de describir una imagen fuera del catálogo." } } ] }, { "id": "reference-only", - "title": "Qué aporta solo la referencia de la imagen", - "intro": "Una imagen lleva sus metadatos de proceso, pero no todo lo que un archivo Compose suele añadir a su alrededor.", + "title": "Qué aporta una referencia de imagen por sí sola", + "intro": "La imagen contiene los metadatos de su proceso, pero no todo el entorno que suele añadir un archivo Compose.", "blocks": [ { "table": { - "headers": ["Se lee de la imagen", "No está en la imagen", "Consecuencia"], + "headers": [ + "Se lee de la imagen", + "No está en la imagen", + "Consecuencia" + ], "rows": [ - ["Entrypoint, Cmd, User, WorkingDir y entorno integrado", "Variables que solo aparecen en la documentación", "Se añaden durante la instalación o con Modificar"], - ["Volúmenes declarados por la imagen", "Directorios del host que solo nombra un archivo Compose", "Las rutas adicionales se añaden antes de instalar"], - ["Puertos EXPOSE", "La URL, el protocolo o un healthcheck funcional", "Se confirman el puerto y la forma de comprobar el servicio"], - ["Arquitecturas y digest del registro", "Dispositivos, privilegios o dependencias externas", "Ninguno se activa sin una definición que lo pida"] + [ + "Entrypoint, Cmd, User, WorkingDir y el entorno incorporado", + "Variables que solo figuran en la documentación", + "Se añaden durante la instalación o desde Modificar" + ], + [ + "Volúmenes declarados por la imagen", + "Directorios del host que solo se indican en Compose", + "Las rutas adicionales se añaden antes de instalar" + ], + [ + "Puertos EXPOSE", + "La URL, el protocolo o la comprobación funcional del servicio", + "Se confirman el puerto y la forma de comprobar el servicio" + ], + [ + "Arquitecturas y digest del registro", + "Dispositivos, privilegios o dependencias externas", + "No se habilitan sin una definición que los solicite" + ] ] } } @@ -69,29 +116,44 @@ }, { "id": "review", - "title": "Revisión antes de instalar", - "intro": "Tras traducir la definición, ProxMenux muestra lo que ha entendido antes de pedir VMID, recursos o almacenamiento.", + "title": "Revisar antes de instalar", + "intro": "Tras adaptar la definición, ProxMenux muestra cómo la ha interpretado antes de preguntar el VMID, los recursos o el almacenamiento.", "blocks": [ { "steps": { "items": [ - { "title": "Analizar", "body": "Se leen servicios, imagen, puertos, rutas, entorno, dispositivos, seguridad y healthcheck." }, - { "title": "Consultar el registro", "body": "La imagen debe existir en su registro, y se enumeran las arquitecturas que publica. Un registro privado se lee con la cuenta guardada para él." }, - { "title": "Enumerar lo que no se aplica", "body": "Se enumeran las etiquetas, las redes Docker, los ajustes de Swarm y el resto de claves que no tienen efecto en un LXC." }, - { "title": "Bloquear lo que no puede traducirse", "body": "Una clave sin equivalencia segura aparece en Lo que no puede traducirse y la instalación no continúa. Nada se descarta en silencio." }, - { "title": "Pedir los secretos", "body": "Las variables cuyo nombre corresponde a una contraseña, un token o una clave se convierten en preguntas sensibles de la instalación." } + { + "title": "Analizar", + "body": "Se leen servicios, imagen, puertos, rutas, entorno, dispositivos, seguridad y comprobaciones de estado." + }, + { + "title": "Consultar el registro", + "body": "La imagen debe existir en el registro y se muestran las arquitecturas publicadas. Si el registro es privado, se utiliza la cuenta guardada para él." + }, + { + "title": "Enumerar lo que no se aplica", + "body": "Se enumeran las etiquetas, redes de Docker, ajustes de Swarm y otras claves que no tienen efecto en un LXC." + }, + { + "title": "Bloquear opciones sin equivalente seguro", + "body": "Las claves sin equivalente seguro aparecen en Opciones sin equivalente e impiden continuar con la instalación. No se descartan silenciosamente." + }, + { + "title": "Solicitar secretos", + "body": "Las variables cuyo nombre indica una contraseña, token o clave se solicitan como datos sensibles durante la instalación." + } ] } }, { "figure": { "src": "/oci-manager/custom-review.png", - "alt": "Resumen de lo que ProxMenux ha entendido de un archivo Compose", - "caption": "El resumen que se muestra antes de que la instalación pida VMID y recursos." + "alt": "Resumen de cómo ProxMenux ha interpretado el archivo Compose", + "caption": "Resumen mostrado antes de que el instalador pregunte el VMID y los recursos." } }, { - "p": "Tras aceptar el resumen, el recorrido es el del catálogo: nombre, modo predeterminado o avanzado, VMID, CPU, memoria, red, arranque con el nodo, rutas persistentes, rutas adicionales, dispositivos compatibles y resumen final." + "p": "Tras aceptar el resumen se sigue el mismo asistente que en el catálogo: nombre, modo predeterminado o avanzado, VMID, CPU, memoria, red, arranque con el nodo, rutas persistentes, rutas adicionales, dispositivos compatibles y resumen final." } ] }, @@ -118,68 +180,79 @@ { "id": "oci-compose", "title": "Un oci-compose escrito a mano", - "intro": "Un oci-compose es el archivo JSON desde el que se instala una aplicación. El catálogo guarda uno por cada aplicación, y el mismo formato puede escribirse a mano e instalarse con esta opción.", + "intro": "Un oci-compose es el archivo JSON a partir del cual se instala una aplicación. El catálogo conserva uno por aplicación. Puedes escribir el mismo formato a mano e instalarlo con esta opción.", "blocks": [ { - "p": "El que se escribe a mano indica lo que decide su autor: la imagen, sus puertos, variables y volúmenes, y lo que pregunta la instalación. ProxMenux completa el resto al leer el archivo. Puede decir lo que un archivo Compose no puede: el texto de una pregunta, una contraseña generada durante la instalación, un perfil de GPU, el propietario de cada volumen, la comprobación del primer arranque o las credenciales que se muestran al terminar." + "p": "Al escribirlo a mano, el autor decide la imagen, los puertos, las variables, los volúmenes y las preguntas de la instalación. ProxMenux completa el resto al leer el archivo. Permite expresar lo que Compose no incluye: el texto de una pregunta, una contraseña generada durante la instalación, un perfil de GPU, el propietario de cada volumen, la comprobación del primer arranque o las credenciales mostradas al final." }, { "steps": { "items": [ { "title": "Comprobar", - "body": "Un archivo con errores no se instala. Cada error indica el campo en el que está, y un archivo que no es JSON válido indica la línea y la columna." + "body": "Los archivos con errores no se instalan. Cada error indica el campo afectado; si el JSON no es válido, se muestran la línea y la columna." }, { "title": "Revisar", - "body": "Un archivo válido muestra lo que instala: imagen, dirección web, puertos, rutas persistentes, las variables que pide la instalación y los archivos que escribe." + "body": "Si el archivo es válido, se muestra lo que instalará: imagen, dirección web, puertos, rutas persistentes, variables que solicitará el instalador y archivos que escribirá." }, { "title": "Instalar", - "body": "La instalación es la de cualquier aplicación del catálogo, con ajustes por defecto o avanzados." + "body": "Se utiliza el mismo instalador que para cualquier aplicación del catálogo, con ajustes predeterminados o avanzados." } ] } }, { "calloutWarning": { - "title": "Un archivo que decide privilegios", - "body": "Un oci-compose decide qué se monta en el contenedor y con qué privilegios. El resumen lo indica antes de instalar: instala solo archivos que vengan de una fuente de confianza." + "title": "Un archivo que decide los privilegios", + "body": "El oci-compose determina qué se monta dentro del contenedor y con qué privilegios. La revisión lo muestra antes de instalar: utiliza únicamente archivos de fuentes de confianza." } }, { - "p": "El formato se describe bloque a bloque, con dos ejemplos completos, en el README del motor OCI. El registro de una instalación se lee de la misma forma: solo se usa la plantilla con la que se instaló, y ninguna de las respuestas de esa instalación." + "p": "El README del motor OCI explica el formato bloque a bloque e incluye dos ejemplos completos. También se puede leer el registro de una instalación: solo se utiliza la plantilla original, sin incorporar ninguna de las respuestas de aquella instalación." } ] }, { "id": "private-registries", - "title": "Imágenes en registros privados", - "intro": "Una imagen en un registro privado, como un paquete privado de GitHub o un repositorio privado de Docker Hub, se lee con una cuenta guardada en ProxMenux.", + "title": "Imágenes de registros privados", + "intro": "Las imágenes de registros privados, como un paquete privado de GitHub o un repositorio privado de Docker Hub, se consultan con una cuenta guardada en ProxMenux.", "blocks": [ { "steps": { "items": [ - { "title": "El registro rechaza la imagen", "body": "Cuando el registro responde que la imagen necesita una cuenta, la revisión ofrece iniciar sesión en ese registro y vuelve a leer la imagen." }, - { "title": "Iniciar sesión", "body": "Un usuario y una contraseña o un token de acceso que puedan descargar la imagen. El registro los comprueba y solo se guardan si los acepta." }, - { "title": "Instalar y mantener al día", "body": "La instalación descarga la imagen con esa cuenta, y también las actualizaciones, Modificar, Recrear, la vigilancia y la comprobación de actualizaciones del Monitor, en cualquier nodo del clúster." } + { + "title": "El registro deniega el acceso a la imagen", + "body": "Si el registro indica que la imagen requiere una cuenta, la revisión ofrece iniciar sesión en ese registro y vuelve a consultar la imagen." + }, + { + "title": "Iniciar sesión", + "body": "Un usuario y una contraseña o token con permiso para descargar la imagen. El registro los comprueba y solo se guardan si los acepta." + }, + { + "title": "Instalar y mantener al día", + "body": "Esa cuenta se utiliza para descargar la imagen durante la instalación y también en las actualizaciones, Modificar, Recrear, el watchdog y la comprobación de actualizaciones del Monitor, en cualquier nodo del clúster." + } ] } }, - { "p": "Cuentas de registro, en el menú principal de OCI manager Apps, lista las cuentas guardadas por registro y usuario, nunca su secreto. Desde cada cuenta se puede comprobar, descargar o instalar una imagen, y actualizar o eliminar la cuenta." }, + { + "p": "Cuentas de registros, en el menú principal de OCI manager Apps, muestra las cuentas guardadas por registro y usuario, nunca sus secretos. Desde cada cuenta puedes comprobar, descargar o instalar una imagen, actualizar las credenciales o eliminarlas." + }, { "list": { "items": [ - "GitHub (ghcr.io): el usuario es la cuenta de GitHub, y la contraseña un token de acceso personal con el permiso read:packages.", - "Docker Hub (docker.io): el usuario es la cuenta de Docker Hub, y la contraseña un token de acceso con permiso de lectura.", - "Otros registros, también uno con su propio puerto como registry.example.com:5000: el usuario y la contraseña o token que da ese registro." + "GitHub (ghcr.io): el usuario es la cuenta de GitHub y la contraseña es un token de acceso personal con permiso read:packages.", + "Docker Hub (docker.io): el usuario es la cuenta de Docker Hub y la contraseña es un token con permiso de lectura.", + "Otros registros, incluidos los que usan un puerto propio como registry.example.com:5000: el usuario y la contraseña o token que proporcione ese registro." ] } }, { "calloutInfo": { - "title": "Dónde se guardan", - "body": "En /etc/pve/priv/proxmenux/registry-auth.json, legible solo por root, en la misma carpeta donde Proxmox guarda las contraseñas de sus almacenamientos. /etc/pve lo comparten todos los nodos del clúster, así que una cuenta añadida en un nodo se usa en todos. El backup del host lo incluye, y al desinstalar ProxMenux se elimina de todos los nodos." + "title": "Dónde se guardan las cuentas", + "body": "En /etc/pve/priv/proxmenux/registry-auth.json, accesible solo por root, en la misma carpeta donde Proxmox guarda las contraseñas de almacenamiento. /etc/pve se comparte entre los nodos del clúster, de modo que una cuenta añadida en un nodo se utiliza en todos. La copia del host incluye ese archivo y desinstalar ProxMenux lo elimina de todos los nodos." } } ] @@ -191,11 +264,11 @@ { "list": { "items": [ - "Un Dockerfile sin imagen publicada: la imagen debe construirse y publicarse antes en un registro OCI.", - "Un archivo Compose con varios servicios: queda bloqueado porque describe más de una imagen. Esta opción instala un único contenedor.", - "configs, secretos externos o formatos de dispositivo que no pueden traducirse sin ambigüedad.", - "Sustituciones del shell como $(command): debe escribirse el valor resultante.", - "Opciones de docker run no reconocidas y --env-file: se lee en su lugar un archivo Compose explícito." + "Un Dockerfile sin imagen publicada: primero hay que construir la imagen y publicarla en un registro OCI.", + "Un archivo Compose con varios servicios: se bloquea porque esta opción instala un único servicio por contenedor, aunque varios servicios utilicen la misma imagen.", + "configs, secretos externos o formatos de dispositivo que no puedan adaptarse de forma inequívoca.", + "Sustituciones de shell como $(command): hay que escribir directamente el valor resultante.", + "Opciones de docker run no reconocidas y --env-file: utiliza un archivo Compose con los valores explícitos." ] } } diff --git a/web/messages/es/docs/oci-manager/hardware.json b/web/messages/es/docs/oci-manager/hardware.json index b4f24154..72239db9 100644 --- a/web/messages/es/docs/oci-manager/hardware.json +++ b/web/messages/es/docs/oci-manager/hardware.json @@ -19,9 +19,18 @@ { "flow": { "nodes": [ - { "label": "Inventario del host", "detail": "/dev/dri/renderD128\nGID 993 · Intel" }, - { "label": "ProxMenux", "detail": "fabricante y\npermisos comprobados" }, - { "label": "LXC", "detail": "mismo dispositivo\nGID efectivo" } + { + "label": "Inventario del host", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "fabricante y\npermisos comprobados" + }, + { + "label": "LXC", + "detail": "mismo dispositivo\nGID efectivo" + } ] } } @@ -33,12 +42,32 @@ "blocks": [ { "table": { - "headers": ["Origen", "Qué se lee", "Qué hace el instalador"], + "headers": [ + "Origen", + "Qué se lee", + "Qué hace el instalador" + ], "rows": [ - ["Docker Compose", "devices, group_add, deploy.resources y peticiones NVIDIA", "Cada requisito se convierte en una petición de dispositivo que se muestra para revisión"], - ["Perfil del catálogo", "El soporte de GPU, Coral, OpenCL, USB o FUSE que tiene realmente la aplicación", "Solo se ofrecen las opciones validadas para esa imagen"], - ["Metadatos y documentación de la imagen", "VA-API, Selkies, mods de LinuxServer o el runtime NVIDIA", "Se añaden las variables y la preparación documentadas"], - ["Selección del usuario", "Solo CPU, Intel/AMD, OpenCL, NVIDIA o un dispositivo opcional", "La selección se guarda en el contrato de la instancia"] + [ + "Docker Compose", + "devices, group_add, deploy.resources y peticiones NVIDIA", + "Cada requisito se convierte en una petición de dispositivo que se muestra para revisión" + ], + [ + "Perfil del catálogo", + "El soporte de GPU, Coral, OpenCL, USB o FUSE que tiene realmente la aplicación", + "Solo se ofrecen las opciones validadas para esa imagen" + ], + [ + "Metadatos y documentación de la imagen", + "VA-API, Selkies, mods de LinuxServer o el runtime NVIDIA", + "Se añaden las variables y la preparación documentadas" + ], + [ + "Selección del usuario", + "Solo CPU, Intel/AMD, OpenCL, NVIDIA o un dispositivo opcional", + "La selección se guarda en el contrato de la instancia" + ] ] } }, @@ -57,16 +86,52 @@ "blocks": [ { "table": { - "headers": ["Tipo", "Identificación", "Validación"], + "headers": [ + "Tipo", + "Identificación", + "Validación" + ], "rows": [ - ["Intel/AMD DRM", "/dev/dri/renderD* y /sys/class/drm/NODO/device/vendor", "Un dispositivo de caracteres con fabricante 0x8086 (Intel) o 0x1002 (AMD)"], - ["OpenCL AMD", "El render node, más /dev/kfd cuando el perfil lo necesita", "Existencia, tipo, fabricante, permisos y compatibilidad declarada"], - ["AMD ROCm", "El nodo de render, /dev/kfd y la generación que declara el driver de cómputo en /sys/class/kfd/kfd/topology/nodes/*/properties", "Una generación para la que la imagen ROCm de la aplicación trae kernels, las GPU RDNA2 que incluye y las posteriores, se ofrece tal cual. Las demás GPU de esas familias, como las Radeon 680M y 780M, se ofrecen como experimentales y nunca son la opción propuesta: ROCm no las soporta oficialmente, la aplicación las presenta como la generación de su familia (HSA_OVERRIDE_GFX_VERSION) y el instalador pide confirmación antes de descargar la imagen, que es más grande. Una GPU anterior, como los gráficos integrados de un Ryzen 5000U, no se ofrece e Immich deja el reconocimiento en la CPU"], - ["NVIDIA", "nvidia-smi y nvidia-container-cli", "GPU, UUID, bus PCI, versión del driver, Toolkit, nodos /dev/nvidia*, binarios y librerías"], - ["Coral PCIe/M.2", "/dev/apex_N y su enlace en /sys/dev/char/MAJOR:MINOR", "Nodo de caracteres, major/minor, propietario, GID y permisos"], - ["USB y serie", "/dev/ttyUSB*, /dev/ttyACM* o /dev/bus/usb/BBB/DDD", "Nodo de caracteres; en USB también fabricante, producto y número de serie cuando sysfs los publica"], - ["KVM, TUN, FUSE, vídeo y SCSI genérico", "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN o /dev/sgN", "Ruta admitida, tipo de nodo y permisos efectivos"], - ["Unidad óptica", "/dev/srN", "Un dispositivo de bloques"] + [ + "Intel/AMD DRM", + "/dev/dri/renderD* y /sys/class/drm/NODE/device/vendor", + "Un dispositivo de caracteres con fabricante 0x8086 (Intel) o 0x1002 (AMD)" + ], + [ + "OpenCL AMD", + "El render node, más /dev/kfd cuando el perfil lo necesita", + "Existencia, tipo, fabricante, permisos y compatibilidad declarada" + ], + [ + "AMD ROCm", + "El nodo de render, /dev/kfd y la generación que declara el driver de cómputo en /sys/class/kfd/kfd/topology/nodes/*/properties", + "Una generación para la que la imagen ROCm de la aplicación trae kernels, las GPU RDNA2 que incluye y las posteriores, se ofrece tal cual. Las demás GPU de esas familias, como las Radeon 680M y 780M, se ofrecen como experimentales y nunca son la opción propuesta: ROCm no las soporta oficialmente, la aplicación las presenta como la generación de su familia (HSA_OVERRIDE_GFX_VERSION) y el instalador pide confirmación antes de descargar la imagen, que es más grande. Una GPU anterior, como los gráficos integrados de un Ryzen 5000U, no se ofrece e Immich deja el reconocimiento en la CPU" + ], + [ + "NVIDIA", + "nvidia-smi y nvidia-container-cli", + "GPU, UUID, bus PCI, versión del driver, Toolkit, nodos /dev/nvidia*, binarios y librerías" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N y su enlace en /sys/dev/char/MAJOR:MINOR", + "Nodo de caracteres, major/minor, propietario, GID y permisos" + ], + [ + "USB y serie", + "/dev/ttyUSB*, /dev/ttyACM* o /dev/bus/usb/BBB/DDD", + "Nodo de caracteres; en USB también fabricante, producto y número de serie cuando sysfs los publica" + ], + [ + "KVM, TUN, FUSE, vídeo y SCSI genérico", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN o /dev/sgN", + "Ruta admitida, tipo de nodo y permisos efectivos" + ], + [ + "Unidad óptica", + "/dev/srN", + "Un dispositivo de bloques" + ] ] } } @@ -79,12 +144,30 @@ { "steps": { "items": [ - { "title": "La plantilla ofrece sus perfiles", "body": "Por ejemplo solo CPU, Intel/AMD VA-API, OpenCL AMD, OpenCL Intel o NVIDIA. Las opciones pertenecen a la imagen, no a un menú común." }, - { "title": "Se elige un perfil", "body": "Define los nodos de dispositivo, el entorno, los mods o el runtime que necesita la aplicación." }, - { "title": "Se propone una ruta", "body": "Para DRM, /dev/dri/renderD128, que puede cambiarse en un host con varios render nodes. Para USB o serie se selecciona el nodo concreto." }, - { "title": "Validación", "body": "Se comprueban existencia, tipo, fabricante admitido, permisos y GID. Una discrepancia detiene la operación." }, - { "title": "Se escribe el contrato", "body": "Ruta, modo, GID, acceso de escritura y perfil quedan registrados para las actualizaciones y las recreaciones." }, - { "title": "Conexión y prueba", "body": "pct set añade la entrada devN y después se comprueba el acceso dentro del LXC. Las imágenes de LinuxServer se comprueban también como usuario abc." } + { + "title": "La plantilla ofrece sus perfiles", + "body": "Por ejemplo solo CPU, Intel/AMD VA-API, OpenCL AMD, OpenCL Intel o NVIDIA. Las opciones pertenecen a la imagen, no a un menú común." + }, + { + "title": "Se elige un perfil", + "body": "Define los nodos de dispositivo, el entorno, los mods o el runtime que necesita la aplicación." + }, + { + "title": "Se propone una ruta", + "body": "Para DRM, /dev/dri/renderD128, que puede cambiarse en un host con varios render nodes. Para USB o serie se selecciona el nodo concreto." + }, + { + "title": "Validación", + "body": "Se comprueban existencia, tipo, fabricante admitido, permisos y GID. Una discrepancia detiene la operación." + }, + { + "title": "Se escribe el contrato", + "body": "Ruta, modo, GID, acceso de escritura y perfil quedan registrados para las actualizaciones y las recreaciones." + }, + { + "title": "Conexión y prueba", + "body": "pct set añade la entrada devN y después se comprueba el acceso dentro del LXC. Las imágenes de LinuxServer se comprueban también como usuario abc." + } ] } } @@ -98,10 +181,22 @@ { "codeGrid": { "items": [ - { "title": "Intel/AMD VA-API", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" }, - { "title": "Coral PCIe/M.2", "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" }, - { "title": "Un dispositivo USB concreto", "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" }, - { "title": "OpenCL AMD", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" } + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Un dispositivo USB concreto", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "OpenCL AMD", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } ] } }, @@ -116,13 +211,37 @@ "blocks": [ { "table": { - "headers": ["Perfil", "Traducción", "Se ofrece cuando"], + "headers": [ + "Perfil", + "Configuración resultante", + "Se ofrece cuando" + ], "rows": [ - ["Intel/AMD VA-API", "Render node de /dev/dri", "La aplicación admite aceleración de vídeo"], - ["OpenCL", "Render node, /dev/kfd si hace falta y el mod oficial", "La imagen o el perfil lo documentan"], - ["NVIDIA", "Dispositivos y librerías del driver del host", "El host tiene un driver operativo y NVIDIA Container Toolkit"], - ["Coral", "/dev/apex_0 o el bus USB", "El perfil declara soporte de Coral (Frigate)"], - ["USB, serie, FUSE", "Un dispositivo concreto, un árbol validado o una función LXC", "El contrato lo pide"] + [ + "Intel/AMD VA-API", + "Render node de /dev/dri", + "La aplicación admite aceleración de vídeo" + ], + [ + "OpenCL", + "Render node, /dev/kfd si hace falta y el mod oficial", + "La imagen o el perfil lo documentan" + ], + [ + "NVIDIA", + "Dispositivos y librerías del driver del host", + "El host tiene un driver operativo y NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 o el bus USB", + "El perfil declara soporte de Coral (Frigate)" + ], + [ + "USB, serie, FUSE", + "Un dispositivo concreto, un árbol validado o una función LXC", + "El contrato lo pide" + ] ] } } @@ -145,7 +264,9 @@ "p": "Estos dos comandos, en el host, muestran si el driver y el Toolkit están disponibles:" }, { - "shell": { "code": "nvidia-smi -L\nnvidia-container-cli --version" } + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } }, { "p": "En un host cuyo driver se instaló por otros medios, el Toolkit se instala desde el repositorio estable oficial:" @@ -159,7 +280,9 @@ "p": "El inventario que usa OCI manager Apps es la salida de:" }, { - "shell": { "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" } + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } }, { "calloutInfo": { @@ -170,10 +293,26 @@ { "cards": { "items": [ - { "icon": "cpu", "title": "Inventario desde el driver", "body": "nvidia-container-cli enumera los nodos de dispositivo, los binarios, el firmware y las librerías del driver instalado." }, - { "icon": "refresh", "title": "Sin versión fija", "body": "La plantilla no nombra archivos de librería. El perfil se genera a partir del host actual." }, - { "icon": "shield", "title": "Montajes de solo lectura", "body": "Las librerías del host se montan en solo lectura en lugar de copiarse dentro del contenedor." }, - { "icon": "hardDrive", "title": "Cambios de driver", "body": "Tras un cambio de driver, el inventario se genera de nuevo antes de que arranquen los LXC afectados." } + { + "icon": "cpu", + "title": "Inventario desde el driver", + "body": "nvidia-container-cli enumera los nodos de dispositivo, los binarios, el firmware y las librerías del driver instalado." + }, + { + "icon": "refresh", + "title": "Sin versión fija", + "body": "La plantilla no nombra archivos de librería. El perfil se genera a partir del host actual." + }, + { + "icon": "shield", + "title": "Montajes de solo lectura", + "body": "Las librerías del host se montan en solo lectura en lugar de copiarse dentro del contenedor." + }, + { + "icon": "hardDrive", + "title": "Cambios de driver", + "body": "Tras un cambio de driver, el inventario se genera de nuevo antes de que arranquen los LXC afectados." + } ] } }, @@ -209,12 +348,32 @@ "blocks": [ { "table": { - "headers": ["Petición", "Traducción", "Alcance"], + "headers": [ + "Petición", + "Configuración resultante", + "Alcance" + ], "rows": [ - ["/dev/dvb, /dev/snd o /dev/bus/usb", "Cada nodo de caracteres del árbol recibe su propia entrada devN con el modo y el GID del host", "Solo el árbol pedido, no el resto de /dev"], - ["/dev/fuse", "El nodo y, cuando el perfil lo necesita, la función fuse=1", "FUSE por sí solo no publica montajes a otros LXC"], - ["/dev/net/tun", "Una entrada devN en la misma ruta dentro del LXC", "La configuración de la VPN o de la red queda en la aplicación"], - ["/dev/kvm", "Una entrada devN validada", "Solo se ofrece cuando el contrato lo pide"] + [ + "/dev/dvb, /dev/snd o /dev/bus/usb", + "Cada nodo de caracteres del árbol recibe su propia entrada devN con el modo y el GID del host", + "Solo el árbol pedido, no el resto de /dev" + ], + [ + "/dev/fuse", + "El nodo y, cuando el perfil lo necesita, la función fuse=1", + "FUSE por sí solo no publica montajes a otros LXC" + ], + [ + "/dev/net/tun", + "Una entrada devN en la misma ruta dentro del LXC", + "La configuración de la VPN o de la red queda en la aplicación" + ], + [ + "/dev/kvm", + "Una entrada devN validada", + "Solo se ofrece cuando el contrato lo pide" + ] ] } } diff --git a/web/messages/es/docs/oci-manager/index.json b/web/messages/es/docs/oci-manager/index.json index 5f548dbf..8eef2329 100644 --- a/web/messages/es/docs/oci-manager/index.json +++ b/web/messages/es/docs/oci-manager/index.json @@ -1,11 +1,11 @@ { "meta": { "title": "OCI manager Apps | ProxMenux", - "description": "Imágenes OCI oficiales ejecutadas como contenedores LXC nativos de Proxmox VE, con datos persistentes, dispositivos, aplicaciones multicontenedor y actualizaciones transaccionales." + "description": "Ejecuta imágenes OCI oficiales como contenedores LXC nativos de Proxmox VE, con datos persistentes, acceso a dispositivos, aplicaciones con varios contenedores y actualizaciones transaccionales." }, "header": { "title": "OCI manager Apps", - "description": "Las imágenes OCI oficiales se ejecutan como contenedores LXC nativos de Proxmox VE. La imagen sigue siendo la que publica su responsable; ProxMenux reproduce el entorno que Docker Compose habría creado a su alrededor.", + "description": "Las imágenes OCI oficiales se ejecutan como contenedores LXC nativos de Proxmox VE. Se utiliza la misma imagen que publica su desarrollador; ProxMenux reproduce el entorno que Docker Compose habría creado a su alrededor.", "section": "OCI manager Apps" }, "sections": [ @@ -15,34 +15,43 @@ { "calloutInfo": { "title": "Una imagen OCI, un LXC nativo", - "body": "Dentro de los contenedores no se ejecuta ningún motor Docker. Proxmox VE importa el sistema de archivos de la imagen y sus metadatos OCI, y la aplicación pasa a ser el proceso principal de un LXC nativo, gestionado con las mismas herramientas que cualquier otro contenedor del nodo." + "body": "No se ejecuta Docker Engine dentro de los contenedores. Proxmox VE importa el sistema de archivos de la imagen y sus metadatos OCI; la aplicación pasa a ser el proceso principal de un LXC nativo, administrado con las mismas herramientas que cualquier otro contenedor del nodo." } }, { "flow": { "nodes": [ - { "label": "Origen", "detail": "Imagen OCI\nCompose y documentación" }, - { "label": "ProxMenux", "detail": "Contrato JSON\nvalidación y plan" }, - { "label": "Proxmox VE", "detail": "LXC nativo\nvolúmenes y dispositivos" } + { + "label": "Origen", + "detail": "Imagen OCI\nCompose y documentación" + }, + { + "label": "ProxMenux", + "detail": "Contrato JSON\nvalidación y planificación" + }, + { + "label": "Proxmox VE", + "detail": "LXC nativo\nvolúmenes y dispositivos" + } ], - "caption": "La aplicación no se reconstruye: su entorno se reproduce de forma declarativa." + "caption": "Se utiliza la aplicación original; su entorno se define de forma declarativa y se reproduce." } } ] }, { "id": "where", - "title": "Dónde se encuentra", - "intro": "OCI manager Apps se abre desde la opción OCI manager Apps (Technology Preview) del menú principal de ProxMenux, en el nodo Proxmox y como root. Su primera pantalla ofrece:", + "title": "Dónde encontrarlo", + "intro": "OCI manager Apps se abre desde la opción OCI manager Apps (Technology Preview) del menú principal de ProxMenux, en el nodo Proxmox y como root. La primera pantalla ofrece:", "blocks": [ { "list": { "items": [ - "Buscar aplicaciones: búsqueda por nombre en todo el catálogo.", - "Todas las aplicaciones: el catálogo completo, con el número de aplicaciones.", - "Gestionar aplicaciones OCI instaladas: actualizar, modificar, eliminar o recuperar lo instalado.", - "Cuentas de registro: las cuentas con las que se instalan y actualizan imágenes de registros privados.", - "Instalar una imagen que no está en el catálogo: traducción de un archivo Compose, un comando docker run o una referencia de imagen.", + "Buscar aplicaciones: busca por nombre en todo el catálogo.", + "Todas las aplicaciones: muestra el catálogo completo y su número de aplicaciones.", + "Gestionar aplicaciones OCI instaladas: actualiza, modifica, elimina o recupera las aplicaciones que ya has instalado.", + "Cuentas de registros: administra las cuentas utilizadas para instalar y actualizar imágenes de registros privados.", + "Instalar una imagen que no esté en el catálogo: adapta un archivo Compose, un comando docker run o una referencia de imagen.", "Las categorías del catálogo, cada una con su número de aplicaciones." ] } @@ -50,40 +59,72 @@ { "figure": { "src": "/oci-manager/main-menu.png", - "alt": "Pantalla principal de OCI manager Apps con la búsqueda, todas las aplicaciones, la gestión, la imagen propia y las categorías del catálogo", + "alt": "Pantalla principal de OCI manager Apps con búsqueda, listado completo, gestión de aplicaciones, imágenes fuera del catálogo y categorías.", "caption": "Pantalla principal de OCI manager Apps." } }, { - "p": "Al seleccionar una aplicación se muestra su descripción, la imagen que ejecuta y dos modos de instalación: Instalar con la configuración predeterminada, que pregunta solo lo que decide toda instalación (almacenamiento, dónde van los datos, dirección e inicio), e Instalar con la configuración avanzada, que abre cada decisión en orden: VMID y recursos, el almacenamiento de cada ruta, el bridge y los dispositivos." + "p": "Al seleccionar una aplicación se muestran su descripción, la imagen que utiliza y dos modos de instalación. Instalar con ajustes predeterminados solo pregunta lo esencial de cada instalación: almacenamiento, ubicación de los datos, dirección y arranque. Instalar con ajustes avanzados recorre todas las decisiones en orden: VMID y recursos, almacenamiento de cada ruta, puente de red y dispositivos." }, { - "p": "Una aplicación probada en real muestra Verificado en esa vista y aparece marcada con ✓ en las listas, tanto si se probó en el laboratorio de ProxMenux como si la probó la comunidad." + "p": "Las aplicaciones que se han probado realmente muestran Verificado en esa vista y una marca ✓ en las listas, tanto si la prueba se hizo en el laboratorio de ProxMenux como si la realizó la comunidad." } ] }, { "id": "beta", - "title": "Por qué es una Technology Preview", + "title": "Por qué lleva la etiqueta Technology Preview", "blocks": [ - { "p": "OCI manager Apps lleva la etiqueta Technology Preview que Proxmox VE da a los contenedores de aplicación creados a partir de imágenes OCI, introducidos en Proxmox VE 9.1. Se refiere a aquello sobre lo que funciona OCI manager Apps, no al motor de ProxMenux: el comportamiento de esos contenedores todavía puede cambiar entre versiones de Proxmox VE. La etiqueta se mantiene hasta que Proxmox VE dé ese soporte por estable." }, - { "p": "El motor de OCI manager Apps (instalación, actualización, modificación, recreación, backups, restauración, migración y vigilancia) está probado en el laboratorio de ProxMenux. Cada aplicación se prueba por separado: las que se han probado de verdad muestran Verified y llevan una ✓ en las listas." } + { + "p": "OCI manager Apps mantiene la etiqueta Technology Preview que Proxmox VE asigna a los contenedores de aplicaciones creados a partir de imágenes OCI, cuyo soporte se introdujo en Proxmox VE 9.1. La etiqueta se refiere a la tecnología sobre la que funciona OCI manager Apps, no al motor de ProxMenux: el comportamiento de estos contenedores puede cambiar entre versiones de Proxmox VE. Se mantendrá hasta que Proxmox VE considere estable ese soporte." + }, + { + "p": "El motor de OCI manager Apps se prueba en el laboratorio de ProxMenux: instalación, actualización, modificación, recreación, copias de seguridad, restauración, migración y watchdog. Cada aplicación se verifica por separado. Las que se han probado realmente muestran Verificado y llevan un ✓ en las listas." + } ] }, { "id": "translation", - "title": "Lo que Docker expresa y en qué lo convierte OCI manager Apps", + "title": "Cómo describe Docker los requisitos y cómo los aplica OCI manager Apps", "blocks": [ { "table": { - "headers": ["Necesidad", "Docker la expresa como", "OCI manager Apps la convierte en"], + "headers": [ + "Requisito", + "Cómo lo expresa Docker", + "Cómo lo aplica OCI manager Apps" + ], "rows": [ - ["Ejecutar la aplicación", "image, entrypoint, command", "rootfs OCI y proceso principal del LXC"], - ["Conservar la configuración", "volumes: /config", "un disco mpN persistente incluido en el backup, o un directorio del host"], - ["Publicar el servicio", "ports", "una dirección propia del LXC y la URL de acceso al servicio"], - ["Usar hardware", "devices, group_add", "entradas devN con el GID efectivo del host y un perfil validado"], - ["Conectar dependencias", "networks, depends_on", "un bridge privado, direcciones fijas, orden de arranque y healthchecks"], - ["Actualizar", "pull y recrear", "un rootfs nuevo con el mismo contrato persistente"] + [ + "Ejecutar la aplicación", + "image, entrypoint, command", + "El rootfs de la imagen OCI y el proceso principal del LXC" + ], + [ + "Conservar la configuración", + "volumes: /config", + "Un volumen persistente mpN incluido en la copia de seguridad, o bien un directorio del host." + ], + [ + "Publicar el servicio", + "ports", + "Una dirección propia para el LXC y la URL de acceso al servicio." + ], + [ + "Usar hardware", + "devices, group_add", + "Entradas devN con el GID efectivo del host y un perfil validado." + ], + [ + "Conectar las dependencias", + "networks, depends_on", + "Un puente de red privado, direcciones fijas, orden de arranque y comprobaciones de estado." + ], + [ + "Actualizar", + "Descargar con pull y recrear", + "Un rootfs nuevo que mantiene el mismo contrato de persistencia." + ] ] } } @@ -91,15 +132,31 @@ }, { "id": "kept", - "title": "Qué se mantiene y qué cambia", + "title": "Qué se conserva y qué cambia", "blocks": [ { "cards": { "items": [ - { "icon": "archive", "title": "Se mantiene", "body": "La imagen oficial, sus rutas internas, su entorno, su proceso de arranque y su documentación funcional." }, - { "icon": "boxes", "title": "Se adapta", "body": "El entorno de ejecución: red, persistencia, dispositivos, permisos y dependencias usan primitivas LXC nativas de Proxmox VE." }, - { "icon": "shield", "title": "No se traduce", "body": "Una clave de Compose sin equivalencia segura figura como bloqueo. Las plantillas con bloqueos no se ofrecen, y las opciones sensibles se confirman durante la instalación." }, - { "icon": "refresh", "title": "Se registra", "body": "Digest de la imagen, recursos, rutas, red, dispositivos y pertenencia a una pila quedan en el contrato de la instancia, que las actualizaciones y las recreaciones reproducen." } + { + "icon": "archive", + "title": "Se conserva", + "body": "La imagen oficial, sus rutas internas, su entorno, su proceso de arranque y su documentación de uso." + }, + { + "icon": "boxes", + "title": "Se adapta", + "body": "El entorno de ejecución: la red, la persistencia, los dispositivos, los permisos y las dependencias utilizan los mecanismos nativos de LXC de Proxmox VE." + }, + { + "icon": "shield", + "title": "Sin equivalente", + "body": "Una opción de Compose sin una equivalencia segura se registra como un impedimento para la instalación. Las plantillas con impedimentos no se ofrecen para instalar, y las opciones sensibles requieren confirmación durante el asistente." + }, + { + "icon": "refresh", + "title": "Se registra", + "body": "El digest de la imagen, los recursos, las rutas, la red, los dispositivos y la pertenencia a una pila se guardan en el contrato de la instancia. Las actualizaciones y recreaciones reproducen ese contrato." + } ] } } @@ -112,13 +169,41 @@ { "next": { "items": [ - { "label": "Cómo se traduce una imagen OCI", "href": "/docs/oci-manager/architecture", "tail": "de la imagen y su archivo Compose a un LXC nativo." }, - { "label": "Instalar una imagen que no está en el catálogo", "href": "/docs/oci-manager/custom-image", "tail": "Compose, docker run o una referencia de imagen." }, - { "label": "Datos, rutas y red", "href": "/docs/oci-manager/storage-network", "tail": "discos del contenedor, directorios del host, montajes Rclone y direcciones." }, - { "label": "Dispositivos y aceleración", "href": "/docs/oci-manager/hardware", "tail": "GPU, NVIDIA, Coral, USB y otros dispositivos." }, - { "label": "Aplicaciones multicontenedor", "href": "/docs/oci-manager/stacks", "tail": "aplicación, base de datos y caché como LXC coordinados." }, - { "label": "Instalar, actualizar y modificar", "href": "/docs/oci-manager/lifecycle", "tail": "el contrato de la instancia y sus operaciones." }, - { "label": "En ProxMenux Monitor", "href": "/docs/oci-manager/monitor", "tail": "versiones, actualizaciones, salida de consola y terminal." } + { + "label": "Cómo se adapta una imagen OCI", + "href": "/docs/oci-manager/architecture", + "tail": "De la imagen y su archivo Compose a un LXC nativo." + }, + { + "label": "Instalar una imagen que no esté en el catálogo", + "href": "/docs/oci-manager/custom-image", + "tail": "Un archivo Compose, un comando docker run o una referencia de imagen." + }, + { + "label": "Datos, rutas y red", + "href": "/docs/oci-manager/storage-network", + "tail": "Volúmenes del contenedor, directorios del host, montajes con rclone y direcciones." + }, + { + "label": "Dispositivos y aceleración", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB y otros dispositivos." + }, + { + "label": "Aplicaciones con varios contenedores", + "href": "/docs/oci-manager/stacks", + "tail": "Aplicación, base de datos y caché en varios LXC coordinados." + }, + { + "label": "Instalar, actualizar y modificar", + "href": "/docs/oci-manager/lifecycle", + "tail": "El contrato de la instancia y sus operaciones." + }, + { + "label": "En ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versiones, actualizaciones, salida de consola y terminal." + } ] } } diff --git a/web/messages/es/docs/oci-manager/lifecycle.json b/web/messages/es/docs/oci-manager/lifecycle.json index a722935a..0649c11e 100644 --- a/web/messages/es/docs/oci-manager/lifecycle.json +++ b/web/messages/es/docs/oci-manager/lifecycle.json @@ -18,7 +18,7 @@ }, { "code": { - "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── imagen y digest resuelto\n ├── recursos y red\n ├── entorno (secretos protegidos)\n ├── discos del contenedor y directorios del host\n ├── perfil de hardware y dispositivos\n ├── log de consola y modo de terminal\n └── pertenencia a una pila y ciclo de vida" + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" } }, { @@ -86,7 +86,7 @@ "registry": "Registro OCI", "update": "Actualizar la instancia", "resolve": "Resolver la etiqueta", - "verify": "Verificar el archivo y preflight", + "verify": "Verificar el archivo y realizar las comprobaciones previas", "backup": "Detener y respaldar el CT", "import": "Importar el rootfs nuevo", "reapply": "Aplicar de nuevo el contrato", @@ -215,7 +215,7 @@ { "id": "restore", "title": "Backup, restauración y otro host", - "intro": "Un backup hecho con vzdump o Proxmox Backup Server incluye el contenedor, sus discos y la copia de su contrato. Lo que la instalación guarda en el host no forma parte de él. Gestionar aplicaciones OCI instaladas detecta los contenedores restaurados en un host que no tiene su contrato, un Proxmox recién instalado u otro host, y ofrece registrarlos de nuevo.", + "intro": "Un backup hecho con vzdump o Proxmox Backup Server incluye el contenedor, los discos del contenedor seleccionados para el backup y la copia de su contrato. Lo que la instalación guarda en el host no forma parte de él. Gestionar aplicaciones OCI instaladas detecta los contenedores restaurados en un host que no tiene su contrato, un Proxmox recién instalado u otro host, y ofrece registrarlos de nuevo.", "blocks": [ { "steps": { @@ -462,7 +462,7 @@ ], [ "Discos del contenedor", - "Proxmox los mueve con el contenedor. HA necesita que estén en un almacenamiento compartido." + "Proxmox los mueve con el contenedor. HA necesita sus datos disponibles en el nodo de destino, mediante almacenamiento compartido o una configuración de replicación compatible." ], [ "Directorios del host y dispositivos", diff --git a/web/messages/es/docs/oci-manager/monitor.json b/web/messages/es/docs/oci-manager/monitor.json index 80b1361b..aae9c49f 100644 --- a/web/messages/es/docs/oci-manager/monitor.json +++ b/web/messages/es/docs/oci-manager/monitor.json @@ -30,7 +30,7 @@ "Un interruptor Vigilancia bajo Iniciar al arrancar: la aplicación se inicia de nuevo cuando se detiene sola" ], [ - "App", + "Aplicación", "La aplicación se identifica desde el registro y sus actualizaciones se siguen por la imagen" ], [ @@ -42,7 +42,7 @@ "Discos del contenedor y directorios del host, con su uso" ], [ - "Logs", + "Registros", "Solo en contenedores OCI: la salida de consola de la aplicación" ] ] @@ -52,7 +52,7 @@ }, { "id": "app", - "title": "App", + "title": "Aplicación", "intro": "La pestaña App ofrece la aplicación instalada como una detección, con el nombre, el logo, el puerto y el esquema del registro. Al registrarla se abre el editor con el método Imagen OCI (instalada por ProxMenux), que no necesita configuración.", "blocks": [ { @@ -150,7 +150,7 @@ }, { "id": "logs", - "title": "Logs", + "title": "Registros", "intro": "La pestaña Logs solo aparece en los contenedores instalados por OCI manager Apps, entre Montajes y Backups. Muestra la salida estándar y de errores del proceso principal de la imagen, la misma que muestra docker logs en un contenedor Docker.", "blocks": [ { diff --git a/web/messages/es/docs/oci-manager/stacks.json b/web/messages/es/docs/oci-manager/stacks.json index 854f3f5b..2f690790 100644 --- a/web/messages/es/docs/oci-manager/stacks.json +++ b/web/messages/es/docs/oci-manager/stacks.json @@ -4,7 +4,7 @@ "description": "Cómo convierte OCI manager Apps una aplicación con su base de datos y su caché en LXC nativos coordinados: plan, red privada, hook de dependencias, validación y actualizaciones transaccionales." }, "header": { - "title": "Aplicaciones multicontenedor", + "title": "Aplicaciones con varios contenedores", "description": "Una aplicación con su base de datos, su caché y otros servicios se convierte en varios LXC nativos coordinados, que se instalan y actualizan como uno solo.", "section": "OCI manager Apps" }, @@ -21,7 +21,14 @@ { "mermaid": { "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", - "labels": { "plan": "Plan de la pila", "app": "LXC de la aplicación", "appNet": "LAN + privada", "db": "LXC PostgreSQL", "cache": "LXC Valkey", "private": "privada" } + "labels": { + "plan": "Plan de la pila", + "app": "LXC de la aplicación", + "appNet": "LAN + privada", + "db": "LXC PostgreSQL", + "cache": "LXC Valkey", + "private": "privada" + } } } ] @@ -35,14 +42,42 @@ }, { "table": { - "headers": ["Elemento del plan", "Contenido", "Comprobado antes de crear nada"], + "headers": [ + "Elemento del plan", + "Contenido", + "Comprobado antes de crear nada" + ], "rows": [ - ["Miembros", "Aplicación principal, base de datos, caché, aprendizaje automático y demás dependencias", "VMID únicos, roles conocidos y exactamente un miembro principal"], - ["Imágenes", "Referencia rolling, arquitectura y digest resuelto de cada servicio", "Todas existen, admiten la arquitectura y superan la verificación de integridad OCI"], - ["Red", "Bridge privado, subred, una dirección fija por servicio y acceso LAN para el miembro principal", "Sin colisión con bridges o subredes existentes y sin direcciones repetidas"], - ["Persistencia", "Discos del contenedor, directorios del host, propietarios y backup", "Rutas sin solapamientos, almacenamiento disponible y permisos declarados"], - ["Secretos", "Contraseña de la base de datos, claves de la aplicación y credenciales iniciales", "Se generan una vez y solo se entregan a los miembros que los usan"], - ["Ciclo de vida", "Orden de arranque, orden de parada y una comprobación de salud por miembro", "La aplicación principal arranca la última y se detiene la primera"] + [ + "Miembros", + "Aplicación principal, base de datos, caché, aprendizaje automático y demás dependencias", + "VMID únicos, roles conocidos y exactamente un miembro principal" + ], + [ + "Imágenes", + "Referencia rolling, arquitectura y digest resuelto de cada servicio", + "Todas existen, admiten la arquitectura y superan la verificación de integridad OCI" + ], + [ + "Red", + "Bridge privado, subred, una dirección fija por servicio y acceso LAN para el miembro principal", + "Sin colisión con bridges o subredes existentes y sin direcciones repetidas" + ], + [ + "Persistencia", + "Discos del contenedor, directorios del host, propietarios y backup", + "Rutas sin solapamientos, almacenamiento disponible y permisos declarados" + ], + [ + "Secretos", + "Contraseña de la base de datos, claves de la aplicación y credenciales iniciales", + "Se generan una vez y solo se entregan a los miembros que los usan" + ], + [ + "Ciclo de vida", + "Orden de arranque, orden de parada y una comprobación de salud por miembro", + "La aplicación principal arranca la última y se detiene la primera" + ] ] } } @@ -55,13 +90,34 @@ { "steps": { "items": [ - { "title": "Reservar todos los VMID", "body": "Se comprueban el inventario de Proxmox VE y el registro de instancias. No se adopta un CT existente ni se reutiliza un contrato que todavía pertenece a otra instancia." }, - { "title": "Preparar todas las imágenes", "body": "Todas las imágenes se resuelven, descargan y verifican antes de crear el primer contenedor." }, - { "title": "Crear cada rootfs", "body": "Se importan los metadatos OCI oficiales y se añaden la identidad de instancia de ProxMenux y el rol del miembro." }, - { "title": "Red privada", "body": "Se crean un bridge y una subred y cada miembro recibe su dirección fija; solo el miembro principal recibe además la interfaz de la LAN." }, - { "title": "Persistencia", "body": "Cada base de datos y configuración recibe su propio disco del contenedor; solo los datos destinados a compartirse usan directorios del host." }, - { "title": "Entorno", "body": "Se escriben los endpoints internos, los secretos compartidos y las variables de servicio. La aplicación llega a sus dependencias en sus direcciones privadas reservadas." }, - { "title": "Registro", "body": "Cada miembro registra su configuración nativa, los cambios del rootfs que las actualizaciones deben reproducir y su relación con la pila." } + { + "title": "Reservar todos los VMID", + "body": "Se comprueban el inventario de Proxmox VE y el registro de instancias. No se adopta un CT existente ni se reutiliza un contrato que todavía pertenece a otra instancia." + }, + { + "title": "Preparar todas las imágenes", + "body": "Todas las imágenes se resuelven, descargan y verifican antes de crear el primer contenedor." + }, + { + "title": "Crear cada rootfs", + "body": "Se importan los metadatos OCI oficiales y se añaden la identidad de instancia de ProxMenux y el rol del miembro." + }, + { + "title": "Red privada", + "body": "Se crean un bridge y una subred y cada miembro recibe su dirección fija; solo el miembro principal recibe además la interfaz de la LAN." + }, + { + "title": "Persistencia", + "body": "Cada base de datos y configuración recibe su propio disco del contenedor; solo los datos destinados a compartirse usan directorios del host." + }, + { + "title": "Entorno", + "body": "Se escriben los endpoints internos, los secretos compartidos y las variables de servicio. La aplicación llega a sus dependencias en sus direcciones privadas reservadas." + }, + { + "title": "Registro", + "body": "Cada miembro registra su configuración nativa, los cambios del rootfs que las actualizaciones deben reproducir y su relación con la pila." + } ] } } @@ -74,20 +130,48 @@ "blocks": [ { "table": { - "headers": ["Servicio", "Comprobación", "Qué demuestra"], + "headers": [ + "Servicio", + "Comprobación", + "Qué demuestra" + ], "rows": [ - ["PostgreSQL", "pg_isready en el CT con el host, el usuario y la base de datos esperados", "El servidor acepta conexiones para la base de datos configurada"], - ["Redis / Valkey", "redis-cli o valkey-cli PING contra su dirección privada", "El broker escucha y responde"], - ["Aprendizaje automático de Immich", "HTTP GET /ping, más una comprobación del runtime GPU seleccionado", "El servicio responde y la aceleración pedida no ha pasado a CPU"], - ["Nextcloud", "GET /status.php con installed=true, maintenance=false y needsDbUpgrade=false", "La inicialización terminó sin migraciones pendientes"], - ["Paperless-ngx / Tandoor", "HTTP en la dirección de la LAN y el puerto real del servicio", "El frontend y sus dependencias sirven la aplicación"], - ["Aplicación principal", "El endpoint de su plantilla, por ejemplo /api/server/ping en Immich", "La pila completa funciona a través de la aplicación que usa las dependencias"] + [ + "PostgreSQL", + "pg_isready en el CT con el host, el usuario y la base de datos esperados", + "El servidor acepta conexiones para la base de datos configurada" + ], + [ + "Redis / Valkey", + "redis-cli o valkey-cli PING contra su dirección privada", + "El broker escucha y responde" + ], + [ + "Aprendizaje automático de Immich", + "HTTP GET /ping, más una comprobación del runtime GPU seleccionado", + "El servicio responde y la aceleración pedida no ha pasado a CPU" + ], + [ + "Nextcloud", + "GET /status.php con installed=true, maintenance=false y needsDbUpgrade=false", + "La inicialización terminó sin migraciones pendientes" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP en la dirección de la LAN y el puerto real del servicio", + "El frontend y sus dependencias sirven la aplicación" + ], + [ + "Aplicación principal", + "El endpoint de su plantilla, por ejemplo /api/server/ping en Immich", + "La pila completa funciona a través de la aplicación que usa las dependencias" + ] ] } }, { "calloutWarning": { - "title": "running no significa healthy", + "title": "En ejecución no significa que funcione correctamente", "body": "El estado running solo indica que existe el proceso del LXC. Cuando el servicio ofrece una comprobación mejor, se usa una comprobación exec o HTTP con un tiempo límite. Si un miembro se detiene o no supera su comprobación, la pila no se declara instalada." } } @@ -103,8 +187,14 @@ { "codeGrid": { "items": [ - { "title": "Configuración del CT principal", "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" }, - { "title": "Contrato privado (ejemplo)", "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" } + { + "title": "Configuración del CT principal", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Contrato privado (ejemplo)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } ] } }, @@ -121,12 +211,30 @@ { "steps": { "items": [ - { "title": "Proxmox VE llama a pre-start", "body": "Antes de arrancar el CT principal, el hook se ejecuta con su VMID y la fase del ciclo de vida." }, - { "title": "Un bloqueo por pila", "body": "flock sobre /run/lock/proxmenux-stack-VMID.lock impide dos secuencias de arranque a la vez." }, - { "title": "Se lee y valida el contrato", "body": "Se exigen un esquema conocido, dependencias numéricas, etiquetas, una comprobación exec, http o running y un tiempo límite positivo." }, - { "title": "Dependencias en orden", "body": "Cada CT debe existir; uno detenido se arranca y uno que ya está en marcha no se reinicia." }, - { "title": "Espera de salud", "body": "La comprobación se ejecuta cada dos segundos y se confirma además que el CT sigue en marcha." }, - { "title": "Arranca el CT principal", "body": "Cuando todas las dependencias están listas, pre-start termina y Proxmox VE arranca la aplicación." } + { + "title": "Proxmox VE llama a pre-start", + "body": "Antes de arrancar el CT principal, el hook se ejecuta con su VMID y la fase del ciclo de vida." + }, + { + "title": "Un bloqueo por pila", + "body": "flock sobre /run/lock/proxmenux-stack-VMID.lock impide dos secuencias de arranque a la vez." + }, + { + "title": "Se lee y valida el contrato", + "body": "Se exigen un esquema conocido, dependencias numéricas, etiquetas, una comprobación exec, http o running y un tiempo límite positivo." + }, + { + "title": "Dependencias en orden", + "body": "Cada CT debe existir; uno detenido se arranca y uno que ya está en marcha no se reinicia." + }, + { + "title": "Esperar a que el servicio esté listo", + "body": "La comprobación se ejecuta cada dos segundos y se confirma además que el CT sigue en marcha." + }, + { + "title": "Arranca el CT principal", + "body": "Cuando todas las dependencias están listas, pre-start termina y Proxmox VE arranca la aplicación." + } ] } }, @@ -138,11 +246,27 @@ }, { "table": { - "headers": ["Tipo", "Ejemplo", "Arranques posteriores"], + "headers": [ + "Tipo", + "Ejemplo", + "Arranques posteriores" + ], "rows": [ - ["Pila dependiente", "Immich, Nextcloud, aplicación con PostgreSQL", "El hook del CT principal arranca las dependencias y las espera"], - ["Suite de aplicaciones", "Suite Arr", "Sin miembro principal: cada LXC sigue su propio onboot"], - ["Aplicación simple", "Jellyfin", "Proxmox VE arranca directamente ese LXC"] + [ + "Pila dependiente", + "Immich, Nextcloud, aplicación con PostgreSQL", + "El hook del CT principal arranca las dependencias y las espera" + ], + [ + "Suite de aplicaciones", + "Suite Arr", + "Sin miembro principal: cada LXC sigue su propio onboot" + ], + [ + "Aplicación simple", + "Jellyfin", + "Proxmox VE arranca directamente ese LXC" + ] ] } } @@ -176,21 +300,51 @@ { "steps": { "items": [ - { "title": "Cualquier miembro", "body": "El contrato del miembro indica el VMID principal y la lista completa de miembros." }, - { "title": "La pila es reproducible", "body": "Se validan identidades, contratos, hook, adaptaciones y la existencia de una reproducción coordinada para esa receta." }, - { "title": "Primero las imágenes", "body": "La aplicación no se detiene hasta que todos los digests están resueltos, descargados y verificados." }, - { "title": "Detener y respaldar el conjunto", "body": "Los backups nativos verificados se toman con la pila detenida, de modo que aplicación y bases de datos corresponden al mismo momento." }, - { "title": "Actualizar y comprobar cada miembro", "body": "Adaptación, red, montajes, secretos y dispositivos se aplican de nuevo antes de la comprobación de salud de cada miembro." }, - { "title": "Publicar o recuperar todo", "body": "Los contratos nuevos solo se publican cuando todos los miembros pasan. Si uno falla, se restauran todos." } + { + "title": "Cualquier miembro", + "body": "El contrato del miembro indica el VMID principal y la lista completa de miembros." + }, + { + "title": "La pila es reproducible", + "body": "Se validan identidades, contratos, hook, adaptaciones y la existencia de una reproducción coordinada para esa receta." + }, + { + "title": "Primero las imágenes", + "body": "La aplicación no se detiene hasta que todos los digests están resueltos, descargados y verificados." + }, + { + "title": "Detener y respaldar el conjunto", + "body": "Los backups nativos verificados se toman con la pila detenida, de modo que aplicación y bases de datos corresponden al mismo momento." + }, + { + "title": "Actualizar y comprobar cada miembro", + "body": "Adaptación, red, montajes, secretos y dispositivos se aplican de nuevo antes de la comprobación de salud de cada miembro." + }, + { + "title": "Publicar o recuperar todo", + "body": "Los contratos nuevos solo se publican cuando todos los miembros pasan. Si uno falla, se restauran todos." + } ] } }, { "table": { - "headers": ["Modificar", "Qué cambia", "Qué se conserva"], + "headers": [ + "Modificar", + "Qué cambia", + "Qué se conserva" + ], "rows": [ - ["Rutas y dispositivos extra", "Se añaden al contenedor de la aplicación o se eliminan de él, y el contenedor se reinicia. No se reconstruye nada", "Los datos de la aplicación, su base de datos y los demás contenedores"], - ["Qué ejecuta el reconocimiento (Immich)", "El contenedor Machine learning toma la imagen y los dispositivos de la CPU o de una GPU del host. Toda la aplicación se detiene y se actualiza, como en una actualización", "La caché de modelos, la biblioteca y la base de datos. Si algo falla, se restauran los contenedores y la opción anteriores"] + [ + "Rutas y dispositivos extra", + "Se añaden al contenedor de la aplicación o se eliminan de él, y el contenedor se reinicia. No se reconstruye nada", + "Los datos de la aplicación, su base de datos y los demás contenedores" + ], + [ + "Qué ejecuta el reconocimiento (Immich)", + "El contenedor Machine learning toma la imagen y los dispositivos de la CPU o de una GPU del host. Toda la aplicación se detiene y se actualiza, como en una actualización", + "La caché de modelos, la biblioteca y la base de datos. Si algo falla, se restauran los contenedores y la opción anteriores" + ] ] } }, @@ -220,7 +374,7 @@ "stop": "Detener, primero el principal", "backup": "Backup verificado de cada CT", "replace": "Sustituir el rootfs", - "healthy": "¿Todos sanos?", + "healthy": "¿Todos funcionan correctamente?", "yes": "Sí", "no": "No", "commit": "Publicar los contratos", diff --git a/web/messages/es/docs/oci-manager/storage-network.json b/web/messages/es/docs/oci-manager/storage-network.json index 08d67c4e..554e3f91 100644 --- a/web/messages/es/docs/oci-manager/storage-network.json +++ b/web/messages/es/docs/oci-manager/storage-network.json @@ -1,11 +1,11 @@ { "meta": { "title": "Datos, rutas y red | ProxMenux", - "description": "Cómo conserva OCI manager Apps los datos de un contenedor OCI: discos del contenedor, directorios del host, montajes Rclone, direcciones y redes privadas." + "description": "Cómo conserva OCI manager Apps los datos de un contenedor OCI: discos del contenedor, directorios del host, montajes con rclone, direcciones y redes privadas." }, "header": { "title": "Datos, rutas y red", - "description": "Qué vive en el rootfs, qué sobrevive a su sustitución, cómo se comparten datos entre contenedores y cómo obtiene cada contenedor su dirección.", + "description": "Qué se guarda en el rootfs, qué sobrevive a su sustitución, cómo se comparten datos entre contenedores y cómo obtiene cada uno su dirección.", "section": "OCI manager Apps" }, "sections": [ @@ -14,8 +14,8 @@ "blocks": [ { "calloutInfo": { - "title": "La persistencia se decide antes de que exista el LXC", - "body": "Los volúmenes que publican la imagen y su archivo Compose se leen antes de crear el contenedor. Cada ruta que debe sobrevivir a una actualización se convierte en un punto de montaje independiente del rootfs, de modo que el rootfs solo contiene lo que pertenece a la imagen y puede sustituirse." + "title": "La persistencia se decide antes de crear el LXC", + "body": "Antes de crear el contenedor se leen los volúmenes declarados por la imagen y por su archivo Compose. Cada ruta que deba conservarse tras una actualización se convierte en un punto de montaje independiente del rootfs. Así, el rootfs solo contiene lo que pertenece a la imagen y puede sustituirse." } } ] @@ -23,17 +23,35 @@ { "id": "questions", "title": "Qué pregunta el instalador", - "intro": "La plantilla aporta las rutas que necesita la aplicación. Cada una se sitúa en un disco del contenedor o en un directorio del host, y pueden añadirse más rutas antes del resumen.", + "intro": "La plantilla proporciona las rutas que necesita la aplicación. Cada una se ubica en un disco del contenedor o en un directorio del host, y se pueden añadir más rutas antes del resumen.", "blocks": [ { "steps": { "items": [ - { "title": "Rutas requeridas", "body": "Se enumeran /config, /data, bibliotecas, descargas y cualquier volumen que declare la aplicación." }, - { "title": "Ubicación", "body": "Cada ruta se sitúa en un disco del contenedor o en un directorio existente del host." }, - { "title": "Almacenamiento y tamaño", "body": "Un disco del contenedor se crea en un almacenamiento de Proxmox VE con el tamaño indicado. La instalación por defecto usa local-lvm; la instalación avanzada pregunta el almacenamiento de cada ruta justo antes de su tamaño y propone el del rootfs. Aparece como vm-VMID-disk-N y se conecta como mpN." }, - { "title": "Directorio del host", "body": "Un directorio del host se indica por su ruta. Si no existe, se crea con el propietario que mapea el contenedor." }, - { "title": "Rutas adicionales", "body": "Antes de instalar pueden añadirse más pares de ruta del host o volumen y ruta del contenedor." }, - { "title": "Resumen", "body": "La relación completa se muestra antes de crear el CT y se guarda en su contrato de instancia." } + { + "title": "Rutas necesarias", + "body": "Se enumeran /config, /data, bibliotecas, descargas y todos los volúmenes declarados por la aplicación." + }, + { + "title": "Ubicación", + "body": "Cada ruta se ubica en un disco del contenedor o en un directorio del host ya existente." + }, + { + "title": "Almacenamiento y tamaño", + "body": "Se crea un disco del contenedor en un almacenamiento de Proxmox VE con el tamaño indicado. La instalación predeterminada usa local-lvm; la avanzada pregunta el almacenamiento de cada ruta justo antes de su tamaño y propone el del rootfs. El disco aparece como vm-VMID-disk-N y se conecta como mpN." + }, + { + "title": "Directorio del host", + "body": "El directorio del host se indica mediante su ruta. Si no existe, se crea con el usuario al que se mapea el del contenedor como propietario." + }, + { + "title": "Rutas adicionales", + "body": "Antes de instalar se pueden añadir más pares de ruta o volumen del host y ruta dentro del contenedor." + }, + { + "title": "Resumen", + "body": "Antes de crear el CT se muestra la correspondencia completa de rutas y se guarda en el contrato de la instancia." + } ] } } @@ -45,44 +63,85 @@ "blocks": [ { "table": { - "headers": ["Propiedad", "Disco del contenedor", "Directorio del host"], + "headers": [ + "Propiedad", + "Disco del contenedor", + "Directorio del host" + ], "rows": [ - ["En la configuración", "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", "mpN: /mnt/oci-shared/media,mp=/data/media"], - ["Backup del contenedor (vzdump)", "Incluido, con backup=1", "No incluido"], - ["Tamaño", "Fijo; crece con un redimensionado del punto de montaje", "El espacio libre del sistema de archivos o dataset del host"], - ["Otros contenedores", "Solo lo monta su propio contenedor", "El mismo directorio puede montarse en varios contenedores"], - ["Snapshots y restauración", "Los gestiona Proxmox VE junto con el CT", "Se gestionan en el almacenamiento del host"], - ["Al eliminar la aplicación", "Se elimina con el contenedor", "Se conserva, con su contenido"], - ["Al mover el CT a otro nodo", "Se mueve con el CT", "La misma ruta debe existir en el otro nodo"] + [ + "En la configuración", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Copia del contenedor (vzdump)", + "Incluido, con backup=1", + "No incluido" + ], + [ + "Tamaño", + "Fijo; se amplía redimensionando el punto de montaje", + "El espacio libre del sistema de archivos o dataset del host" + ], + [ + "Otros contenedores", + "Solo lo monta el contenedor al que pertenece", + "El mismo directorio se puede montar en varios contenedores" + ], + [ + "Snapshots y restauración", + "Los gestiona Proxmox VE junto con el CT", + "Se gestionan en el almacenamiento del host" + ], + [ + "Al eliminar la aplicación", + "Se elimina con el contenedor", + "Se conserva con su contenido" + ], + [ + "Al mover el CT a otro nodo", + "Se mueve con el CT", + "La misma ruta debe existir en el otro nodo" + ] ] } }, { - "p": "El rootfs queda reservado para los binarios y el contenido de la imagen. Una actualización o una recreación lo sustituye sin tocar ninguno de los dos tipos de punto de montaje." + "p": "El rootfs se reserva para los binarios y el contenido de la imagen. Una actualización o recreación lo sustituye sin tocar ninguno de los dos tipos de punto de montaje." } ] }, { "id": "example", - "title": "Ejemplo: un contenedor con las dos ubicaciones", + "title": "Ejemplo: un contenedor con ambas ubicaciones", "blocks": [ { "code": { - "title": "Jellyfin instalado como CT 151 en local-lvm (extracto)", - "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Disco del contenedor, incluido en el backup del CT\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Directorio del host, fuera del backup del CT\nmp1: /mnt/oci-shared/media,mp=/data/media" + "title": "Jellyfin instalado como CT 151 en local-lvm (fragmento)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" } }, { - "p": "Una actualización o una recreación sustituye solo el rootfs: mp0 conserva usuarios, bibliotecas y ajustes, y mp1 sigue mostrando los mismos archivos multimedia. Restaurar el backup del CT devuelve /config; el directorio multimedia se restaura, si hace falta, desde el backup del almacenamiento del host." + "p": "Una actualización o recreación solo sustituye el rootfs: mp0 conserva usuarios, bibliotecas y ajustes, y mp1 sigue mostrando los mismos archivos multimedia. Al restaurar la copia del CT se recupera /config; si hace falta restaurar el directorio multimedia, se utiliza la copia de seguridad del almacenamiento del host." }, { "flow": { "nodes": [ - { "label": "Contrato", "detail": "/config" }, - { "label": "Ubicación", "detail": "disco del contenedor\no directorio del host" }, - { "label": "LXC", "detail": "siempre /config\npara la aplicación" } + { + "label": "Contrato", + "detail": "/config" + }, + { + "label": "Ubicación", + "detail": "Disco del contenedor\no directorio del host" + }, + { + "label": "LXC", + "detail": "Siempre /config\npara la aplicación" + } ], - "caption": "La aplicación ve la ruta que publica la imagen; solo cambia dónde se guarda." + "caption": "La aplicación ve la ruta declarada por la imagen; solo cambia dónde se almacena." } } ] @@ -94,33 +153,48 @@ { "mermaid": { "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", - "labels": { "host": "Directorio del host", "config": "configuración propia" } + "labels": { + "host": "Directorio del host", + "config": "Configuración propia" + } } }, { - "p": "Cada contenedor guarda su configuración en su propio disco. La biblioteca o las descargas son un único directorio del host montado en la misma ruta interna de todos los contenedores, de modo que la ruta en la que escribe una aplicación es la misma en la que lee otra." + "p": "Cada contenedor guarda su configuración en su propio disco. La biblioteca o las descargas se guardan en un único directorio del host, montado en la misma ruta interna en todos los contenedores. Así, una ruta en la que escribe una aplicación es la misma desde la que lee otra." } ] }, { "id": "rclone", - "title": "Almacenamiento en la nube con la aplicación Rclone", - "intro": "La aplicación Rclone del catálogo ofrece, además de su instalación, Activar un montaje en un contenedor Rclone OCI existente. Monta un remoto ya creado y autorizado en la interfaz web de Rclone y lo publica en el host, donde otros contenedores pueden usarlo como directorio del host.", + "title": "Almacenamiento en la nube mediante la aplicación rclone", + "intro": "Además de instalarla, la ficha de rclone en el catálogo permite Activar un montaje en un contenedor OCI de rclone existente. Se monta un remoto ya creado y autorizado en la interfaz web de rclone y se publica en el host. Otros contenedores pueden usarlo como un directorio del host.", "blocks": [ { "steps": { "items": [ - { "title": "Contenedor y remoto", "body": "El VMID del contenedor Rclone, el nombre exacto del remoto y, opcionalmente, una ruta dentro de él." }, - { "title": "Nombre y caché", "body": "El nombre del montaje y el modo de caché VFS: off, minimal, writes o full (por defecto)." }, - { "title": "Vistas publicadas", "body": "Una raíz común, /mnt/oci-shared por defecto, contiene una vista de lectura y escritura en /mnt/oci-shared/remotes/NOMBRE y una de solo lectura en /mnt/oci-shared/remotes-ro/NOMBRE." }, - { "title": "Activación", "body": "Tras una confirmación, el CT se detiene, se configuran su orden de arranque y un hookscript de Proxmox VE, y se vuelve a iniciar. La operación espera hasta que las dos vistas están montadas en el host." } + { + "title": "Contenedor y remoto", + "body": "El VMID del contenedor de rclone, el nombre exacto del remoto y, opcionalmente, una ruta dentro de él." + }, + { + "title": "Nombre del montaje y caché", + "body": "El nombre del montaje y el modo de caché VFS: off, minimal, writes o full (predeterminado)." + }, + { + "title": "Vistas publicadas", + "body": "Una raíz común, /mnt/oci-shared de forma predeterminada, contiene una vista de lectura y escritura en /mnt/oci-shared/remotes/NAME y otra de solo lectura en /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Activación", + "body": "Tras confirmar, se detiene el CT, se configuran su comando de arranque y un hookscript de Proxmox VE y se vuelve a iniciar. La operación espera a que ambas vistas estén montadas en el host." + } ] } }, { "calloutInfo": { - "title": "Si el montaje no llega a activarse", - "body": "Se restaura la configuración anterior del contenedor y se vuelve a iniciar, de modo que una activación fallida deja Rclone como estaba." + "title": "Si el montaje no se activa", + "body": "Se restaura la configuración anterior del contenedor y se vuelve a iniciar. Así, si la activación falla, rclone queda como estaba." } } ] @@ -128,41 +202,58 @@ { "id": "network", "title": "Direcciones y redes", - "intro": "Una aplicación simple necesita una dirección. Una aplicación multicontenedor necesita además una red estable entre sus miembros.", + "intro": "Una aplicación de un solo contenedor necesita una dirección. Una aplicación con varios contenedores también necesita una red estable entre sus miembros.", "blocks": [ { "cards": { "items": [ - { "icon": "network", "title": "Aplicación simple", "body": "Se eligen el bridge y DHCP o una dirección CIDR fija. El LXC tiene una dirección propia y el resumen muestra las URL completas de los servicios." }, - { "icon": "waypoints", "title": "Aplicación multicontenedor", "body": "Se busca una subred libre, se crea un bridge privado persistente y cada miembro (aplicación, base de datos, caché) recibe en él una dirección fija." } + { + "icon": "network", + "title": "Aplicación de un contenedor", + "body": "Se elige el puente de red y DHCP o una dirección fija en formato CIDR. El LXC tiene su propia dirección y el resumen muestra las URL completas de los servicios." + }, + { + "icon": "waypoints", + "title": "Aplicación con varios contenedores", + "body": "Se busca una subred libre, se crea un puente privado persistente y cada miembro —aplicación, base de datos y caché— recibe una dirección fija en él." + } ] } }, { "flow": { "nodes": [ - { "label": "LAN", "detail": "dirección accesible\nsolo el servicio principal" }, - { "label": "LXC principal", "detail": "web / API\nLAN + red privada" }, - { "label": "Red privada", "detail": "PostgreSQL · Valkey · ML\ndirecciones fijas" } + { + "label": "LAN", + "detail": "Dirección accesible\nsolo para el servicio principal" + }, + { + "label": "LXC principal", + "detail": "Web / API\nLAN + red privada" + }, + { + "label": "Red privada", + "detail": "PostgreSQL · Valkey · ML\ndirecciones fijas" + } ], - "caption": "Las dependencias se comunican por la red privada y no tienen dirección en la LAN." + "caption": "Las dependencias se comunican por la red privada y no tienen una dirección en la LAN." } }, { - "p": "El contrato de la pila guarda el bridge, la subred, las direcciones y las relaciones entre servicios. La actualización de la aplicación reutiliza la misma topología en todos sus miembros. ProxMenux Monitor abre el contenedor principal en su dirección de la LAN, no en la de la red privada." + "p": "El contrato de la pila guarda el puente, la subred, las direcciones y las relaciones entre servicios. Al actualizar la aplicación se reutiliza la misma topología para todos sus miembros. ProxMenux Monitor abre el contenedor principal en su dirección de la LAN, no en la de la red privada." } ] }, { "id": "ownership", - "title": "Propietarios", + "title": "Propietarios de archivos y directorios", "blocks": [ { "list": { "items": [ - "Los directorios nuevos se crean con el UID y el GID que mapea el LXC no privilegiado.", - "Los directorios existentes no cambian de propietario de forma recursiva.", - "Los sockets, los archivos del sistema y las rutas sensibles no se ofrecen como directorios del host genéricos." + "Los directorios nuevos se crean con el UID y el GID a los que se mapea el LXC sin privilegios.", + "No se cambia recursivamente el propietario de los directorios existentes.", + "Los sockets, archivos del sistema y rutas sensibles no se ofrecen como directorios genéricos del host." ] } } @@ -170,17 +261,41 @@ }, { "id": "backup", - "title": "Qué contiene el backup de un contenedor", + "title": "Qué contiene una copia del contenedor", "blocks": [ { "table": { - "headers": ["Elemento", "En el vzdump del CT", "Dónde se conserva"], + "headers": [ + "Elemento", + "En la copia vzdump del CT", + "Dónde se conserva" + ], "rows": [ - ["rootfs OCI", "Sí", "El backup del CT; también puede reconstruirse desde la imagen y el contrato"], - ["Disco del contenedor con backup=1", "Sí", "El backup del CT"], - ["Directorio del host", "No", "El backup del almacenamiento del host"], - ["Contrato de la instancia", "No", "/usr/local/share/proxmenux/oci/instances/VMID/ en el host"], - ["Aplicación multicontenedor", "Cada miembro en su propio backup", "Los backups de cada miembro, más el contrato de la pila y su bridge en el host"] + [ + "Rootfs OCI", + "Sí", + "En la copia del CT; también se puede reconstruir a partir de la imagen y el contrato" + ], + [ + "Disco del contenedor con backup=1", + "Sí", + "La copia del CT" + ], + [ + "Directorio del host", + "No", + "La copia de seguridad del almacenamiento del host" + ], + [ + "Contrato de la instancia", + "Sí, la copia que guarda el CT", + "/.proxmenux/oci-record.json dentro del CT; las copias adicionales del host y del clúster se guardan por separado" + ], + [ + "Aplicación con varios contenedores", + "Cada miembro en su propia copia", + "Las copias de todos los miembros, más el contrato de la pila y su puente de red en el host" + ] ] } } diff --git a/web/messages/fr/docs/oci-manager/architecture.json b/web/messages/fr/docs/oci-manager/architecture.json new file mode 100644 index 00000000..215730e8 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "Comment une image OCI est adaptée | ProxMenux", + "description": "Du dépôt de l’image et de son fichier Compose à un modèle vérifiable, un plan de déploiement et un LXC natif de Proxmox VE, sans Docker à l’intérieur." + }, + "header": { + "title": "Comment une image OCI est adaptée", + "description": "Du dépôt de l’image et de son fichier Compose à un modèle vérifiable, un plan de déploiement et un LXC natif, sans installer Docker à l’intérieur.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "Le processus d’adaptation", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Dépôt de l’image", + "converter": "Convertisseur", + "template": "Modèle JSON", + "blockers": "Aucun point bloquant ?", + "no": "Non", + "yes": "Oui", + "review": "Révision / overlay", + "plan": "Plan de déploiement", + "lxc": "LXC natif" + } + } + }, + { + "p": "Le convertisseur lit l’image et le fichier Compose publiés par le projet, puis produit un modèle JSON. Si des exigences n’ont pas pu être adaptées et bloquent l’installation, le modèle est révisé avant sa publication dans le catalogue. Un fichier d’ajustements vérifiés, appelé overlay, permet de résoudre ces points. Seuls les modèles sans point bloquant sont proposés à l’installation." + } + ] + }, + { + "id": "template", + "title": "Ce que conserve le modèle", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Identité de l’image", + "body": "Dépôt, tag mis à jour en continu, architecture, digest résolu et révision de la source." + }, + { + "icon": "braces", + "title": "Contrat du conteneur", + "body": "Entrypoint, Cmd, environnement, utilisateur, répertoire de travail, signal d’arrêt, ports et volumes." + }, + { + "icon": "layers", + "title": "Adaptation à Proxmox VE", + "body": "Ressources, sécurité, points de montage, périphériques, sysctls, contrôles de bon fonctionnement et adaptations nécessaires pour chacun, avec leur justification." + }, + { + "icon": "shield", + "title": "Compatibilité", + "body": "Clés prises en charge, exigences non adaptées qui bloquent l’installation et état de chaque validation." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "OCI fournit le processus ; Compose fournit l’environnement", + "blocks": [ + { + "table": { + "headers": [ + "Source", + "Exemple", + "Résultat natif" + ], + "rows": [ + [ + "Métadonnées OCI", + "Entrypoint, Cmd, User", + "Proxmox VE les importe à la création du CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Entrées d’environnement LXC, mpN et devN" + ], + [ + "Profil ProxMenux", + "GPU, contrôle de bon fonctionnement, identifiants", + "Questions et adaptations vérifiées" + ], + [ + "Utilisateur", + "VMID, stockage, réseau", + "Le contrat de l’instance" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Ce qui se passe pendant l’installation", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Résoudre la référence", + "body": "Le registre est interrogé, l’architecture de l’hôte est sélectionnée et le digest exact désigné par le tag mis à jour en continu est fixé." + }, + { + "title": "Télécharger et vérifier", + "body": "Skopeo télécharge l’image sous forme d’archive OCI. Avant toute création, chaque couche est vérifiée à l’aide de son digest, puis décompressée. Si le téléchargement est endommagé, il est tenté une seconde fois avant l’arrêt de l’installation." + }, + { + "title": "Créer le système de fichiers", + "body": "pct create crée le rootfs à partir de l’archive et conserve les métadonnées officielles du processus de l’image." + }, + { + "title": "Raccorder les ressources", + "body": "Les volumes, le réseau, l’environnement, les périphériques et les profils de sécurité déclarés sont appliqués." + }, + { + "title": "Console", + "body": "La sortie console du conteneur est conservée sur l’hôte. La console de Proxmox VE ouvre un shell si l’image en contient un." + }, + { + "title": "Vérifier", + "body": "Au premier démarrage, l’installation attend l’attribution d’une adresse et la réponse du service. En cas d’échec, elle n’est pas signalée comme réussie." + }, + { + "title": "Enregistrer la configuration", + "body": "La configuration effective est enregistrée dans le contrat de l’instance utilisé lors des mises à jour et des recréations." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Exemple : une image avec /config et /downloads", + "intro": "Une définition Compose courante et la configuration Proxmox VE qui en résulte. Les chemins attendus par l’application ne changent pas.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (extrait)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "mp0 est un second disque appartenant au conteneur, nommé vm-VMID-disk-N sur le stockage sélectionné. Il est monté dans /config et, avec backup=1, inclus dans la sauvegarde du conteneur. mp1 ne crée aucun disque : il monte le répertoire de l’hôte /srv/downloads dans /downloads à l’intérieur du LXC. Le port 8080 n’est pas redirigé : le LXC possède sa propre adresse et le service y répond." + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/custom-image.json b/web/messages/fr/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..6817b296 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Installer une image absente du catalogue | ProxMenux", + "description": "Adaptez une image OCI à partir d’un fichier Compose, d’une URL, d’une commande docker run ou d’une référence d’image, puis vérifiez ce que ProxMenux peut reproduire avant l’installation." + }, + "header": { + "title": "Installer une image absente du catalogue", + "description": "Votre image est adaptée à partir de son fichier Compose, d’une commande docker run ou de sa seule référence. Le résultat est présenté pour vérification avant la création du moindre LXC.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Le même convertisseur et le même installateur", + "body": "L’option Installer une image absente du catalogue utilise le même convertisseur et le même installateur que les modèles du catalogue. Le contrat est simplement généré à ce moment-là à partir de la définition fournie, puis validé avant toute création de LXC." + } + } + ] + }, + { + "id": "sources", + "title": "Comment décrire l’image", + "intro": "Le premier écran demande comment l’image est décrite. Quatre possibilités fournissent une définition à adapter, une cinquième utilise uniquement le nom de l’image et trois autres lisent un oci-compose déjà rédigé.", + "blocks": [ + { + "table": { + "headers": [ + "Option", + "Contenu lu" + ], + "rows": [ + [ + "Coller son fichier Compose dans le terminal", + "Collez le contenu YAML dans le terminal et terminez avec Ctrl+D." + ], + [ + "Lire son fichier Compose depuis cet hôte", + "Un chemin sur le nœud, /root/docker-compose.yml par défaut. Le fichier doit exister et ne pas dépasser 256 KiB." + ], + [ + "Télécharger son fichier Compose depuis une adresse", + "Une adresse http:// ou https:// qui fournit le YAML brut. Le téléchargement est limité à 256 KiB." + ], + [ + "Coller sa commande docker run dans le terminal", + "La commande publiée par le projet. Les ports, l’environnement, les volumes, les périphériques, les capabilities, l’utilisateur, la mémoire partagée et les autres options prises en charge sont adaptés." + ], + [ + "Uniquement la référence de l’image, sans fichier Compose", + "Une référence comme ghcr.io/user/application:latest. Le registre est interrogé et les métadonnées OCI de l’image sont conservées." + ], + [ + "Coller un oci-compose dans le terminal", + "Collez le JSON dans le terminal et terminez avec Ctrl+D. Il n’est pas converti : il est vérifié puis installé tel qu’il est rédigé." + ], + [ + "Lire un oci-compose depuis cet hôte", + "Un fichier JSON sur le nœud, /root/oci-compose.json par défaut, de 2 MiB au maximum." + ], + [ + "Télécharger un oci-compose depuis une adresse", + "Une adresse http:// ou https:// qui fournit le fichier JSON. Le téléchargement est limité à 2 MiB." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Menu de choix de la description d’une image absente du catalogue", + "caption": "Les façons de décrire une image absente du catalogue." + } + } + ] + }, + { + "id": "reference-only", + "title": "Ce que fournit la seule référence de l’image", + "intro": "L’image contient les métadonnées de son processus, mais pas tout l’environnement habituellement ajouté par un fichier Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Lu depuis l’image", + "Absent de l’image", + "Conséquence" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir et l’environnement intégré", + "Variables présentes uniquement dans la documentation", + "Ajoutées pendant l’installation ou avec Modifier" + ], + [ + "Volumes déclarés par l’image", + "Répertoires de l’hôte indiqués uniquement dans le fichier Compose", + "Les chemins supplémentaires sont ajoutés avant l’installation" + ], + [ + "Ports EXPOSE", + "L’URL, le protocole ou le contrôle de bon fonctionnement", + "Le port et la méthode de vérification du service sont confirmés" + ], + [ + "Architectures et digest dans le registre", + "Périphériques, privilèges ou dépendances externes", + "Ils ne sont pas activés sans une définition qui les demande" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Vérifier avant l’installation", + "intro": "Après adaptation de la définition, ProxMenux présente ce qu’il en a compris avant de demander le VMID, les ressources ou le stockage.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analyser", + "body": "Les services, l’image, les ports, les chemins, l’environnement, les périphériques, la sécurité et le contrôle de bon fonctionnement sont lus." + }, + { + "title": "Interroger le registre", + "body": "L’image doit exister dans le registre et les architectures publiées sont affichées. Un registre privé est consulté avec le compte enregistré pour celui-ci." + }, + { + "title": "Lister les options non appliquées", + "body": "Les labels, réseaux Docker, paramètres Swarm et autres clés sans effet sur un LXC sont répertoriés." + }, + { + "title": "Bloquer les options sans équivalent sûr", + "body": "Les clés sans équivalent sûr apparaissent sous Options sans équivalent et empêchent la poursuite de l’installation. Aucune n’est ignorée silencieusement." + }, + { + "title": "Demander les secrets", + "body": "Les variables dont le nom évoque un mot de passe, un jeton ou une clé sont demandées comme données sensibles pendant l’installation." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Récapitulatif de l’interprétation du fichier Compose par ProxMenux", + "caption": "Récapitulatif affiché avant la demande du VMID et des ressources." + } + }, + { + "p": "Après validation du récapitulatif, le parcours est celui du catalogue : nom, mode par défaut ou avancé, VMID, CPU, mémoire, réseau, démarrage avec le nœud, chemins persistants et supplémentaires, périphériques compatibles et récapitulatif final." + } + ] + }, + { + "id": "examples", + "title": "Exemples de saisie", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "Un oci-compose rédigé à la main", + "intro": "Un oci-compose est le fichier JSON à partir duquel une application est installée. Le catalogue en conserve un par application. Le même format peut être rédigé à la main et installé avec cette option.", + "blocks": [ + { + "p": "Dans un fichier rédigé à la main, l’auteur définit l’image, les ports, les variables, les volumes et les questions de l’installation. ProxMenux complète le reste à la lecture. Le format permet d’exprimer ce que Compose ne prévoit pas : texte d’une question, mot de passe généré à l’installation, profil GPU, propriétaire de chaque volume, contrôle du premier démarrage ou identifiants affichés à la fin." + }, + { + "steps": { + "items": [ + { + "title": "Vérifier", + "body": "Un fichier contenant des erreurs n’est pas installé. Chaque erreur indique le champ concerné ; un JSON invalide signale la ligne et la colonne." + }, + { + "title": "Examiner", + "body": "Un fichier valide affiche ce qu’il installe : image, adresse web, ports, chemins persistants, variables demandées et fichiers écrits pendant l’installation." + }, + { + "title": "Installer", + "body": "L’installation suit le même parcours que pour toute application du catalogue, avec les paramètres par défaut ou avancés." + } + ] + } + }, + { + "calloutWarning": { + "title": "Un fichier qui détermine les privilèges", + "body": "Un oci-compose détermine ce qui est monté dans le conteneur et avec quels privilèges. La vérification le précise avant l’installation : n’installez que des fichiers de sources fiables." + } + }, + { + "p": "Le README du moteur OCI décrit le format bloc par bloc avec deux exemples complets. Un enregistrement d’installation peut aussi être lu de cette façon : seul le modèle d’origine est utilisé, sans aucune des réponses de cette installation." + } + ] + }, + { + "id": "private-registries", + "title": "Images de registres privés", + "intro": "Une image d’un registre privé, comme un paquet privé GitHub ou un dépôt privé Docker Hub, est lue avec un compte enregistré dans ProxMenux.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Le registre refuse l’accès à l’image", + "body": "Si le registre indique que l’image nécessite un compte, la vérification propose de s’y connecter, puis lit à nouveau l’image." + }, + { + "title": "Se connecter", + "body": "Un nom d’utilisateur et un mot de passe ou jeton autorisé à télécharger l’image. Le registre les vérifie ; ils ne sont enregistrés que s’il les accepte." + }, + { + "title": "Installer et maintenir à jour", + "body": "Ce compte sert au téléchargement pendant l’installation, ainsi qu’aux mises à jour, à Modifier, à Recréer, au watchdog et à la recherche de mises à jour du Monitor, sur chaque nœud du cluster." + } + ] + } + }, + { + "p": "Comptes de registres, dans le menu principal d’OCI manager Apps, affiche les comptes enregistrés par registre et utilisateur, jamais leurs secrets. Chaque compte permet de vérifier, télécharger ou installer une image, ainsi que de mettre à jour ou supprimer les identifiants." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io) : l’utilisateur est le compte GitHub et le mot de passe un jeton d’accès personnel avec l’autorisation read:packages.", + "Docker Hub (docker.io) : l’utilisateur est le compte Docker Hub et le mot de passe un jeton d’accès avec autorisation de lecture.", + "Autres registres, y compris avec un port propre comme registry.example.com:5000 : l’utilisateur et le mot de passe ou jeton fournis par ce registre." + ] + } + }, + { + "calloutInfo": { + "title": "Où les comptes sont conservés", + "body": "Dans /etc/pve/priv/proxmenux/registry-auth.json, lisible uniquement par root, dans le même dossier que les mots de passe de stockage de Proxmox. /etc/pve est partagé entre tous les nœuds du cluster : un compte ajouté sur un nœud est donc utilisé partout. La sauvegarde de l’hôte inclut ce fichier et la désinstallation de ProxMenux le supprime de tous les nœuds." + } + } + ] + }, + { + "id": "limits", + "title": "Définitions qui ne sont pas installées", + "blocks": [ + { + "list": { + "items": [ + "Un Dockerfile sans image publiée : l’image doit d’abord être construite et publiée dans un registre OCI.", + "Un fichier Compose à plusieurs services : cette option installe un seul service par conteneur, même si plusieurs services utilisent la même image.", + "configs, secrets externes ou formats de périphériques impossibles à adapter sans ambiguïté.", + "Substitutions shell comme $(command) : il faut saisir directement la valeur résultante.", + "Options docker run non reconnues et --env-file : utilisez plutôt un fichier Compose aux valeurs explicites." + ] + } + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/hardware.json b/web/messages/fr/docs/oci-manager/hardware.json new file mode 100644 index 00000000..e1bf73c0 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Dispositifs et accélération ProxMenux", + "description": "Comment OCI manager Apps rend les GPU, NVIDIA, Coral, USB, FUSE et autres périphériques accessibles à un conteneur OCI sous forme de ressources natives Proxmox VE validées." + }, + "header": { + "title": "Dispositifs et accélération", + "description": "GPU, NVIDIA, Coral, USB, FUSE et les dispositifs de blocs deviennent des ressources natives validées Proxmox VE du conteneur.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "L'appareil dont l'application a besoin, pas l'hôte entier", + "blocks": [ + { + "p": "Un appareil demandé par le fichier Compose ou par le profil d'application devient une entrée concrète devN ou un montage LXC. Demander un GPU, un périphérique USB ou un Coral ne rend pas le conteneur privilégié." + }, + { + "flow": { + "nodes": [ + { + "label": "Inventaire des hôtes", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "fournisseur et\npermissions vérifiées" + }, + { + "label": "LXC", + "detail": "même périphérique\nGID effectif" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "D'où vient la demande de l'appareil", + "blocks": [ + { + "table": { + "headers": [ + "Source", + "Ce qui est lu", + "Ce que fait l'installateur" + ], + "rows": [ + [ + "Docker Compose", + "Demandes devices, group_add, deploy.resources et NVIDIA", + "Chaque exigence devient une demande d'appareil présentée pour examen" + ], + [ + "Profil du catalogue", + "La prise en charge réelle des GPU, de Coral, d’OpenCL, d’USB ou de FUSE par l’application", + "Seules les options validées pour cette image sont proposées" + ], + [ + "métadonnées d'images et documentation", + "VA-API, Selkies, LinuxServer mods ou le NVIDIA runtime", + "Les variables documentées et la préparation sont ajoutées" + ], + [ + "Sélection de l'utilisateur", + "CPU uniquement, Intel/AMD, OpenCL, NVIDIA ou un appareil optionnel", + "La sélection est stockée dans le contrat d'instance" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Les périphériques détectés ne sont pas ajoutés automatiquement", + "body": "L'hôte est inventorié, mais seuls les appareils déclarés par le fichier Compose ou par un profil compatible sont proposés et attachés. Un GPU, un dongle USB ou un Coral présent sur l'hôte n'est pas exposé à tous les LXC." + } + } + ] + }, + { + "id": "identify", + "title": "Comment le périphérique hôte est identifié", + "intro": "Avant que le LXC ne soit modifié, l'appareil est lu sur l'hôte et correspond au profil choisi.", + "blocks": [ + { + "table": { + "headers": [ + "Type", + "Identité", + "Validation" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* et /sys/class/drm/NODE/device/vendor", + "Dispositif de caractères avec fournisseur 0x8086 (Intel) ou 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "Le nœud de rendu, plus /dev/kfd lorsque le profil en a besoin", + "Existence, type, fournisseur, autorisations et compatibilité déclarée" + ], + [ + "AMD ROCm", + "Le nœud de rendu, /dev/kfd et la génération indiquée par le pilote de calcul dans /sys/class/kfd/kfd/topology/nodes/*/properties", + "Une génération pour laquelle l’image ROCm de l’application contient des noyaux de calcul — les GPU RDNA2 cités et les plus récents — est proposée telle quelle. Les autres GPU de ces familles, comme les Radeon 680M et 780M, sont proposés à titre expérimental et jamais présélectionnés : ROCm ne les prend pas officiellement en charge, l’application les présente comme la génération de leur famille (HSA_OVERRIDE_GFX_VERSION) et l’installateur demande confirmation avant de télécharger l’image plus volumineuse. Une GPU plus ancienne, telle que la partie graphique intégrée d’un Ryzen 5000U, n’est pas proposée et Immich conserve la reconnaissance sur le CPU." + ], + [ + "NVIDIA", + "nvidia-smi et nvidia-container-cli", + "GPU, UUID, PCI bus, version pilote, Toolkit, /dev/nvidia* nœuds, binaires et bibliothèques" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N et son lien dans /sys/dev/char/MAJOR:MINOR", + "Nœud de caractère, majeur/mineur, propriétaire, GID et permissions" + ], + [ + "USB et série", + "/dev/ttyUSB*, /dev/ttyACM* ou /dev/bus/usb/BBB/DDD", + "Nœud de caractère; pour USB aussi fournisseur, produit et série lorsque sysfs les publie" + ], + [ + "KVM, TUN, FUSE, vidéo et SCSI générique", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN ou /dev/sgN", + "Chemin pris en charge, type de nœud et permissions effectives" + ], + [ + "Lecteur optique", + "/dev/srN", + "Un périphérique bloc" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Ce qui se passe pendant l'installation", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Le modèle offre ses profils", + "body": "Par exemple CPU seulement, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL ou NVIDIA. Les options appartiennent à l'image, pas à un menu commun." + }, + { + "title": "Un profil est choisi", + "body": "Il définit les nœuds de périphérique, l'environnement, les mods ou l'exécution dont l'application a besoin." + }, + { + "title": "Un chemin est proposé", + "body": "Pour DRM, /dev/dri/renderD128, qui peut être modifié sur un hôte avec plusieurs nœuds de rendu. Pour USB ou série, le nœud précis est sélectionné." + }, + { + "title": "Validation", + "body": "L'existence, le type, le fournisseur autorisé, les autorisations et le GID sont vérifiés. Une inadéquation arrête l'opération." + }, + { + "title": "Le contrat est écrit", + "body": "Chemin, mode, GID, accès à l'écriture et profil sont enregistrés pour les mises à jour et les recréations de conteneurs." + }, + { + "title": "Ajout et vérification", + "body": "pct set ajoute l'entrée devN et l'accès est ensuite vérifié à l'intérieur du LXC. Les images LinuxServer sont également vérifiées en tant qu'utilisateur abc." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Comment il apparaît dans la configuration LXC", + "intro": "Valeurs indicatives : dev0 et dev1 sont les emplacements libres attribués par Proxmox VE ; renderD128, apex_0 et le GID dépendent du matériel du nœud.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Un périphérique USB spécifique", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "Le GID est lu avec stat sur l'hôte et écrit à l'entrée devN; les groupes render et video ne sont pas supposés avoir un nombre fixe. L'appareil conserve le même chemin /dev à l'intérieur du LXC, où les propres mécanismes de l'application le recherchent." + } + ] + }, + { + "id": "profiles", + "title": "Profils qu'une image peut offrir", + "blocks": [ + { + "table": { + "headers": [ + "Profil", + "Configuration résultante", + "Proposé quand" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "Le noeud de rendu /dev/dri", + "L'application prend en charge l'accélération vidéo" + ], + [ + "OpenCL", + "Noeud Render, /dev/kfd au besoin et le mod officiel", + "L'image ou le profil le documente" + ], + [ + "NVIDIA", + "Dispositifs pilotes et bibliothèques de l'hôte", + "L’hôte dispose d’un pilote fonctionnel et de NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 ou le bus USB", + "Le profil déclare la prise en charge de Coral (Frigate)" + ], + [ + "USB, série, FUSE", + "Un seul appareil, un arbre validé ou une fonction LXC", + "Le contrat le demande" + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Prérequis sur le nœud : NVIDIA Container Toolkit", + "body": "Un pilote fonctionnel sur Proxmox VE ne suffit pas à donner une GPU NVIDIA à une image OCI. OCI Manager Apps utilise nvidia-container-cli, de NVIDIA Container Toolkit, pour identifier les appareils et obtenir les binaires et les bibliothèques qui correspondent au pilote chargé." + } + }, + { + "p": "Le installateur NVIDIA de ProxMenux installe NVIDIA Container Toolkit du dépôt officiel NVIDIA avec le pilote. Il vérifie ses quatre paquets, valide nvidia-container-cli et enregistre le résultat dans le journal de changement de Audit & Report." + }, + { + "p": "Ces deux commandes sur l'hôte montrent si le pilote et le Toolkit sont disponibles :" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "Sur un hôte où le pilote a été installé par d'autres moyens, le Toolkit est installé à partir du dépôt stable officiel:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "L’inventaire utilisé par OCI manager Apps provient de la sortie de :" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "La configuration d'exécution Docker n'est pas impliquée", + "body": "Les conteneurs sont des LXC natifs sans démon Docker : nvidia-ctk runtime configure --runtime=docker ne joue donc aucun rôle. ProxMenux interroge directement nvidia-container-cli et écrit les périphériques et montages LXC. Les commandes et plateformes prises en charge sont maintenues dans le guide d’installation de NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventaire issu du pilote", + "body": "nvidia-container-cli énumère les nœuds de périphérique, les binaires, les micrologiciels et les bibliothèques du pilote installé." + }, + { + "icon": "refresh", + "title": "Pas de version fixe", + "body": "Le modèle ne nomme pas les fichiers de bibliothèque. Le profil est généré à partir de l'hôte actuel." + }, + { + "icon": "shield", + "title": "Montages en lecture seule", + "body": "Les bibliothèques hôtes sont montées en lecture seule au lieu d'être copiées dans le conteneur." + }, + { + "icon": "hardDrive", + "title": "Changements de pilote", + "body": "Après un changement de pilote, l'inventaire est généré à nouveau avant le démarrage des LXC touchés." + } + ] + } + }, + { + "code": { + "title": "Résultat NVIDIA (simplifié)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Passer uniquement /dev/nvidia0 ne suffit pas. Les composants espace utilisateur du pilote chargé sont montés en lecture seule, et nvidia-smi fonctionne ensuite à l'intérieur du LXC pour comparer GPU, UUID, PCI bus et version avec l'inventaire hôte." + } + ] + }, + { + "id": "usb", + "title": "Coral USB, série et USB", + "blocks": [ + { + "calloutWarning": { + "title": "La numérotation USB peut changer", + "body": "Un chemin tel que /dev/bus/usb/003/004 peut changer lorsque le périphérique est reconnecté ou que l'hôte redémarre. Le profil enregistre le fournisseur, le produit et la série lorsqu'ils sont disponibles, mais une nouvelle adresse de bus n'est pas automatiquement réaffectée." + } + }, + { + "p": "Un périphérique est donné par son nœud concret : /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 ou /dev/bus/usb/BBB/DDD. Passer l'intégralité de /dev n'est pas accepté. Coral est proposé uniquement aux applications dont le profil le déclare." + } + ] + }, + { + "id": "trees", + "title": "Arbres de périphériques et fonctionnalités LXC", + "blocks": [ + { + "table": { + "headers": [ + "Demande", + "Configuration résultante", + "Portée" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd ou /dev/bus/usb", + "Chaque noeud de caractère de l'arbre obtient sa propre entrée devN avec le mode hôte et GID", + "Seulement l'arbre demandé, pas le reste de /dev" + ], + [ + "/dev/fuse", + "Le nœud et, lorsque le profil en a besoin, la fonction fuse=1", + "FUSE seul ne publie pas de montages sur d'autres LXC" + ], + [ + "/dev/net/tun", + "Une entrée devN sur le même chemin à l'intérieur du LXC", + "La configuration VPN ou réseau reste dans l'application" + ], + [ + "/dev/kvm", + "Une entrée devN validée", + "Offert seulement lorsque le contrat le demande" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Confirmations par niveau de risque", + "blocks": [ + { + "p": "Les périphériques concrets, les privilèges facultatifs, les privilèges requis, la relaxation AppArmor ou seccomp et l'accès à l'espace de noms PID hôte sont traités comme des cas distincts, et non sous une seule étiquette privilégiée générique. Chaque option présentant un risque est expliquée et confirmée lors de l'installation." + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/index.json b/web/messages/fr/docs/oci-manager/index.json new file mode 100644 index 00000000..9a82d408 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Exécutez des images OCI officielles dans des conteneurs LXC natifs de Proxmox VE, avec des données persistantes, l’accès aux périphériques, des applications à plusieurs conteneurs et des mises à jour transactionnelles." + }, + "header": { + "title": "OCI manager Apps", + "description": "Les images OCI officielles s’exécutent dans des conteneurs LXC natifs de Proxmox VE. L’image reste celle publiée par son mainteneur ; ProxMenux reproduit l’environnement que Docker Compose aurait créé autour d’elle.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "Une image OCI, un LXC natif", + "body": "Aucun moteur Docker ne s’exécute dans les conteneurs. Proxmox VE importe le système de fichiers de l’image et ses métadonnées OCI. L’application devient le processus principal d’un LXC natif, administré avec les mêmes outils que les autres conteneurs du nœud." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Source", + "detail": "Image OCI\nCompose et documentation" + }, + { + "label": "ProxMenux", + "detail": "Contrat JSON\nvalidation et planification" + }, + { + "label": "Proxmox VE", + "detail": "LXC natif\nvolumes et périphériques" + } + ], + "caption": "L’application d’origine est conservée : son environnement est décrit de façon déclarative, puis reproduit." + } + } + ] + }, + { + "id": "where", + "title": "Où le trouver", + "intro": "Sur le nœud Proxmox, en tant que root, ouvrez OCI manager Apps (Technology Preview) dans le menu principal de ProxMenux. Le premier écran propose :", + "blocks": [ + { + "list": { + "items": [ + "Rechercher des applications : rechercher par nom dans tout le catalogue.", + "Toutes les applications : afficher le catalogue complet et le nombre d’applications.", + "Gérer les applications OCI installées : mettre à jour, modifier, supprimer ou récupérer les applications déjà installées.", + "Comptes de registres : gérer les comptes utilisés pour installer et mettre à jour des images provenant de registres privés.", + "Installer une image absente du catalogue : adapter un fichier Compose, une commande docker run ou une référence d’image.", + "Les catégories du catalogue, chacune avec son nombre d’applications." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Écran principal d’OCI manager Apps avec recherche, catalogue complet, gestion des applications, images hors catalogue et catégories.", + "caption": "Écran principal d’OCI manager Apps." + } + }, + { + "p": "La sélection d’une application affiche sa description, l’image utilisée et deux modes d’installation. Installer avec les paramètres par défaut ne application que les choix essentiels : stockage, emplacement des données, adresse et démarrage. Installer avec les paramètres avancés présente toutes les décisions dans l’ordre : VMID et ressources, stockage de chaque chemin, pont réseau et périphériques." + }, + { + "p": "Les applications effectivement testées affichent Vérifié dans cette vue et portent un ✓ dans les listes, que le test ait été effectué dans le laboratoire ProxMenux ou par la communauté." + } + ] + }, + { + "id": "beta", + "title": "Pourquoi le label Technology Preview est conservé", + "blocks": [ + { + "p": "OCI manager Apps conserve le label Technology Preview que Proxmox VE attribue aux conteneurs d’applications créés à partir d’images OCI, dont la prise en charge a été introduite dans Proxmox VE 9.1. Ce label concerne la technologie sur laquelle repose OCI manager Apps, et non le moteur de ProxMenux : le comportement de ces conteneurs peut encore changer entre les versions de Proxmox VE. Le label restera en place jusqu’à ce que Proxmox VE considère cette prise en charge comme stable." + }, + { + "p": "Le moteur d’OCI manager Apps est testé dans le laboratoire ProxMenux : installation, mise à jour, modification, recréation, sauvegarde, restauration, migration et watchdog. Chaque application est vérifiée séparément. Celles qui ont été effectivement testées affichent Vérifié et portent un ✓ dans les listes." + } + ] + }, + { + "id": "translation", + "title": "Comment Docker décrit les besoins et comment OCI manager Apps les applique", + "blocks": [ + { + "table": { + "headers": [ + "Besoin", + "Expression dans Docker", + "Mise en œuvre par OCI manager Apps" + ], + "rows": [ + [ + "Exécuter l’application", + "image, entrypoint, command", + "Le rootfs OCI et le processus principal du LXC" + ], + [ + "Conserver la configuration", + "volumes: /config", + "Un volume persistant mpN inclus dans la sauvegarde, ou un répertoire de l’hôte." + ], + [ + "Rendre le service accessible", + "ports", + "Une adresse propre au LXC et l’URL d’accès au service." + ], + [ + "Utiliser du matériel", + "devices, group_add", + "Des entrées devN avec le GID effectif de l’hôte et un profil validé." + ], + [ + "Relier les dépendances", + "networks, depends_on", + "Un pont réseau privé, des adresses fixes, un ordre de démarrage et des contrôles de bon fonctionnement." + ], + [ + "Mettre à jour", + "Télécharger avec pull et recréer", + "Un nouveau rootfs avec le même contrat de persistance." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "Ce qui est conservé et ce qui change", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Conservé", + "body": "L’image officielle, ses chemins internes, son environnement, son processus de démarrage et sa documentation d’utilisation." + }, + { + "icon": "boxes", + "title": "Adapté", + "body": "L’environnement d’exécution : le réseau, la persistance, les périphériques, les permissions et les dépendances utilisent les mécanismes LXC natifs de Proxmox VE." + }, + { + "icon": "shield", + "title": "Sans équivalent", + "body": "Une option Compose sans équivalent sûr est signalée comme un point bloquant. Les modèles comportant des points bloquants ne sont pas proposés à l’installation, et les options sensibles doivent être confirmées pendant l’assistant." + }, + { + "icon": "refresh", + "title": "Enregistré", + "body": "Le digest de l’image, les ressources, les chemins, le réseau, les périphériques et l’appartenance à une pile sont enregistrés dans le contrat de l’instance. Les mises à jour et les recréations reproduisent ce contrat." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Pages de cette section", + "blocks": [ + { + "next": { + "items": [ + { + "label": "Comment une image OCI est adaptée", + "href": "/docs/oci-manager/architecture", + "tail": "De l’image et de son fichier Compose à un LXC natif." + }, + { + "label": "Installer une image absente du catalogue", + "href": "/docs/oci-manager/custom-image", + "tail": "Un fichier Compose, une commande docker run ou une référence d’image." + }, + { + "label": "Données, chemins et réseau", + "href": "/docs/oci-manager/storage-network", + "tail": "Volumes du conteneur, répertoires de l’hôte, montages rclone et adresses." + }, + { + "label": "Périphériques et accélération", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB et autres périphériques." + }, + { + "label": "Applications à plusieurs conteneurs", + "href": "/docs/oci-manager/stacks", + "tail": "Application, base de données et cache dans des LXC coordonnés." + }, + { + "label": "Installer, mettre à jour et modifier", + "href": "/docs/oci-manager/lifecycle", + "tail": "Le contrat de l’instance et ses opérations." + }, + { + "label": "Dans ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versions, mises à jour, sortie console et terminal." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/lifecycle.json b/web/messages/fr/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..5030ebcc --- /dev/null +++ b/web/messages/fr/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Installer, mettre à jour et modifier | ProxMenux", + "description": "Le contrat d'instance d'un conteneur OCI et les opérations qui s'appuient dessus : mise à jour, modification, suppression, reprise d'une opération interrompue et remise en service après une restauration sur cet hôte ou un autre." + }, + "header": { + "title": "Installer, mettre à jour et modifier", + "description": "Chaque instance conserve un contrat reproductible, ce qui permet de remplacer son rootfs sans perdre sa configuration ni ses données persistantes.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "Le contrat d'instance", + "blocks": [ + { + "p": "Après l'installation, la configuration effective est enregistrée dans /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json. Il ne s'agit pas d'une copie du fichier Compose d'origine : c'est le contrat reproductible du LXC présent sur cet hôte." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "Une copie du contrat est conservée dans le conteneur, dans /.proxmenux/oci-record.json, et seul l'utilisateur root du conteneur peut la lire. Une sauvegarde du conteneur emporte donc toujours le contrat en vigueur au moment de la sauvegarde. Une seconde copie est conservée dans /etc/pve/priv/proxmenux/oci, un emplacement partagé par tous les nœuds du cluster et lisible uniquement par root sur l'hôte. Les deux copies sont écrites après chaque installation, mise à jour et modification." + } + ] + }, + { + "id": "manage", + "title": "Gérer les applications OCI installées", + "intro": "Cette option de l'écran principal liste les instances enregistrées avec leur application et image. Chacun d'eux est vérifié par rapport à son contrat avant toute action; un CT qui ne correspond plus à son enregistrement n'est ni modifié ni supprimé. La même mise à jour et le même éditeur sont proposés dans l'onglet Mises à jour de ProxMenux Monitor.", + "blocks": [ + { + "table": { + "headers": [ + "Option", + "Ce qui change", + "Ce qui est gardé" + ], + "rows": [ + [ + "Mettre à jour l'image avec la configuration enregistrée", + "Le rootfs est remplacé par l'image actuellement publiée sur le canal enregistré", + "Contrat, disques de conteneur, répertoires hôtes, réseau et appareils" + ], + [ + "Modifier : éditer les ressources, le réseau, les chemins et le GPU", + "L'éditeur s'ouvre avec le contrat actuel; le CT est reconstruit avec les modifications", + "Les données des disques de conteneur et des répertoires hôtes" + ], + [ + "Watchdog : redémarrer l'application lorsqu'elle plante", + "Détermine si l'application est relancée lorsqu'elle s'arrête d'elle-même. Rien n'est reconstruit", + "L'ensemble de l'installation ; seul le choix est sauvegardé dans le contrat" + ], + [ + "Supprimer: supprimer l'application et ses conteneurs", + "Le LXC, ou chaque membre d'une pile, et leurs contrats sont supprimés", + "Répertoires hôtes, avec leur contenu" + ] + ] + } + }, + { + "p": "Pour une application à plusieurs conteneurs, le menu propose Mettre à jour tous les conteneurs de l'application, Modifier les chemins et périphériques supplémentaires, Recréer tous les conteneurs avec leur configuration enregistrée, le watchdog et la suppression. Modifier ajoute ou supprime les chemins et périphériques supplémentaires du conteneur applicatif sans rien reconstruire et, pour Immich, change le composant qui exécute la reconnaissance. Recréer reconstruit chaque conteneur à partir de l'image utilisée lors de son installation, sans en chercher une plus récente." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Liste des applications OCI installées, avec les options de mise à jour, de modification et de suppression", + "caption": "Gérer les applications OCI installées." + } + } + ] + }, + { + "id": "update", + "title": "Une mise à jour transactionnelle", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Utilisateur", + "registry": "Registre OCI", + "update": "Mettre à jour l'instance", + "resolve": "Résoudre la balise", + "verify": "Vérifier l’archive et effectuer les contrôles préalables", + "backup": "Arrêter et sauvegarder le CT", + "import": "Importer le nouveau rootfs", + "reapply": "Appliquer à nouveau le contrat", + "healthy": "sain", + "failure": "défaillance", + "commit": "Contrat publié avec le nouveau digest", + "restored": "instance précédente restaurée" + } + } + }, + { + "list": { + "items": [ + "Lorsque le registre sert encore le digest installé, rien n'est téléchargé et l'instance n'est pas touchée.", + "La nouvelle image est vérifiée couche par couche avant l'arrêt du CT. Un téléchargement qui arrive endommagé est récupéré une deuxième fois ; une image déjà mise en cache qui échoue la vérification est à nouveau téléchargée.", + "Les paramètres modifiés dans Proxmox VE après l'installation (mémoire, swap, cœurs, limite et priorité du CPU, démarrage avec le nœud) sont conservés et intégrés au nouveau contrat.", + "L'interface LAN ajoutée par la suite Arr et les applications à plusieurs conteneurs (net1), ainsi que l'ordre de démarrage (startup), sont réappliqués tels quels au nouveau conteneur.", + "Toute autre différence entre le CT et son contrat arrête la mise à jour avant que le CT ne soit arrêté." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Avant", + "detail": "rootfs A\n/config mp0\n/media répertoire de l’hôte" + }, + { + "label": "Mise à jour", + "detail": "remplace uniquement\nle rootfs" + }, + { + "label": "Après", + "detail": "rootfs B\n/config mp0\n/media répertoire de l’hôte" + } + ] + } + }, + { + "p": "Avant tout arrêt, l'opération vérifie l'espace nécessaire à la sauvegarde : les données utilisées sur les volumes inclus, plus une marge. La sauvegarde est écrite à côté de l'enregistrement de l'application, sur le disque système de l'hôte. Si l'espace y est insuffisant, le menu demande un autre stockage de l'hôte acceptant les sauvegardes. La sauvegarde y est créée puis supprimée à la fin de l'opération. Ce choix est mémorisé pour l'application, y compris pour les mises à jour planifiées. Si aucun stockage n'a assez de place, l'opération est refusée et l'application reste inchangée. L'espace nécessaire à la nouvelle image est également vérifié avant son téléchargement." + } + ] + }, + { + "id": "recovery", + "title": "Récupération d'une opération interrompue", + "intro": "Une mise à jour et un changement utilisent une transaction persistante. Si le processus, le terminal ou le nœud est interrompu après l'arrêt du CT, l'opération n'est pas prise comme terminée.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Marqueur en attente", + "body": "À l'ouverture de Gérer les applications OCI installées, l'état enregistré indique que le remplacement n'a jamais été publié." + }, + { + "title": "Afficher l’état", + "body": "Affiche la phase qui a été atteinte sans changer de conteneur ou de données." + }, + { + "title": "Récupérer l'installation précédente", + "body": "Restaure la sauvegarde native vérifiée réalisée avant le remplacement, ainsi que le contrat précédent." + }, + { + "title": "Piles traitées comme une unité", + "body": "Pour une application multi-conteneurs, chaque membre est récupéré du même point de transaction, pas seulement celui sélectionné." + } + ] + } + }, + { + "calloutWarning": { + "title": "Les répertoires de l’hôte sont exclus du retour arrière", + "body": "La sauvegarde couvre le rootfs et les disques du conteneur qui y sont inclus. Un répertoire de l’hôte n’est pas rétabli à son état précédent, car d’autres LXC peuvent utiliser ses données." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: redémarrer une application qui s'arrête", + "intro": "Proxmox n'a pas de politique de redémarrage : quand le processus d'un conteneur applicatif se termine, le conteneur reste arrêté. Si le watchdog est activé, une application qui s'arrête d'elle-même est relancée. Il joue pour une application OCI le rôle de restart: unless-stopped dans un fichier Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Ce qui se passe", + "Avec le watchdog activé" + ], + "rows": [ + [ + "L’application plante ou son processus se termine", + "Le conteneur est relancé en quelques secondes" + ], + [ + "Il est arrêté ou éteint depuis Proxmox, depuis le Monitor ou avec pct", + "Il reste arrêté jusqu’à ce que quelqu’un le démarre" + ], + [ + "Une sauvegarde, une migration ou une opération ProxMenux travaille dessus", + "Rien n'est fait jusqu'à la fin" + ], + [ + "Il était déjà arrêté au démarrage de l’hôte", + "Il reste arrêté ; cela dépend de Démarrer avec Proxmox" + ], + [ + "Il s’arrête systématiquement juste après son démarrage", + "Pour une application qui s’arrête quelques secondes après son démarrage ou qui ne démarre pas, cinq tentatives sont effectuées, séparées par des délais de 30 secondes, 1, 2 et 5 minutes. Elle reste ensuite arrêtée et une notification est envoyée" + ] + ] + } + }, + { + "p": "L'installateur pose la question dans les deux modes d'installation et propose la valeur déclarée par la recette de l'application. Ce réglage peut ensuite être modifié dans Gérer les applications OCI installées ou avec le commutateur Watchdog du conteneur dans ProxMenux Monitor. Il s'applique à tous les conteneurs d'une application à plusieurs conteneurs. Un seul service de l'hôte, proxmenux-oci-watchdog.service, surveille les applications pour lesquelles il est activé ; son installation figure dans l'onglet Modifications d'Audit & Report." + }, + { + "calloutInfo": { + "title": "Ce que l'hôte ne peut pas dire", + "body": "Le code de sortie de l'application ne parvient pas à l'hôte. Un arrêt normal et un plantage sont donc indiscernables et entraînent tous deux un redémarrage, comme avec always et unless-stopped dans Docker. Un arrêt demandé en dehors de Proxmox, avec lxc-stop ou systemctl, ne laisse aucune tâche d'arrêt et est interprété comme un arrêt spontané." + } + } + ] + }, + { + "id": "restore", + "title": "Sauvegarde, restauration et un autre hôte", + "intro": "Une sauvegarde réalisée avec vzdump ou Proxmox Backup Server contient le conteneur, les disques du conteneur sélectionnés pour la sauvegarde et la copie de son contrat. Les éléments conservés par l'installation sur l'hôte n'en font pas partie. Gérer les applications OCI installées détecte les conteneurs restaurés sur un hôte dépourvu de leur contrat, après une nouvelle installation de Proxmox ou sur un autre hôte, et propose de les enregistrer à nouveau.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Restaurer les conteneurs dans Proxmox", + "body": "Depuis le stockage de sauvegarde, avec l’ID d’origine ou tout autre ID libre. Une application à plusieurs conteneurs nécessite tous ses conteneurs." + }, + { + "title": "Ouvrir la gestion des applications OCI installées", + "body": "Les conteneurs restaurés sont listés et la récupération est proposée. Le bouton Récupérer de l’onglet Mises à jour de ProxMenux Monitor ouvre la même récupération." + }, + { + "title": "Vérifier avant de changer", + "body": "La récupération vérifie que l'hôte a tout ce dont chaque application a besoin. Une application qui ne peut pas être récupérée entière est laissée comme elle a été trouvée, avec la raison." + }, + { + "title": "Enregistrer et démarrer", + "body": "Le contrat est enregistré sur cet hôte et les éléments que l'installation y conservait sont réécrits. Le démarrage des applications fait l'objet d'une question distincte, à laquelle il faut répondre Non si les conteneurs d'origine fonctionnent encore sur un autre hôte." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Boîte de dialogue qui énumère les conteneurs restaurés et propose de les récupérer", + "caption": "La récupération offerte lors de l'ouverture de la gestion des applications OCI installées." + } + }, + { + "table": { + "headers": [ + "Ce que l'hôte a gardé", + "Après la récupération" + ], + "rows": [ + [ + "Contrat d’instance", + "Enregistré à partir de la copie conservée dans le conteneur et vérifié par rapport à la configuration restaurée par Proxmox" + ], + [ + "Réseau privé d'une application multiconteneurs", + "Recréé avec le même pont et sous-réseau ; les adresses fixes des conteneurs ne changent pas" + ], + [ + "Ordre de démarrage d'une application multi-conteneurs", + "Le hookscript de dépendance et son contrat sont à nouveau installés ; les extraits sont activés sur le stockage local quand aucun stockage ne les accepte" + ], + [ + "Sysctls réseau et fichier host-monitor", + "Ecrit à nouveau en /etc/pve/proxmenux" + ], + [ + "Environnement d’exécution NVIDIA", + "Le hook d’un conteneur non privilégié est réinstallé. Un conteneur privilégié reçoit les fichiers de pilote de cet hôte à la place de ceux de son hôte d’origine" + ], + [ + "Rclone montage", + "Le hookscript et les programmes qui publient le montage sont à nouveau écrits, avec les mêmes vues sur l'hôte" + ], + [ + "Règle du pare-feu d'un moniteur hôte", + "Une nouvelle confirmation est demandée pour son port web et le sous-réseau du pont sur cet hôte" + ], + [ + "lost+found de chaque disque restauré", + "Supprimé s’il est vide ; une restauration le crée et certaines applications ne démarrent pas s’il figure dans leurs données" + ], + [ + "Disques restaurés sur un autre stockage", + "Le contrat est mis à jour pour refléter le stockage sur lequel ils se trouvent maintenant" + ] + ] + } + }, + { + "p": "Un conteneur restauré avec un autre ID conserve son application. Le contrat, son journal de console, ses paramètres sysctl réseau, le hookscript d'un montage Rclone et l'ordre de démarrage d'une application à plusieurs conteneurs sont enregistrés avec les ID des conteneurs sur cet hôte." + }, + { + "table": { + "headers": [ + "Ce qui empêche une récupération", + "Que faire" + ], + "rows": [ + [ + "Il manque un conteneur d'une application multiconteneurs", + "Restaurer aussi; l'application est récupérée entière" + ], + [ + "Le sous-réseau privé est déjà utilisé sur cet hôte", + "La récupération est annulée et rien n’est modifié : les adresses des conteneurs sont fixes et ne sont pas déplacées vers un autre sous-réseau" + ], + [ + "Un répertoire hôte n'existe pas", + "Monter ou créer avec ses données; une sauvegarde du conteneur n'inclut pas les répertoires hôtes" + ], + [ + "Un périphérique n'existe pas sur cet hôte", + "Connectez-le ou retirez-le du conteneur dans Proxmox" + ], + [ + "Une application avec NVIDIA sur un hôte sans pilote", + "Installez le pilote NVIDIA et le Container Toolkit" + ], + [ + "La sauvegarde a été effectuée avant l'existence de la copie du contrat", + "Le conteneur reste en tant que LXC ordinaire et n'est pas offert à nouveau" + ] + ] + } + }, + { + "calloutInfo": { + "title": "Un conteneur qui revient", + "body": "Un conteneur restauré sur lui-même à partir d'une ancienne sauvegarde, retourné à un instantané ou retourné d'un autre noeud porte le contrat de l'état dans lequel il est. Lorsqu'il est sélectionné pour une opération et que son contrat diffère de celui de cet hôte, l'opération ne démarre pas et le conteneur est offert pour récupération." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Résultat de la récupération avec le réseau privé, l’ordre de démarrage et les conteneurs enregistrés", + "caption": "Résultat d'une reprise, étape par étape." + } + } + ] + }, + { + "id": "remove", + "title": "Supprimer une application OCI", + "blocks": [ + { + "p": "Avant la confirmation, un résumé est établi à partir de la configuration réelle : les conteneurs supprimés, les données supprimées avec eux, le réseau privé libéré et les répertoires de l’hôte conservés. La réponse par défaut est Non, car les données des disques supprimés ne pourront pas être récupérées ensuite." + }, + { + "table": { + "headers": [ + "Ressource", + "À la suppression", + "Motifs" + ], + "rows": [ + [ + "rootfs et disques du conteneur", + "Supprimés", + "Ils appartiennent seulement au conteneur" + ], + [ + "Répertoire de l'hôte", + "Conservé, avec son contenu", + "D'autres applications peuvent l'utiliser" + ], + [ + "Fichiers hôtes du conteneur", + "Supprimés", + "Son journal de console, ses paramètres sysctl réseau, le hookscript du montage Rclone et son inscription dans l’onglet App de ProxMenux Monitor ne servent à aucun autre conteneur" + ], + [ + "Fichiers partagés par plusieurs installations", + "Supprimé avec la dernière installation qui les utilise", + "Le fichier de surveillance de l’hôte et le hookscript des dépendances des applications à plusieurs conteneurs" + ], + [ + "Contrat d’instance", + "Supprimé après une suppression réussie", + "Aucun CT ne lui est plus associé" + ], + [ + "Pont privé d'une pile", + "Relâché avec la pile", + "Il n'y a plus de membres pour se connecter" + ], + [ + "Réseau partagé par la suite Arr", + "Libéré lors de la suppression de la dernière application de la suite", + "Chaque application de la suite est indépendante et se supprime séparément" + ], + [ + "Un seul membre d'une pile", + "Non enlevé seul", + "Toute l'application est retirée, donc aucune pile n'est laissée incomplète" + ], + [ + "Un conteneur sur un autre noeud du cluster", + "Non enlevé", + "Son enregistrement et ses fichiers sur l’hôte se trouvent sur le nœud où il a été installé : il faut le supprimer là-bas, après l’y avoir remigré" + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Résumé de suppression avec les conteneurs, les données et les répertoires hôtes touchés", + "caption": "Le résumé de suppression, construit à partir de la configuration réelle." + } + } + ] + }, + { + "id": "archives", + "title": "Images téléchargées et espace hôte", + "blocks": [ + { + "p": "L’archive OCI sert à construire le rootfs ; le CT en cours d’exécution ne la lit pas. Après une installation ou une mise à jour, les archives téléchargées pendant cette opération sont listées avec leur taille et leur suppression est proposée. Les supprimer libère de l’espace sans affecter le conteneur ni ses données." + }, + { + "calloutInfo": { + "title": "Les mises à jour ne nécessitent pas l'archive", + "body": "Sans l'archive, une mise à jour résout le canal enregistré et télécharge le nouveau digest. Avec elle, une archive n'est réutilisée que lorsque sa référence et son intégrité correspondent à ce qui est demandé." + } + } + ] + }, + { + "id": "registry", + "title": "Registre et nettoyage", + "blocks": [ + { + "p": "À l'ouverture de Gérer les applications OCI installées, les restes des conteneurs absents de tous les nœuds du cluster sont d'abord supprimés : l'enregistrement conservé comme historique après une suppression depuis l'interface Proxmox, et les fichiers de l'hôte laissés par une suppression. Un enregistrement lié à une opération interrompue est conservé, car sa sauvegarde peut encore être nécessaire. Les volumes et répertoires de l'hôte ne sont jamais supprimés par déduction." + } + ] + }, + { + "id": "cluster", + "title": "Dans un cluster : migration et haute disponibilité", + "intro": "Un conteneur OCI est un LXC Proxmox ordinaire : il peut être migré ou géré par HA comme les autres, avec les mêmes limites. Les éléments nécessaires à son démarrage sont conservés à un emplacement accessible à tous les nœuds du cluster.", + "blocks": [ + { + "table": { + "headers": [ + "Partie", + "Sur un autre noeud du cluster" + ], + "rows": [ + [ + "Journal de console", + "Le conteneur crée /var/log/proxmenux/oci avant de démarrer, quel que soit le nœud qui l’exécute. Chaque nœud conserve le journal des démarrages qu’il a effectués." + ], + [ + "Paramètres sysctl réseau et surveillance de l’hôte", + "Conservés dans /etc/pve/proxmenux, partagé par tous les nœuds du cluster, afin qu’un conteneur migré les retrouve." + ], + [ + "Disques du conteneur", + "Proxmox les déplace avec le conteneur. HA nécessite que leurs données soient disponibles sur le nœud cible, via un stockage partagé ou une configuration de réplication prise en charge." + ], + [ + "Répertoires et périphériques de l'hôte", + "Les mêmes règles que n'importe quel LXC : un répertoire hôte doit exister sur le nœud cible et être marqué comme partagé, et un GPU, Coral ou NPU doit y être présent." + ], + [ + "Enregistrement ProxMenux", + "Le contrat reste sur le nœud où l'application a été installée, et chaque nœud lit la copie conservée dans /etc/pve/priv/proxmenux/oci. Une application à un seul conteneur est enregistrée sur son nœud de destination après migration lors de l'ouverture de Gérer les applications OCI installées ou du lancement d'une opération la concernant. Une application à plusieurs conteneurs y est proposée à la récupération, car son réseau privé doit être créé sur ce nœud." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Pas pour une disponibilité élevée", + "body": "Une application à plusieurs conteneurs relie ses membres par un pont privé du nœud sur lequel elle a été installée ; ses membres restent donc sur ce nœud. Un outil de surveillance de l’hôte, comme Glances en mode hôte ou Netdata, surveille le nœud qui l’exécute ; le déplacer reviendrait à surveiller un autre nœud." + } + }, + { + "p": "Une sauvegarde restaurée à l'extérieur du cluster obtient les fichiers de /etc/pve/proxmenux que le conteneur utilise lorsque l'application est récupérée." + } + ] + }, + { + "id": "channel", + "title": "Une étiquette évolutive n’est pas une mise à jour sans surveillance", + "blocks": [ + { + "p": "Le catalogue installe l’étiquette évolutive publiée par son responsable, mais le digest effectif est résolu et enregistré, puis modifié uniquement lors d’une mise à jour explicite avec contrôles préalables et retour arrière. ProxMenux Monitor compare le digest installé à celui publié par le registre et affiche une indication et une notification lorsqu’une nouvelle image est disponible." + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/monitor.json b/web/messages/fr/docs/oci-manager/monitor.json new file mode 100644 index 00000000..bf77e449 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "Conteneurs OCI dans ProxMenux Monitor | ProxMenux", + "description": "Ce que ProxMenux Monitor montre pour un conteneur installé par OCI Manager Apps : versions d'applications et d'images, nouvelles images, sortie console et terminal Proxmox VE." + }, + "header": { + "title": "Dans ProxMenux Monitor", + "description": "Un conteneur installé par OCI Manager Apps apparaît dans VMs & LXCs comme n'importe quel autre LXC. Sa fenêtre modale lit l'enregistrement d'installation : versions d'application et d'image, nouvelles images, sortie console et terminal.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "L'enregistrement est la source", + "body": "OCI manager Apps connaît l’application installée et sa provenance. Le Monitor lit cet enregistrement d’installation au lieu de rechercher l’application dans le conteneur. Ces informations restent donc disponibles que le conteneur fonctionne ou soit arrêté." + } + }, + { + "table": { + "headers": [ + "onglet", + "Ce qui change pour un conteneur OCI" + ], + "rows": [ + [ + "État", + "Un commutateur Watchdog sous Démarrer au démarrage: l'application est redémarrée quand elle s'arrête seule" + ], + [ + "Application", + "L'application est identifiée à partir du dossier et les mises à jour sont suivies par l'image" + ], + [ + "Mises à jour", + "L'image est mise à jour ou l'application modifiée, avec le même flux que le menu OCI" + ], + [ + "Montages", + "Disques de conteneurs et répertoires hôtes, avec leur utilisation" + ], + [ + "Journaux", + "Uniquement pour les conteneurs OCI : la sortie console de l'application" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "Application", + "intro": "L'onglet App offre l'application installée comme détection, avec le nom, le logo, le port et le schéma de l'enregistrement. L'enregistrer ouvre l'éditeur avec la méthode OCI image (installée par ProxMenux), qui ne nécessite aucune configuration.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Application", + "body": "La version de l'application à l'intérieur de l'image, lue à partir de l'image elle-même : son environnement ou son étiquette org.opencontainers.image.version. Cette information est indicative." + }, + { + "icon": "archive", + "title": "Image", + "body": "La date de construction et le digest de l'image installée. La mise à jour est décidée ici: le digest installé est comparé à celui que le registre publie aujourd'hui pour le même tag." + } + ] + } + }, + { + "list": { + "items": [ + "Lorsque le registre publie un nouveau digest, la carte affiche Nouvelle image avec sa date et son digest, même si la version de l'application à l'intérieur n'a pas changé: une reconstruction sur une base mise à jour est une mise à jour.", + "Lorsque les digests correspondent, la carte lit Version.", + "La carte relie au dépôt de l'image : son projet GitHub, ou sa page Docker Hub pour une image officielle.", + "Le lien d'accès utilise l'adresse LAN du conteneur, également pour le membre principal d'une application multi-conteneurs, qui a une deuxième adresse sur son réseau privé.", + "Le bouton Actualiser les données relit l’enregistrement et le registre des images. Les options de recherche ou d’enregistrement d’une autre application ne sont pas proposées : le conteneur contient exactement l’application de son enregistrement." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "Onglet App d'un conteneur OCI avec la version de l'application, l'image et le lien du dépôt", + "caption": "Onglet Application d’un conteneur installé par OCI manager Apps." + } + }, + { + "p": "La vérification s'effectue une fois par jour avec les vérifications de mise à jour du moniteur, et Actualiser les données l'exécute immédiatement. Une nouvelle image est envoyée sous forme de notification via les canaux configurés dans Notifications. La mise à jour est appliquée depuis l'onglet Mises à jour." + } + ] + }, + { + "id": "updates", + "title": "Mises à jour", + "intro": "Pour un conteneur installé par OCI manager Apps, l’onglet Mises à jour présente l’application avec son image installée et, si le registre en publie une, la nouvelle image, au même format que les autres applications. Les outils de mise à jour des paquets et applications d’un LXC ordinaire ne sont pas proposés.", + "blocks": [ + { + "table": { + "headers": [ + "Bouton", + "Ce qu'il ouvre" + ], + "rows": [ + [ + "Mise à jour", + "La mise à jour de Gérer les applications OCI installées pour ce conteneur, dans le terminal de Monitor. Elle peut être lancée avec ou sans nouvelle image ; sans nouvelle image, rien ne change. Pour une application multiconteneur, elle met à jour tous les membres." + ], + [ + "Modifier", + "L'éditeur de l'application (ressources, réseau, chemins et GPU). Dans une application multi-conteneurs, il ajoute ou supprime les chemins et périphériques supplémentaires du conteneur d'application et, dans Immich, modifie ce qui exécute la reconnaissance." + ], + [ + "Récupérer", + "Remplace Mise à jour quand une opération sur le conteneur a été interrompue, et ouvre sa récupération." + ], + [ + "Récupérer (conteneur restauré)", + "Affiché pour un conteneur restauré depuis une sauvegarde sans contrat sur cet hôte, sous Application OCI restaurée. Ouvre la récupération dans le terminal de Monitor ; Mettre à jour et Modifier apparaissent après son enregistrement." + ] + ] + } + }, + { + "p": "Les modifications externes, les répertoires hôtes et les applications multiconteneurs sont traités comme dans le menu OCI. Dans une application multi-conteneurs, les boutons et les options apparaissent uniquement sur le conteneur principal; les autres membres montrent leur image, une note qu'ils sont mis à jour à partir du conteneur principal et un bouton qui l'ouvre. Lorsque le terminal se ferme, l'image, l'enregistrement et les montages sont à nouveau lus." + }, + { + "table": { + "headers": [ + "Option", + "Comportement" + ], + "rows": [ + [ + "Conserver la sauvegarde prise avant la mise à jour", + "Chaque mise à jour sauvegarde le conteneur pour le restaurer si la mise à jour échoue. Avec cette option, la même sauvegarde est conservée dans le stockage choisi et apparaît parmi les sauvegardes du CT; sur Proxmox Backup Server une sauvegarde est écrite avant la mise à jour." + ], + [ + "Mises à jour programmées", + "L’image n’est mise à jour à l’heure choisie que si le registre en publie une nouvelle, avec la possibilité d’attendre qu’elle ait 1, 3, 7 ou 14 jours. Un conteneur modifié en dehors de ProxMenux est ignoré et signalé." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Journaux", + "intro": "L'onglet Logs n'apparaît que pour les conteneurs installés par l'OCI Manager Apps, entre Montages et Sauvegardes. Il montre la sortie standard et l'erreur du processus principal de l'image, la même sortie docker logs affiche pour un conteneur Docker.", + "blocks": [ + { + "list": { + "items": [ + "La sortie est conservée sur l'hôte dans /var/log/proxmenux/oci/VMID.console.log (mode 0600), dès le premier démarrage et sur les redémarrages, de sorte qu'il peut être lu avec le conteneur arrêté.", + "Le fichier est tourné à 10 MB, en conservant trois copies compressées (/etc/logrotate.d/proxmenux-oci).", + "Chaque démarrage du conteneur est marqué dans la sortie. L'onglet affiche la sortie depuis le dernier démarrage par défaut; L'historique complet montre également les précédents.", + "Lorsque l'application est supprimée, sa sortie est supprimée avec elle.", + "Les 100, 500 ou 1000 dernières lignes sont affichées. Quand le conteneur fonctionne, les nouvelles lignes sont suivies en direct ; faire défiler vers le haut met le suivi en pause et Suivre le reprend.", + "Un filtre n'indique que les lignes qui contiennent un texte, et Télécharger enregistre les lignes chargées.", + "Les codes de couleur sont supprimés et une ligne qu'une barre de progression redessine est affichée dans son état final.", + "L’onglet relit le fichier à chaque ouverture, sans mise en cache." + ] + } + }, + { + "calloutInfo": { + "title": "Identifiants du premier démarrage", + "body": "Les images qui impriment un mot de passe généré sur leur premier démarrage le laissent dans cette sortie, comme le fait docker logs. L'installateur le lit à partir de là pour le montrer dans son résumé." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Onglet Logs avec la sortie console d'un conteneur OCI, le sélecteur de ligne, le filtre et le bouton suivant", + "caption": "Sortie de console d'un conteneur OCI." + } + } + ] + }, + { + "id": "terminal", + "title": "Console Proxmox VE", + "blocks": [ + { + "p": "Une image OCI exécute son propre processus comme PID 1 et aucun service de connexion, de sorte que la console par défaut d'un LXC ouvrirait un terminal où rien ne répond. Les conteneurs installés par OCI Manager Apps sont créés avec cmode: shell : la console Proxmox VE ouvre un shell avec lxc-attach, l'équivalent de docker exec, avec l'application en cours d'exécution intacte." + }, + { + "list": { + "items": [ + "Le shell est celui attribué à root dans l’image par /etc/passwd. Si root n’a pas de shell ou si l’image n’en contient aucun, la console par défaut est conservée.", + "Le shell s’exécute en tant que root dans le conteneur, sans mot de passe. Qui peut l'ouvrir est décidé par la permission VM.Console de Proxmox VE.", + "Le terminal du moniteur entre dans le conteneur avec pct enter, qui fonctionne de la même manière." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Montages", + "blocks": [ + { + "p": "L'onglet Mounts énumère les disques de conteneur et les répertoires hôtes du conteneur. L'utilisation d'un disque de conteneur sur le stockage de blocs comme LVM-thin est lue à partir du système de fichiers du disque, qui est monté uniquement à l'intérieur du conteneur." + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/stacks.json b/web/messages/fr/docs/oci-manager/stacks.json new file mode 100644 index 00000000..5a1dfd4c --- /dev/null +++ b/web/messages/fr/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Applications multiconteneurs ProxMenux", + "description": "Comment OCI manager Apps transforme une application avec sa base de données et cache en LXC natifs coordonnés: plan, réseau privé, crochet de dépendance, validation et mises à jour transactionnelles." + }, + "header": { + "title": "Applications à plusieurs conteneurs", + "description": "Une application avec sa base de données, cache et d'autres services devient plusieurs LXC natifs coordonnés, installés et mis à jour comme un seul.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Une application pour l'utilisateur, plusieurs LXC pour Proxmox VE", + "body": "Une définition multi-conteneurs est une entrée du catalogue. L'installateur crée un LXC natif par service et garde leurs dépendances explicites. Immich, Nextcloud, Paperless-ngx et Tandoor sont installés de cette façon." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Plan de la pile", + "app": "Application LXC", + "appNet": "LAN + privé", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "privé" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. Le plan, avant l'existence d'un conteneur", + "blocks": [ + { + "p": "Aucun LXC n'est créé pendant que la définition Compose est interprétée. Un plan complet vient en premier, avec chaque membre, image, VMID, réseau, chemin, secret, ordre et bilan de santé. Si le plan n'est pas cohérent, l'installation ne démarre pas." + }, + { + "table": { + "headers": [ + "Élément du plan", + "Contenu", + "Vérification avant de créer quelque chose" + ], + "rows": [ + [ + "Membres", + "Application principale, base de données, cache, apprentissage automatique et autres dépendances", + "VMID uniques, rôles connus et exactement un membre principal" + ], + [ + "Images", + "Référence roulante, architecture et digest résolu de chaque service", + "Toutes existent, prennent en charge l’architecture et réussissent la vérification d’intégrité OCI" + ], + [ + "Réseau", + "Pont privé, sous-net, une adresse fixe par service et accès au réseau local pour le membre principal", + "Aucune collision avec des ponts ou sous-réseaux existants et aucune adresse répétée" + ], + [ + "Persistance", + "Disques de conteneur, répertoires hôtes, propriétaires et sauvegarde", + "Aucun chemin de chevauchement, stockage disponible et autorisations déclarées" + ], + [ + "Secrets", + "Mot de passe de la base de données, clés d'application et identifiants initiaux", + "Généré une fois et donné seulement aux membres qui les utilisent" + ], + [ + "Cycle de vie", + "Ordre de départ, ordre d'arrêt et bilan de santé par membre", + "L'application principale démarre en dernier et s'arrête en premier" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Création, membre par membre", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Réserve tous les VMID", + "body": "L'inventaire Proxmox VE et le registre des instances sont vérifiés. Un CT existant n'est pas adopté et un contrat qui appartient encore à une autre instance n'est pas réutilisé." + }, + { + "title": "Préparer chaque image", + "body": "Toutes les images sont résolues, téléchargées et vérifiées avant la création du premier conteneur." + }, + { + "title": "Créer chaque rootfs", + "body": "Les métadonnées OCI officielles sont importées, puis l’identité de l’instance ProxMenux et le rôle du membre sont ajoutés." + }, + { + "title": "Réseau privé", + "body": "Un bridge et un sous-net sont créés et chaque membre obtient son adresse fixe; seul le membre principal obtient également l'interface LAN." + }, + { + "title": "Persistance", + "body": "Chaque base de données et configuration obtient son propre disque conteneur ; seules les données destinées à être partagées utilisent des répertoires hôtes." + }, + { + "title": "Environnement", + "body": "Les points d’accès internes, les secrets partagés et les variables de service sont écrits. L’application accède à ses dépendances via leurs adresses privées réservées." + }, + { + "title": "Enregistrement", + "body": "Chaque membre enregistre sa configuration native, les changements rootfs que les mises à jour doivent reproduire et sa relation avec la pile." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Démarrer et vérifier chaque conteneur", + "intro": "Un LXC créé ne signifie pas un service prêt. Les dépendances commencent dans l'ordre et chacune passe un contrôle spécifique à son service.", + "blocks": [ + { + "table": { + "headers": [ + "Services", + "Vérification", + "Ce qu'il montre" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready dans le CT avec l'hôte, l'utilisateur et la base de données attendus", + "Le serveur accepte les connexions pour la base de données configurée" + ], + [ + "Redis / Valkey", + "redis-cli ou valkey-cli PING contre son adresse privée", + "Le broker écoute et répond" + ], + [ + "Immich apprentissage automatique", + "HTTP GET /ping, avec vérification de l’environnement d’exécution GPU sélectionné", + "Le service répond et l’accélération demandée n’a pas basculé vers le CPU" + ], + [ + "Nextcloud", + "GET /status.php avec installed=true, maintenance=false et needsDbUpgrade=false", + "Initialisation terminée sans aucune migration en attente" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP sur l'adresse LAN et le port réel du service", + "La façade et ses dépendances servent l’application" + ], + [ + "Application principale", + "L'extrémité de son modèle, par exemple /api/server/ping dans Immich", + "La pile entière fonctionne à travers l'application qui utilise les dépendances" + ] + ] + } + }, + { + "calloutWarning": { + "title": "En cours d’exécution ne signifie pas opérationnel", + "body": "L’état en cours d’exécution indique seulement que le processus LXC existe. Si le service propose une meilleure vérification, un contrôle exec ou HTTP avec délai maximal est utilisé. Si un membre s’arrête ou échoue à son contrôle, la pile n’est pas déclarée installée." + } + } + ] + }, + { + "id": "hook", + "title": "4. Le crochet de dépendance", + "blocks": [ + { + "p": "Le crochet est placé uniquement sur le conteneur principal d'une pile dépendante. Proxmox VE conserve le script comme un extrait et l'exécute comme le hookscript de ce CT. La recette de pile n'est pas écrite dans le script : elle vit dans un contrat privé séparé." + }, + { + "codeGrid": { + "items": [ + { + "title": "Configuration principale CT", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Contrat privé (exemple)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Source complète de proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "Le script est le même pour chaque pile. Les VMID, noms, types de vérifications et délais proviennent du contrat privé /etc/pve/priv/proxmenux-stack-VMID.json de chaque pile." + }, + { + "steps": { + "items": [ + { + "title": "Proxmox VE appelle pre-start", + "body": "Avant le démarrage du CT principal, le crochet fonctionne avec son VMID et la phase du cycle de vie." + }, + { + "title": "Un verrou par pile", + "body": "flock sur /run/lock/proxmenux-stack-VMID.lock empêche deux séquences de démarrage en même temps." + }, + { + "title": "Le contrat est lu et validé", + "body": "Un schéma connu, des dépendances numériques, des étiquettes, un contrôle exec, http ou running et un délai maximal positif sont requis." + }, + { + "title": "Dépendances dans l'ordre", + "body": "Chaque CT doit exister ; un CT arrêté est démarré, tandis qu’un CT déjà actif n’est pas redémarré." + }, + { + "title": "Attendre que le service soit prêt", + "body": "Le contrôle est effectué toutes les deux secondes, et le CT est également confirmé être toujours en cours d'exécution." + }, + { + "title": "Le principal CT démarre", + "body": "Lorsque chaque dépendance est prête, prédémarrage se termine et Proxmox VE démarre l'application." + } + ] + } + }, + { + "calloutInfo": { + "title": "Le crochet n'arrête pas les dépendances", + "body": "Les phases post-démarrage, pré-stop et post-stop ne font rien. L'arrêt du principal CT laisse tourner PostgreSQL, Redis, Valkey ou l'apprentissage automatique. Le crochet commande le démarrage; il ne transforme pas plusieurs LXC en un seul processus." + } + }, + { + "table": { + "headers": [ + "Type", + "Exemple", + "Démarrages suivants" + ], + "rows": [ + [ + "Pile dépendante", + "Immich, Nextcloud, application avec PostgreSQL", + "Le crochet du CT principal commence les dépendances et les attend" + ], + [ + "Suite d'application", + "Suite Arr", + "Aucun membre principal: chaque LXC suit son propre onboot" + ], + [ + "Application unique", + "Jellyfin", + "Proxmox VE commence directement ce LXC" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Contrôle de la pile dans son ensemble", + "blocks": [ + { + "list": { + "items": [ + "Chaque VMID existe, est unique et conserve l'identité d'instance attendue.", + "Chaque contrat appartient à la même pile, conserve son rôle et sa configuration native enregistrée.", + "Le membre principal est le dernier en start_order et le premier en stop_order.", + "Aucun membre, volume ou ajustement du rootfs ne manque.", + "Le crochet pointe vers l'extrait officiel, son contenu est inchangé et son contrat correspond à la recette de la pile.", + "Les images et les digests observés correspondent aux archives OCI préparées.", + "Les appareils, les profils GPU, les montages, les secrets et les paramètres correspondent encore aux contrats.", + "Une fois toutes les dépendances validées, le point d’accès de l’application principale vérifie l’intégration entre les membres." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Gestion d'une pile installée", + "intro": "Dans Gérer les applications OCI installées, chaque membre mène à toute la pile. Le menu propose Mettre à jour tous les conteneurs de l’application, Modifier les chemins et périphériques supplémentaires, Recréer tous les conteneurs avec leur configuration enregistrée et Retirer : supprimer l’application et ses conteneurs. La recréation reconstruit chaque conteneur depuis son image d’origine, identifiée par le digest enregistré, sans chercher d’image plus récente. Elle suit les mêmes étapes qu’une mise à jour et est proposée pour les piles pouvant être mises à jour.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Tout membre", + "body": "Le contrat du membre nomme le VMID principal et la liste complète des membres." + }, + { + "title": "La pile est reproductible", + "body": "Les identités, les contrats, le crochet, les adaptations et un replay coordonné pour cette recette sont validés." + }, + { + "title": "Images d'abord", + "body": "L'application n'est pas arrêtée avant que chaque digest ait été résolu, téléchargé et vérifié." + }, + { + "title": "Arrêter les membres et sauvegarder l’ensemble", + "body": "Les sauvegardes natives vérifiées sont prises avec la pile arrêtée, de sorte que l'application et les bases de données appartiennent au même moment." + }, + { + "title": "Mettre à jour et vérifier chaque membre", + "body": "Adaptation, réseau, montages, secrets et dispositifs sont appliqués à nouveau avant le contrôle de santé de chaque membre." + }, + { + "title": "Publier ou récupérer tout", + "body": "Les nouveaux contrats ne sont publiés que lorsque chaque membre passe. Si on échoue, chaque membre est rétabli." + } + ] + } + }, + { + "table": { + "headers": [ + "Modifier", + "Ce qui change", + "Ce qui est gardé" + ], + "rows": [ + [ + "Chemins et périphériques supplémentaires", + "Ils sont ajoutés ou retirés du conteneur d'application, qui redémarre. Rien n'est reconstruit", + "Les données de l'application, sa base de données et les autres conteneurs" + ], + [ + "Ce qui exécute la reconnaissance (Immich)", + "Le conteneur d'apprentissage de la machine prend l'image et les appareils du CPU ou d'un GPU de l'hôte. Toute l'application est arrêtée et mise à jour, comme dans une mise à jour", + "Le cache modèle, la bibliothèque et la base de données. Si quelque chose échoue, les conteneurs précédents et le choix précédent sont restaurés" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Une pile sans replay coordonné n'est pas mise à jour", + "body": "Si la préparation d'une pile ne peut pas être reproduite, la mise à jour est refusée avant l'arrêt de la pile. L'application continue à fonctionner comme elle est." + } + } + ] + }, + { + "id": "failure", + "title": "6. Quand quelque chose échoue", + "blocks": [ + { + "p": "Pendant une installation, une erreur s'arrête et enlève les conteneurs incomplets que l'opération a créés, et le pont privé si l'opération l'a créé. Une pile n'est pas publiée comme valide jusqu'à ce que la séquence entière se termine." + }, + { + "p": "Une mise à jour est transactionnelle: toutes les images sont préparées d'abord, puis la pile s'arrête, chaque membre est sauvegardé et la sauvegarde vérifiée, et seulement alors chaque rootfs est remplacé. Les membres commencent un par un avec leur bilan de santé; si on échoue, chaque membre est restauré à partir du même ensemble de sauvegardes, de sorte que la base de données et l'application n'appartiennent jamais à différents moments." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Préparer chaque image", + "stop": "Stop, membre principal d'abord", + "backup": "Sauvegarde vérifiée de chaque CT", + "replace": "Remplacer le rootfs", + "healthy": "Tous opérationnels ?", + "yes": "Oui", + "no": "Non", + "commit": "Publier les contrats", + "rollback": "Restaurer chaque membre" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "Ce qu'une pile ne comprend pas", + "blocks": [ + { + "list": { + "items": [ + "Une suite installée dans un flux, comme la suite Arr, n'est pas une pile dépendante : ses conteneurs n'ont pas d'ordre de départ entre eux.", + "Le réseau privé ne remplace pas l'authentification, TLS ou la configuration de chaque application.", + "La sauvegarde d'un LXC ne contient pas le pont, le crochet ou les contrats du reste de la pile.", + "Prowlarr, Sonarr ou Radarr ne reçoivent aucun indexeur, profil ou fournisseur de ProxMenux." + ] + } + } + ] + } + ] +} diff --git a/web/messages/fr/docs/oci-manager/storage-network.json b/web/messages/fr/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..8f7f3649 --- /dev/null +++ b/web/messages/fr/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Données, chemins et réseau | ProxMenux", + "description": "Comment OCI manager Apps conserve les données d’un conteneur OCI : disques du conteneur, répertoires de l’hôte, montages rclone, adresses et réseaux privés." + }, + "header": { + "title": "Données, chemins et réseau", + "description": "Ce qui se trouve dans le rootfs, ce qui subsiste après son remplacement, comment les données sont partagées entre conteneurs et comment chacun obtient son adresse.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "La persistance se décide avant la création du LXC", + "body": "Les volumes déclarés par l’image et son fichier Compose sont lus avant la création du conteneur. Chaque chemin à conserver après une mise à jour devient un point de montage indépendant du rootfs. Celui-ci ne contient donc que les fichiers appartenant à l’image et peut être remplacé." + } + } + ] + }, + { + "id": "questions", + "title": "Ce que demande l’installateur", + "intro": "Le modèle fournit les chemins nécessaires à l’application. Chacun est placé sur un disque du conteneur ou dans un répertoire de l’hôte. D’autres chemins peuvent être ajoutés avant le récapitulatif.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Chemins nécessaires", + "body": "/config, /data, les bibliothèques, les téléchargements et tous les volumes déclarés par l’application sont répertoriés." + }, + { + "title": "Emplacement", + "body": "Chaque chemin est placé sur un disque du conteneur ou dans un répertoire existant de l’hôte." + }, + { + "title": "Stockage et taille", + "body": "Un disque du conteneur est créé sur un stockage Proxmox VE avec la taille indiquée. L’installation par défaut utilise local-lvm. L’installation avancée demande le stockage de chaque chemin juste avant sa taille et propose celui du rootfs. Le disque apparaît sous le nom vm-VMID-disk-N et est rattaché comme mpN." + }, + { + "title": "Répertoire de l’hôte", + "body": "Le répertoire de l’hôte est indiqué par son chemin. S’il n’existe pas, il est créé avec pour propriétaire l’utilisateur de l’hôte correspondant à celui du conteneur." + }, + { + "title": "Chemins supplémentaires", + "body": "Avant l’installation, d’autres associations entre un chemin ou un volume de l’hôte et un chemin du conteneur peuvent être ajoutées." + }, + { + "title": "Récapitulatif", + "body": "La correspondance complète des chemins est affichée avant la création du CT et enregistrée dans le contrat de l’instance." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "Les deux emplacements persistants", + "blocks": [ + { + "table": { + "headers": [ + "Propriété", + "Disque du conteneur", + "Répertoire de l’hôte" + ], + "rows": [ + [ + "Dans la configuration", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Sauvegarde du conteneur (vzdump)", + "Inclus avec backup=1", + "Non inclus" + ], + [ + "Taille", + "Fixe ; extensible en redimensionnant le point de montage", + "L’espace libre du système de fichiers ou du dataset de l’hôte" + ], + [ + "Autres conteneurs", + "Monté uniquement par son propre conteneur", + "Le même répertoire peut être monté dans plusieurs conteneurs" + ], + [ + "Snapshots et restauration", + "Gérés par Proxmox VE avec le CT", + "Gérés sur le stockage de l’hôte" + ], + [ + "Lors de la suppression de l’application", + "Supprimé avec le conteneur", + "Conservé avec son contenu" + ], + [ + "Lors du déplacement du CT vers un autre nœud", + "Déplacé avec le CT", + "Le même chemin doit exister sur l’autre nœud" + ] + ] + } + }, + { + "p": "Le rootfs est réservé aux binaires et au contenu de l’image. Une mise à jour ou une recréation le remplace sans toucher à l’un ou l’autre type de point de montage." + } + ] + }, + { + "id": "example", + "title": "Exemple : un conteneur avec les deux emplacements", + "blocks": [ + { + "code": { + "title": "Jellyfin installé comme CT 151 sur local-lvm (extrait)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "Une mise à jour ou une recréation remplace uniquement le rootfs : mp0 conserve les utilisateurs, les bibliothèques et les paramètres, tandis que mp1 donne toujours accès aux mêmes médias. Restaurer la sauvegarde du CT rétablit /config. Le répertoire des médias est restauré, si nécessaire, depuis la sauvegarde du stockage de l’hôte." + }, + { + "flow": { + "nodes": [ + { + "label": "Contrat", + "detail": "/config" + }, + { + "label": "Emplacement", + "detail": "Disque du conteneur\nou répertoire de l’hôte" + }, + { + "label": "LXC", + "detail": "Toujours /config\npour l’application" + } + ], + "caption": "L’application voit le chemin déclaré par l’image ; seul son emplacement de stockage change." + } + } + ] + }, + { + "id": "shared", + "title": "Un répertoire de l’hôte, plusieurs conteneurs", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Répertoire de l’hôte", + "config": "Configuration propre" + } + } + }, + { + "p": "Chaque conteneur conserve sa configuration sur son propre disque. La bibliothèque ou les téléchargements se trouvent dans un unique répertoire de l’hôte, monté au même chemin interne dans chaque conteneur. Ainsi, le chemin dans lequel une application écrit est celui depuis lequel une autre lit." + } + ] + }, + { + "id": "rclone", + "title": "Stockage cloud avec l’application rclone", + "intro": "Outre son installation, la fiche rclone du catalogue propose Activer un montage dans un conteneur OCI rclone existant. Un stockage distant déjà créé et autorisé dans l’interface web de rclone est monté et rendu accessible sur l’hôte. D’autres conteneurs peuvent alors l’utiliser comme un répertoire de l’hôte.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Conteneur et stockage distant", + "body": "Le VMID du conteneur rclone, le nom exact du stockage distant et, éventuellement, un chemin à l’intérieur de celui-ci." + }, + { + "title": "Nom du montage et cache", + "body": "Le nom du montage et le mode du cache VFS : off, minimal, writes ou full (par défaut)." + }, + { + "title": "Vues exposées", + "body": "Une racine commune, /mnt/oci-shared par défaut, contient une vue en lecture-écriture dans /mnt/oci-shared/remotes/NAME et une vue en lecture seule dans /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Activation", + "body": "Après confirmation, le CT est arrêté, sa commande de démarrage et un hookscript Proxmox VE sont configurés, puis il est redémarré. L’opération attend que les deux vues soient montées sur l’hôte." + } + ] + } + }, + { + "calloutInfo": { + "title": "Si le montage ne s’active pas", + "body": "La configuration précédente du conteneur est restaurée, puis celui-ci est redémarré. Ainsi, un échec d’activation laisse rclone dans son état initial." + } + } + ] + }, + { + "id": "network", + "title": "Adresses et réseaux", + "intro": "Une application à un seul conteneur a besoin d’une adresse. Une application à plusieurs conteneurs nécessite aussi un réseau stable entre ses membres.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Application à un seul conteneur", + "body": "Le pont réseau est choisi, ainsi que DHCP ou une adresse fixe au format CIDR. Le LXC possède sa propre adresse et le récapitulatif affiche les URL complètes des services." + }, + { + "icon": "waypoints", + "title": "Application à plusieurs conteneurs", + "body": "Un sous-réseau libre est recherché et un pont privé persistant est créé. Chaque membre — application, base de données et cache — y reçoit une adresse fixe." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Adresse accessible\nservice principal uniquement" + }, + { + "label": "LXC principal", + "detail": "Web / API\nLAN + réseau privé" + }, + { + "label": "Réseau privé", + "detail": "PostgreSQL · Valkey · ML\nadresses fixes" + } + ], + "caption": "Les dépendances communiquent sur le réseau privé et n’ont pas d’adresse sur le LAN." + } + }, + { + "p": "Le contrat de la pile enregistre le pont, le sous-réseau, les adresses et les relations entre services. La mise à jour de l’application réutilise la même topologie pour tous les membres. ProxMenux Monitor ouvre le conteneur principal à son adresse LAN, et non à celle du réseau privé." + } + ] + }, + { + "id": "ownership", + "title": "Propriétaires des fichiers et répertoires", + "blocks": [ + { + "list": { + "items": [ + "Les nouveaux répertoires sont créés avec les UID et GID auxquels le LXC non privilégié est mappé.", + "Le propriétaire des répertoires existants n’est pas modifié récursivement.", + "Les sockets, fichiers système et chemins sensibles ne sont pas proposés comme répertoires génériques de l’hôte." + ] + } + } + ] + }, + { + "id": "backup", + "title": "Ce que contient une sauvegarde du conteneur", + "blocks": [ + { + "table": { + "headers": [ + "Élément", + "Dans la sauvegarde vzdump du CT", + "Lieu de conservation" + ], + "rows": [ + [ + "Rootfs OCI", + "Oui", + "Dans la sauvegarde du CT ; il peut aussi être recréé à partir de l’image et du contrat" + ], + [ + "Disque du conteneur avec backup=1", + "Oui", + "La sauvegarde du CT" + ], + [ + "Répertoire de l’hôte", + "Non", + "La sauvegarde du stockage de l’hôte" + ], + [ + "Contrat de l’instance", + "Oui, la copie conservée dans le CT", + "/.proxmenux/oci-record.json dans le CT ; les copies supplémentaires de l’hôte et du cluster sont conservées séparément" + ], + [ + "Application à plusieurs conteneurs", + "Chaque membre dans sa propre sauvegarde", + "Les sauvegardes de tous les membres, ainsi que le contrat de la pile et son pont réseau sur l’hôte" + ] + ] + } + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/architecture.json b/web/messages/it/docs/oci-manager/architecture.json new file mode 100644 index 00000000..3723eb23 --- /dev/null +++ b/web/messages/it/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "Come viene adattata un’immagine OCI | ProxMenux", + "description": "Dal repository dell’immagine e dal suo file Compose a un modello verificabile, un piano di distribuzione e un LXC nativo di Proxmox VE, senza Docker al suo interno." + }, + "header": { + "title": "Come viene adattata un’immagine OCI", + "description": "Dal repository dell’immagine e dal suo file Compose a un modello verificabile, un piano di distribuzione e un LXC nativo, senza installare Docker al suo interno.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "Il processo di adattamento", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Repository dell’immagine", + "converter": "Convertitore", + "template": "Modello JSON", + "blockers": "Nessun requisito bloccante?", + "no": "No", + "yes": "Sì", + "review": "Revisione / overlay", + "plan": "Piano di distribuzione", + "lxc": "LXC nativo" + } + } + }, + { + "p": "Il convertitore legge l’immagine e il file Compose pubblicati dal progetto e genera un modello JSON. Se alcuni requisiti non possono ancora essere adattati e impediscono l’installazione, il modello viene revisionato prima della pubblicazione nel catalogo. Un file di modifiche verificate, chiamato overlay, risolve questi problemi. Solo i modelli senza requisiti bloccanti vengono proposti per l’installazione." + } + ] + }, + { + "id": "template", + "title": "Cosa conserva il modello", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Identità dell’immagine", + "body": "Repository, tag ad aggiornamento continuo, architettura, digest risolto e revisione di origine." + }, + { + "icon": "braces", + "title": "Contratto del container", + "body": "Entrypoint, Cmd, ambiente, utente, directory di lavoro, segnale di arresto, porte e volumi." + }, + { + "icon": "layers", + "title": "Adattamento a Proxmox VE", + "body": "Risorse, sicurezza, punti di mount, dispositivi, sysctls, controlli di stato e adattamenti necessari per ciascuno, con la relativa motivazione." + }, + { + "icon": "shield", + "title": "Compatibilità", + "body": "Chiavi supportate, requisiti non adattati che impediscono l’installazione e stato di ogni verifica." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "OCI fornisce il processo, Compose l’ambiente", + "blocks": [ + { + "table": { + "headers": [ + "Origine", + "Esempio", + "Risultato nativo" + ], + "rows": [ + [ + "Metadati OCI", + "Entrypoint, Cmd, User", + "Proxmox VE li importa durante la creazione del CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Voci dell’ambiente LXC, mpN e devN" + ], + [ + "Profilo ProxMenux", + "GPU, controllo di stato, credenziali", + "Domande e adattamenti verificati" + ], + [ + "Utente", + "VMID, storage, rete", + "Il contratto dell’istanza" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Cosa succede durante l’installazione", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Risolvere il riferimento", + "body": "Viene interrogato il registro, viene scelta l’architettura dell’host e viene fissato il digest esatto a cui punta il tag ad aggiornamento continuo." + }, + { + "title": "Scaricare e verificare", + "body": "Skopeo scarica l’immagine come archivio OCI. Prima di creare qualsiasi risorsa, ogni livello viene verificato rispetto al suo digest e decompresso. Se il download è danneggiato, viene ripetuto una volta prima di interrompere l’installazione." + }, + { + "title": "Creare il filesystem", + "body": "pct create crea il rootfs a partire dall’archivio e conserva i metadati ufficiali del processo dell’immagine." + }, + { + "title": "Collegare le risorse", + "body": "Vengono applicati i volumi, la rete, l’ambiente, i dispositivi e i profili di sicurezza dichiarati." + }, + { + "title": "Console", + "body": "L’output della console del container viene conservato sull’host. La console di Proxmox VE apre una shell se l’immagine ne include una." + }, + { + "title": "Verificare", + "body": "Al primo avvio si attende che il container riceva un indirizzo e che il servizio risponda. In caso di errore, l’installazione non viene indicata come riuscita." + }, + { + "title": "Registrare la configurazione", + "body": "La configurazione effettiva viene salvata nel contratto dell’istanza usato per gli aggiornamenti e le ricreazioni." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Esempio: un’immagine con /config e /downloads", + "intro": "Una definizione Compose comune e la configurazione Proxmox VE che ne deriva. I percorsi attesi dall’applicazione non cambiano.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (estratto)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "mp0 è un secondo disco appartenente al container, denominato vm-VMID-disk-N sullo storage selezionato. Viene montato in /config e, con backup=1, incluso nel backup del container. mp1 non crea alcun disco: monta la directory dell’host /srv/downloads in /downloads all’interno dell’LXC. La porta 8080 non viene mappata: l’LXC ha un proprio indirizzo e il servizio risponde su quello." + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/custom-image.json b/web/messages/it/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..7d48a5b7 --- /dev/null +++ b/web/messages/it/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Installare un’immagine non presente nel catalogo | ProxMenux", + "description": "Adatta un’immagine OCI da un file Compose, un URL, un comando docker run o un riferimento all’immagine e verifica cosa può riprodurre ProxMenux prima di installarla." + }, + "header": { + "title": "Installa un’immagine non presente nel catalogo", + "description": "L’immagine viene adattata dal suo file Compose, da un comando docker run o dal solo riferimento. Il risultato viene mostrato per la revisione prima di creare qualsiasi LXC.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Lo stesso convertitore e lo stesso installer", + "body": "L’opzione Installa un’immagine non presente nel catalogo usa il convertitore e l’installer dei modelli del catalogo. La differenza è che il contratto viene generato al momento dalla definizione fornita e verificato prima di creare qualsiasi LXC." + } + } + ] + }, + { + "id": "sources", + "title": "Come viene descritta l’immagine", + "intro": "La prima schermata chiede come viene descritta l’immagine. Quattro opzioni forniscono una definizione da adattare, una quinta usa solo il nome dell’immagine e altre tre leggono un oci-compose già scritto.", + "blocks": [ + { + "table": { + "headers": [ + "Opzione", + "Cosa legge" + ], + "rows": [ + [ + "Incolla il file Compose nel terminale", + "Incolla il contenuto YAML nel terminale e termina con Ctrl+D." + ], + [ + "Leggi il file Compose da questo host", + "Un percorso sul nodo, /root/docker-compose.yml per impostazione predefinita. Il file deve esistere e non superare 256 KiB." + ], + [ + "Scarica il file Compose da un indirizzo", + "Un indirizzo http:// o https:// che fornisce il contenuto YAML grezzo. Vengono scaricati al massimo 256 KiB." + ], + [ + "Incolla il comando docker run nel terminale", + "Il comando pubblicato dal progetto. Vengono adattati porte, ambiente, volumi, dispositivi, capability, utente, memoria condivisa e le altre opzioni supportate." + ], + [ + "Solo il riferimento all’immagine, senza file Compose", + "Un riferimento come ghcr.io/user/application:latest. Viene interrogato il registro e vengono conservati i metadati OCI dell’immagine." + ], + [ + "Incolla un oci-compose nel terminale", + "Incolla il JSON nel terminale e termina con Ctrl+D. Non viene convertito: viene verificato e installato così come è scritto." + ], + [ + "Leggi un oci-compose da questo host", + "Un file JSON sul nodo, /root/oci-compose.json per impostazione predefinita, di massimo 2 MiB." + ], + [ + "Scarica un oci-compose da un indirizzo", + "Un indirizzo http:// o https:// che fornisce il file JSON. Vengono scaricati al massimo 2 MiB." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Menu per scegliere come descrivere un’immagine non presente nel catalogo", + "caption": "Modi per descrivere un’immagine non presente nel catalogo." + } + } + ] + }, + { + "id": "reference-only", + "title": "Cosa fornisce il solo riferimento all’immagine", + "intro": "L’immagine contiene i metadati del processo, ma non tutto l’ambiente che normalmente aggiunge un file Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Letto dall’immagine", + "Non presente nell’immagine", + "Conseguenza" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir e l’ambiente incorporato", + "Variabili presenti solo nella documentazione", + "Vengono aggiunte durante l’installazione o con Modifica" + ], + [ + "Volumi dichiarati dall’immagine", + "Directory dell’host indicate solo nel file Compose", + "I percorsi aggiuntivi vengono inseriti prima dell’installazione" + ], + [ + "Porte EXPOSE", + "L’URL, il protocollo o il controllo funzionale del servizio", + "Vengono confermati la porta e il metodo di verifica del servizio" + ], + [ + "Architetture e digest nel registro", + "Dispositivi, privilegi o dipendenze esterne", + "Non vengono abilitati senza una definizione che li richieda" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Verifica prima di installare", + "intro": "Dopo aver adattato la definizione, ProxMenux mostra come l’ha interpretata prima di chiedere VMID, risorse o storage.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analizza", + "body": "Vengono letti servizi, immagine, porte, percorsi, ambiente, dispositivi, sicurezza e controllo di stato." + }, + { + "title": "Interroga il registro", + "body": "L’immagine deve esistere nel registro e vengono elencate le architetture pubblicate. Per un registro privato viene usato l’account salvato per quel registro." + }, + { + "title": "Elenca ciò che non viene applicato", + "body": "Vengono elencati label, reti Docker, impostazioni Swarm e altre chiavi che non hanno effetto su un LXC." + }, + { + "title": "Blocca le opzioni senza equivalente sicuro", + "body": "Le chiavi senza un equivalente sicuro compaiono in Opzioni senza equivalente e impediscono di proseguire con l’installazione. Non vengono ignorate senza avviso." + }, + { + "title": "Richiedi i segreti", + "body": "Le variabili il cui nome indica una password, un token o una chiave vengono richieste come dati sensibili durante l’installazione." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Riepilogo di come ProxMenux ha interpretato il file Compose", + "caption": "Riepilogo mostrato prima che l’installer chieda VMID e risorse." + } + }, + { + "p": "Dopo aver accettato il riepilogo, il flusso è quello del catalogo: nome, modalità predefinita o avanzata, VMID, CPU, memoria, rete, avvio con il nodo, percorsi persistenti e aggiuntivi, dispositivi compatibili e riepilogo finale." + } + ] + }, + { + "id": "examples", + "title": "Esempi di input", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "Un oci-compose scritto a mano", + "intro": "Un oci-compose è il file JSON da cui viene installata un’applicazione. Il catalogo ne conserva uno per ogni applicazione. Lo stesso formato può essere scritto a mano e installato con questa opzione.", + "blocks": [ + { + "p": "In un file scritto a mano l’autore decide immagine, porte, variabili, volumi e domande dell’installazione. ProxMenux completa il resto durante la lettura. Il formato può esprimere ciò che Compose non prevede: il testo di una domanda, una password generata all’installazione, un profilo GPU, il proprietario di ciascun volume, il controllo del primo avvio o le credenziali mostrate alla fine." + }, + { + "steps": { + "items": [ + { + "title": "Verifica", + "body": "I file con errori non vengono installati. Ogni errore indica il campo interessato; se il JSON non è valido, vengono segnalate riga e colonna." + }, + { + "title": "Rivedi", + "body": "Un file valido mostra cosa installa: immagine, indirizzo web, porte, percorsi persistenti, variabili richieste dall’installer e file che verranno scritti." + }, + { + "title": "Installa", + "body": "L’installazione è la stessa delle applicazioni del catalogo, con impostazioni predefinite o avanzate." + } + ] + } + }, + { + "calloutWarning": { + "title": "Un file che determina i privilegi", + "body": "Un oci-compose stabilisce cosa viene montato nel container e con quali privilegi. La revisione lo mostra prima dell’installazione: installa solo file provenienti da fonti attendibili." + } + }, + { + "p": "Il README del motore OCI descrive il formato blocco per blocco con due esempi completi. Anche il record di un’installazione può essere letto nello stesso modo: viene usato solo il modello originale, senza alcuna delle risposte di quell’installazione." + } + ] + }, + { + "id": "private-registries", + "title": "Immagini in registri privati", + "intro": "Un’immagine in un registro privato, come un pacchetto privato su GitHub o un repository privato su Docker Hub, viene letta con un account salvato in ProxMenux.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Il registro nega l’accesso all’immagine", + "body": "Quando il registro indica che l’immagine richiede un account, la revisione propone di accedere a quel registro e legge di nuovo l’immagine." + }, + { + "title": "Accedi", + "body": "Un nome utente e una password o un token autorizzato a scaricare l’immagine. Il registro li verifica e vengono salvati solo se accettati." + }, + { + "title": "Installa e mantieni aggiornato", + "body": "L’account viene usato per scaricare l’immagine durante l’installazione e anche per aggiornamenti, Modifica, Ricrea, watchdog e ricerca degli aggiornamenti del Monitor, su qualsiasi nodo del cluster." + } + ] + } + }, + { + "p": "Account dei registri, nel menu principale di OCI manager Apps, elenca gli account salvati per registro e utente, senza mostrarne i segreti. Da ogni account puoi verificare, scaricare o installare un’immagine e aggiornare o rimuovere le credenziali." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io): l’utente è l’account GitHub e la password è un token di accesso personale con il permesso read:packages.", + "Docker Hub (docker.io): l’utente è l’account Docker Hub e la password è un token di accesso con permesso di lettura.", + "Altri registri, anche con una porta propria come registry.example.com:5000: nome utente e password o token forniti da quel registro." + ] + } + }, + { + "calloutInfo": { + "title": "Dove vengono salvati gli account", + "body": "In /etc/pve/priv/proxmenux/registry-auth.json, leggibile solo da root, nella stessa cartella in cui Proxmox conserva le password degli storage. /etc/pve è condiviso tra tutti i nodi del cluster, quindi un account aggiunto su un nodo viene usato su tutti. Il backup dell’host include il file e la disinstallazione di ProxMenux lo rimuove da tutti i nodi." + } + } + ] + }, + { + "id": "limits", + "title": "Definizioni che non vengono installate", + "blocks": [ + { + "list": { + "items": [ + "Un Dockerfile senza immagine pubblicata: prima occorre creare l’immagine e pubblicarla in un registro OCI.", + "Un file Compose con più servizi: viene bloccato perché questa opzione installa un solo servizio per container, anche se più servizi usano la stessa immagine.", + "configs, segreti esterni o formati di dispositivi che non possono essere adattati senza ambiguità.", + "Sostituzioni della shell come $(command): occorre inserire direttamente il valore risultante.", + "Opzioni docker run non riconosciute e --env-file: usa invece un file Compose con valori espliciti." + ] + } + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/hardware.json b/web/messages/it/docs/oci-manager/hardware.json new file mode 100644 index 00000000..8a60aa10 --- /dev/null +++ b/web/messages/it/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Dispositivi e accelerazione | ProxMenux", + "description": "Come OCI manager Apps passa GPU, NVIDIA, Coral, USB, FUSE e altri dispositivi a un contenitore OCI come risorse native Proxmox VE convalidate." + }, + "header": { + "title": "Dispositivi e accelerazione", + "description": "I dispositivi GPU, NVIDIA, Coral, USB, FUSE e a blocchi diventano risorse native convalidate Proxmox VE del contenitore.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "Il dispositivo di cui l'applicazione ha bisogno, non l'intero host", + "blocks": [ + { + "p": "Un dispositivo richiesto dal file Compose o dal profilo dell’applicazione viene configurato come voce devN specifica o come montaggio LXC. Richiedere una GPU, un dispositivo USB o un acceleratore Coral non rende il container privilegiato." + }, + { + "flow": { + "nodes": [ + { + "label": "Inventario host", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "produttore e\npermessi verificati" + }, + { + "label": "LXC", + "detail": "stesso dispositivo\nGID effettivo" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "Da dove proviene la richiesta del dispositivo", + "blocks": [ + { + "table": { + "headers": [ + "Fonte", + "Cosa si legge", + "Cosa fa l'installatore" + ], + "rows": [ + [ + "Docker Compose", + "devices, group_add, deploy.resources e NVIDIA richieste", + "Ogni esigenza diventa una richiesta di dispositivo mostrata per la revisione" + ], + [ + "Profilo del catalogo", + "Il supporto effettivo dell’applicazione per GPU, Coral, OpenCL, USB o FUSE", + "Solo le opzioni convalidate per quell'immagine sono offerte" + ], + [ + "Metadati e documentazione dell’immagine", + "VA-API, Selkies, mod LinuxServer o runtime NVIDIA", + "Vengono aggiunte le variabili documentate e la preparazione" + ], + [ + "Selezione utente", + "CPU solo, Intel/AMD, OpenCL, NVIDIA o un dispositivo opzionale", + "La selezione viene memorizzata nel contratto di istanza" + ] + ] + } + }, + { + "calloutWarning": { + "title": "I dispositivi rilevati non vengono collegati automaticamente", + "body": "Viene creato un inventario dell’host, ma solo i dispositivi dichiarati dal file Compose o da un profilo compatibile sono offerti e allegati. Una GPU, USB dongle o Coral presente sull'host non è esposto ad ogni LXC." + } + } + ] + }, + { + "id": "identify", + "title": "Come viene identificato il dispositivo host", + "intro": "Prima della modifica del LXC, il dispositivo viene letto sull'host e abbinato al profilo scelto.", + "blocks": [ + { + "table": { + "headers": [ + "Tipo", + "Identità", + "Validazione" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* e /sys/class/drm/NODE/device/vendor", + "Un dispositivo a caratteri con il fornitore 0x8086 (Intel) o 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "Il nodo di rendering, più /dev/kfd quando il profilo ne ha bisogno", + "Esistenza, tipo, fornitore, autorizzazioni e compatibilità dichiarata" + ], + [ + "AMD ROCm", + "Il nodo di rendering, /dev/kfd e la generazione indicata dal driver di calcolo in /sys/class/kfd/kfd/topology/nodes/*/properties", + "Una generazione per cui l’immagine ROCm dell’applicazione include kernel di calcolo — le GPU RDNA2 indicate e quelle più recenti — viene proposta così com’è. Le altre GPU di queste famiglie, come Radeon 680M e 780M, sono proposte come sperimentali e non sono mai preselezionate: ROCm non le supporta ufficialmente, l’applicazione le presenta come la generazione della rispettiva famiglia (HSA_OVERRIDE_GFX_VERSION) e l’installatore chiede conferma prima di scaricare l’immagine più grande. Una GPU precedente, come la grafica integrata di un Ryzen 5000U, non viene proposta e Immich mantiene il riconoscimento sulla CPU." + ], + [ + "NVIDIA", + "nvidia-smi e nvidia-container-cli", + "GPU, UUID, bus PCI, versione driver, Toolkit, nodi /dev/nvidia*, binari e librerie" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N e il suo collegamento in /sys/dev/char/MAJOR:MINOR", + "Nodo a caratteri, numeri major/minor, proprietario, GID e permessi" + ], + [ + "USB e seriale", + "/dev/ttyUSB*, /dev/ttyACM* o /dev/bus/usb/BBB/DDD", + "Nodo di carattere; per USB anche produttore, prodotto e seriale quando sysfs li pubblica" + ], + [ + "KVM, TUN, FUSE, video e SCSI generici", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN o /dev/sgN", + "Percorso supportato, tipo nodo e permessi effettivi" + ], + [ + "Unità ottica", + "/dev/srN", + "Un dispositivo a blocchi" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Cosa succede durante l'installazione", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Il modello offre i suoi profili", + "body": "Ad esempio solo CPU, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL o NVIDIA. Le opzioni appartengono all'immagine, non a un menu comune." + }, + { + "title": "Viene scelto un profilo", + "body": "Definisce i nodi del dispositivo, ambiente, mod o runtime di cui l'applicazione ha bisogno." + }, + { + "title": "Si propone un percorso", + "body": "Per DRM, /dev/dri/renderD128, che può essere modificato su un host con diversi nodi di rendering. Per USB o seriale, viene selezionato il nodo specifico." + }, + { + "title": "Validazione", + "body": "Esistenza, tipo, fornitore consentito, autorizzazioni e GID sono controllati. Un errore blocca l'operazione." + }, + { + "title": "Il contratto è scritto", + "body": "Percorso, modalità, GID, accesso scrittura e profilo sono registrati per aggiornamenti e ricreazioni." + }, + { + "title": "Collega e verifica", + "body": "pct set aggiunge l'ingresso devN e l'accesso viene quindi controllato all'interno del LXC. Le immagini LinuxServer vengono anche controllate come utente abc." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Come appare nella configurazione LXC", + "intro": "Valori illustrativi: dev0 e dev1 sono gli slot liberi assegnati da Proxmox VE; renderD128, apex_0 e il GID dipendono dall’hardware del nodo.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Un dispositivo USB specifico", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "Il GID è letto con stat sull'host e scritto all'ingresso devN; i gruppi render e video non si presume abbiano un numero fisso. Il dispositivo mantiene lo stesso percorso /dev all'interno del LXC, dove i propri meccanismi dell'applicazione lo cercano." + } + ] + }, + { + "id": "profiles", + "title": "Profili che un’immagine può offrire", + "blocks": [ + { + "table": { + "headers": [ + "Profilo", + "Configurazione risultante", + "Offerto quando" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "Nodo di rendering /dev/dri", + "L'applicazione supporta l'accelerazione video" + ], + [ + "OpenCL", + "Nodo di rendering, /dev/kfd quando necessario e mod ufficiale", + "L'immagine o il profilo lo documentano" + ], + [ + "NVIDIA", + "Dispositivi driver e librerie dell'host", + "L’host dispone di un driver funzionante e di NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 o il bus USB", + "Il profilo dichiara il supporto Coral (Frigate)" + ], + [ + "USB, seriale, FUSE", + "Un singolo dispositivo, un albero convalidato o una funzione LXC", + "Il contratto lo chiede" + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Requisito del nodo: NVIDIA Container Toolkit", + "body": "Un driver funzionante su Proxmox VE non è sufficiente per dare un NVIDIA GPU ad un'immagine OCI. OCI manager Apps utilizza nvidia-container-cli, dal NVIDIA Container Toolkit, per identificare i dispositivi e per ottenere i binari e le librerie che corrispondono al driver caricato." + } + }, + { + "p": "L’installatore NVIDIA di ProxMenux installa NVIDIA Container Toolkit dal repository ufficiale NVIDIA insieme al driver. Controlla i quattro pacchetti, convalida nvidia-container-cli e registra il risultato nel registro delle modifiche di Audit & Report." + }, + { + "p": "Questi due comandi sull'host mostrano se il driver e il Toolkit sono disponibili:" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "Su un host dove il driver è stato installato con altri mezzi, il Toolkit è installato dal repository ufficiale stabile:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "L’inventario utilizzato da OCI manager Apps è l'output di:" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "La configurazione di runtime Docker non è coinvolta", + "body": "I container sono LXC nativi e non usano un demone Docker, quindi nvidia-ctk runtime configure --runtime=docker non interviene: ProxMenux interroga direttamente nvidia-container-cli e configura dispositivi e montaggi LXC. I comandi e le piattaforme supportate sono mantenuti nella guida di installazione di NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventario dal driver", + "body": "nvidia-container-cli elenca i nodi del dispositivo, i binari, il firmware e le librerie del driver installato." + }, + { + "icon": "refresh", + "title": "Nessuna versione fissa", + "body": "Il modello non nomina i file della libreria. Il profilo viene generato dall'host corrente." + }, + { + "icon": "shield", + "title": "Montaggi in sola lettura", + "body": "Le librerie ospitanti sono montate in sola lettura invece di essere copiate nel contenitore." + }, + { + "icon": "hardDrive", + "title": "Modifiche dei driver", + "body": "Dopo che un driver cambia l'inventario viene generato nuovamente prima dell'avvio dei LXC colpiti." + } + ] + } + }, + { + "code": { + "title": "Risultati NVIDIA (semplificati)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Passare solo /dev/nvidia0 non è sufficiente. I componenti user-space del driver caricato sono montati in sola lettura e nvidia-smi poi funziona all'interno del LXC per confrontare GPU, UUID, PCI bus e versione con l'inventario host." + } + ] + }, + { + "id": "usb", + "title": "USB, seriale e USB Coral", + "blocks": [ + { + "calloutWarning": { + "title": "Numerazione USB può cambiare", + "body": "Un percorso come /dev/bus/usb/003/004 può cambiare quando il dispositivo viene ricollegato o l'host si riavvia. Il profilo registra il fornitore, il prodotto e la serie quando sono disponibili, ma un nuovo indirizzo del bus non viene rimappato automaticamente." + } + }, + { + "p": "Una periferica è data dal suo nodo specifico: /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 o /dev/bus/usb/BBB/DDD. Passare l'intero /dev non è accettato. Coral è offerto solo a applicazioni il cui profilo lo dichiara." + } + ] + }, + { + "id": "trees", + "title": "Alberi dei dispositivi e funzionalità LXC", + "blocks": [ + { + "table": { + "headers": [ + "Richiesta", + "Configurazione risultante", + "Ambito" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd o /dev/bus/usb", + "Ogni nodo di carattere dell'albero ottiene il proprio ingresso devN con la modalità host e GID", + "Solo l'albero richiesto, non il resto di /dev" + ], + [ + "/dev/fuse", + "Il nodo e, quando il profilo ne ha bisogno, la funzione fuse=1", + "FUSE da solo non rende disponibili i montaggi agli altri LXC" + ], + [ + "/dev/net/tun", + "Un ingresso devN nello stesso percorso all'interno del LXC", + "La configurazione VPN o di rete rimane nell’applicazione" + ], + [ + "/dev/kvm", + "Una voce valida devN", + "Offerto solo quando il contratto lo chiede" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Conferma per livello di rischio", + "blocks": [ + { + "p": "Dispositivi concreti, privilegio facoltativo, privilegio richiesto, allentamento di AppArmor o seccomp e accesso al namespace PID host sono trattati come casi separati, non sotto una generica etichetta privilegiata. Ogni opzione con un rischio viene spiegata e confermata durante l'installazione." + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/index.json b/web/messages/it/docs/oci-manager/index.json new file mode 100644 index 00000000..62358d6c --- /dev/null +++ b/web/messages/it/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Esegui immagini OCI ufficiali come container LXC nativi di Proxmox VE, con dati persistenti, accesso ai dispositivi, applicazioni composte da più container e aggiornamenti transazionali." + }, + "header": { + "title": "OCI manager Apps", + "description": "Le immagini OCI ufficiali vengono eseguite come container LXC nativi di Proxmox VE. L’immagine rimane quella pubblicata dal manutentore; ProxMenux ricrea l’ambiente che Docker Compose avrebbe predisposto intorno ad essa.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "Un’immagine OCI, un LXC nativo", + "body": "Nei container non viene eseguito Docker Engine. Proxmox VE importa il filesystem dell’immagine e i suoi metadati OCI. L’applicazione diventa il processo principale di un LXC nativo, gestito con gli stessi strumenti degli altri container del nodo." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Origine", + "detail": "Immagine OCI\nCompose e documentazione" + }, + { + "label": "ProxMenux", + "detail": "Contratto JSON\nvalidazione e pianificazione" + }, + { + "label": "Proxmox VE", + "detail": "LXC nativo\nvolumi e dispositivi" + } + ], + "caption": "Si usa l’applicazione originale: il suo ambiente viene descritto in modo dichiarativo e riprodotto." + } + } + ] + }, + { + "id": "where", + "title": "Dove si trova", + "intro": "Sul nodo Proxmox, come root, apri OCI manager Apps (Technology Preview) dal menu principale di ProxMenux. La prima schermata offre:", + "blocks": [ + { + "list": { + "items": [ + "Cerca applicazioni: cerca per nome nell’intero catalogo.", + "Tutte le applicazioni: mostra il catalogo completo e il numero di applicazioni.", + "Gestisci le applicazioni OCI installate: aggiorna, modifica, rimuovi o recupera le applicazioni già installate.", + "Account dei registri: gestisci gli account usati per installare e aggiornare immagini da registri privati.", + "Installa un’immagine non presente nel catalogo: adatta un file Compose, un comando docker run o un riferimento a un’immagine.", + "Le categorie del catalogo, ciascuna con il numero di applicazioni." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Schermata principale di OCI manager Apps con ricerca, catalogo completo, gestione delle applicazioni, immagini fuori catalogo e categorie.", + "caption": "Schermata principale di OCI manager Apps." + } + }, + { + "p": "Selezionando un’applicazione vengono mostrati la descrizione, l’immagine usata e due modalità di installazione. Installa con le impostazioni predefinite chiede solo le scelte essenziali: storage, posizione dei dati, indirizzo e avvio. Installa con le impostazioni avanzate presenta tutte le decisioni in ordine: VMID e risorse, storage per ogni percorso, bridge di rete e dispositivi." + }, + { + "p": "Le applicazioni provate sul campo mostrano Verificato in questa schermata e un ✓ negli elenchi, sia per i test del laboratorio ProxMenux sia per quelli della community." + } + ] + }, + { + "id": "beta", + "title": "Perché mantiene l’etichetta Technology Preview", + "blocks": [ + { + "p": "OCI manager Apps mantiene l’etichetta Technology Preview assegnata da Proxmox VE ai container applicativi creati da immagini OCI, il cui supporto è stato introdotto in Proxmox VE 9.1. L’etichetta riguarda la tecnologia su cui si basa OCI manager Apps, non il motore di ProxMenux: il comportamento di questi container può ancora cambiare tra le versioni di Proxmox VE. Rimarrà finché Proxmox VE non considererà stabile tale supporto." + }, + { + "p": "Il motore di OCI manager Apps viene testato nel laboratorio ProxMenux: installazione, aggiornamento, modifica, ricreazione, backup, ripristino, migrazione e watchdog. Ogni applicazione viene verificata separatamente. Quelle provate sul campo mostrano Verificato e un ✓ negli elenchi." + } + ] + }, + { + "id": "translation", + "title": "Come Docker descrive i requisiti e come li realizza OCI manager Apps", + "blocks": [ + { + "table": { + "headers": [ + "Requisito", + "Come lo esprime Docker", + "Come lo realizza OCI manager Apps" + ], + "rows": [ + [ + "Eseguire l’applicazione", + "image, entrypoint, command", + "Il rootfs OCI e il processo principale dell’LXC" + ], + [ + "Conservare la configurazione", + "volumes: /config", + "Un volume persistente mpN incluso nel backup, oppure una directory dell’host." + ], + [ + "Rendere accessibile il servizio", + "ports", + "Un indirizzo proprio per l’LXC e l’URL di accesso al servizio." + ], + [ + "Usare l’hardware", + "devices, group_add", + "Voci devN con il GID effettivo dell’host e un profilo validato." + ], + [ + "Collegare le dipendenze", + "networks, depends_on", + "Un bridge di rete privato, indirizzi fissi, ordine di avvio e controlli di stato." + ], + [ + "Aggiornare", + "Scaricare con pull e ricreare", + "Un nuovo rootfs che mantiene lo stesso contratto di persistenza." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "Cosa viene conservato e cosa cambia", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Conservato", + "body": "L’immagine ufficiale, i percorsi interni, l’ambiente, il processo di avvio e la documentazione d’uso." + }, + { + "icon": "boxes", + "title": "Adattato", + "body": "L’ambiente di esecuzione: rete, persistenza, dispositivi, permessi e dipendenze utilizzano i meccanismi LXC nativi di Proxmox VE." + }, + { + "icon": "shield", + "title": "Senza equivalente", + "body": "Un’opzione Compose senza un equivalente sicuro viene segnalata come requisito bloccante. I modelli con requisiti bloccanti non vengono proposti per l’installazione e le opzioni sensibili richiedono conferma durante la procedura." + }, + { + "icon": "refresh", + "title": "Registrato", + "body": "Il digest dell’immagine, le risorse, i percorsi, la rete, i dispositivi e l’appartenenza a uno stack vengono salvati nel contratto dell’istanza. Aggiornamenti e ricreazioni riproducono tale contratto." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Pagine di questa sezione", + "blocks": [ + { + "next": { + "items": [ + { + "label": "Come viene adattata un’immagine OCI", + "href": "/docs/oci-manager/architecture", + "tail": "Dall’immagine e dal suo file Compose a un LXC nativo." + }, + { + "label": "Installare un’immagine non presente nel catalogo", + "href": "/docs/oci-manager/custom-image", + "tail": "Un file Compose, un comando docker run o un riferimento a un’immagine." + }, + { + "label": "Dati, percorsi e rete", + "href": "/docs/oci-manager/storage-network", + "tail": "Volumi del container, directory dell’host, mount con rclone e indirizzi." + }, + { + "label": "Dispositivi e accelerazione", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB e altri dispositivi." + }, + { + "label": "Applicazioni con più container", + "href": "/docs/oci-manager/stacks", + "tail": "Applicazione, database e cache in LXC coordinati." + }, + { + "label": "Installazione, aggiornamento e modifica", + "href": "/docs/oci-manager/lifecycle", + "tail": "Il contratto dell’istanza e le sue operazioni." + }, + { + "label": "In ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versioni, aggiornamenti, output della console e terminale." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/lifecycle.json b/web/messages/it/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..a0271b28 --- /dev/null +++ b/web/messages/it/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Installare, aggiornare e modificare | ProxMenux", + "description": "Il contratto di istanza di un contenitore OCI e le operazioni che lo utilizzano: aggiornamento, modifica, rimozione, recupero di un'operazione interrotta e recupero dopo un ripristino su questo o un altro host." + }, + "header": { + "title": "Installare, aggiornare e modificare", + "description": "Ogni istanza conserva un contratto riproducibile, così il suo rootfs può essere sostituito senza perdere la configurazione o i dati persistenti.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "Il contratto di istanza", + "blocks": [ + { + "p": "Dopo l'installazione, la configurazione effettiva viene salvata in /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json. Non è una copia del file Compose originale: è il contratto riproducibile del LXC presente su questo host." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "Una copia del contratto si trova nel contenitore, in /.proxmenux/oci-record.json, ed è leggibile solo dall'utente root del contenitore. Il backup del contenitore include quindi sempre il contratto in uso in quel momento. Una seconda copia si trova in /etc/pve/priv/proxmenux/oci, condiviso da tutti i nodi del cluster e leggibile solo da root dell'host. Entrambe le copie vengono scritte dopo ogni installazione, aggiornamento e modifica." + } + ] + }, + { + "id": "manage", + "title": "Gestisci le applicazioni OCI installate", + "intro": "Questa opzione della schermata principale elenca le istanze registrate con la relativa applicazione e immagine. Prima di ogni azione, ciascuna istanza viene confrontata con il proprio contratto; un CT che non corrisponde più al proprio record non viene né modificato né eliminato. Lo stesso aggiornamento e lo stesso editor sono disponibili nella scheda Aggiornamenti di ProxMenux Monitor.", + "blocks": [ + { + "table": { + "headers": [ + "Opzione", + "Che cosa cambia", + "Che cosa viene conservato" + ], + "rows": [ + [ + "Aggiorna l’immagine mantenendo la configurazione salvata", + "Il rootfs viene sostituito dall'immagine attualmente pubblicata dal canale salvato", + "Contratto, dischi container, directory host, rete e dispositivi" + ], + [ + "Modifica risorse, rete, percorsi e GPU", + "L’editor si apre con il contratto attuale; il CT viene ricreato con le modifiche", + "I dati dei dischi container e delle directory host" + ], + [ + "Watchdog: riavviare l'applicazione quando si blocca", + "Determina se l'applicazione viene riavviata quando si arresta da sola. Non viene ricreato nulla", + "L'intera installazione; solo la scelta viene salvata nel contratto" + ], + [ + "Rimuovi: elimina l'applicazione e i suoi container", + "Il LXC, o ogni membro di uno stack, e i loro contratti vengono eliminati", + "Directory dell’host, con il loro contenuto" + ] + ] + } + }, + { + "p": "Per un'applicazione con più contenitori, il menu offre Aggiorna tutti i contenitori dell'applicazione, Modifica percorsi e dispositivi aggiuntivi, Ricrea tutti i contenitori con la configurazione salvata, il watchdog e la rimozione. Modifica aggiunge o rimuove percorsi e dispositivi aggiuntivi del contenitore applicativo senza ricreare nulla e, in Immich, cambia il componente che esegue il riconoscimento. Ricrea ricostruisce ogni contenitore dall'immagine usata per installarlo, senza cercarne una più recente." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Elenco delle applicazioni OCI installate e delle opzioni di aggiornamento, modifica e rimozione", + "caption": "Gestione delle applicazioni OCI installate." + } + } + ] + }, + { + "id": "update", + "title": "Un aggiornamento transazionale", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Utente", + "registry": "Registro OCI", + "update": "Aggiorna istanza", + "resolve": "Risolvere il tag", + "verify": "Verifica archivio e controlli preliminari", + "backup": "Arresta e salva un backup del CT", + "import": "Importa il nuovo rootfs", + "reapply": "Applicare nuovamente il contratto", + "healthy": "integro", + "failure": "guasto", + "commit": "Contratto pubblicato con il nuovo digest", + "restored": "Precedente istanza ripristinata" + } + } + }, + { + "list": { + "items": [ + "Se il registro restituisce ancora il digest installato, non viene scaricato nulla e l’istanza rimane invariata.", + "La nuova immagine viene verificata livello per livello prima di arrestare il CT. Un download danneggiato viene ripetuto una seconda volta; un’immagine già nella cache che non supera la verifica viene scaricata di nuovo.", + "Le impostazioni modificate in Proxmox VE dopo l’installazione (memoria, swap, core, limite e priorità della CPU, avvio con il nodo) vengono conservate e incluse nel nuovo contratto.", + "L'interfaccia LAN che la suite Arr e le applicazioni multi-container aggiungono (net1) e l'ordine di avvio (startup) sono restituiti al nuovo contenitore come sono.", + "Qualsiasi altra differenza tra il CT e il suo contratto interrompe l’aggiornamento prima che il CT venga arrestato." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Prima", + "detail": "rootfs A\n/config mp0\ndirectory host /media" + }, + { + "label": "Aggiornamento", + "detail": "sostituisce solo\nil rootfs" + }, + { + "label": "Dopo", + "detail": "rootfs B\n/config mp0\ndirectory host /media" + } + ] + } + }, + { + "p": "Prima di arrestare qualsiasi cosa, l’operazione verifica lo spazio necessario al backup: i dati in uso nei volumi inclusi, più un margine. Il backup viene scritto accanto al record dell’applicazione, sul disco di sistema dell’host. Se lo spazio non basta, il menu richiede un altro storage dell’host che supporti i backup: il backup viene creato lì ed eliminato al termine dell’operazione. La scelta viene memorizzata per l’applicazione, anche per gli aggiornamenti pianificati. Se nessuno storage ha spazio sufficiente, l’operazione viene rifiutata e l’applicazione resta invariata. Anche lo spazio per la nuova immagine viene verificato prima del download." + } + ] + }, + { + "id": "recovery", + "title": "Recuperare un'operazione interrotta", + "intro": "L’aggiornamento e la modifica usano una transazione persistente. Se il processo, il terminale o il nodo subisce un’interruzione dopo l’arresto del CT, l’operazione non viene considerata conclusa.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Indicatore di operazione in sospeso", + "body": "Aprendo Gestisci le applicazioni OCI installate, lo stato salvato indica che la sostituzione non è mai stata pubblicata." + }, + { + "title": "Visualizza stato", + "body": "Mostra la fase raggiunta senza cambiare contenitori o dati." + }, + { + "title": "Ripristina l’installazione precedente", + "body": "Ripristina il backup nativo verificato eseguito prima della sostituzione e il contratto precedente." + }, + { + "title": "Stacks come unità", + "body": "Per un'applicazione multicontainer, ogni membro viene recuperato dallo stesso punto di transazione, non solo quello selezionato." + } + ] + } + }, + { + "calloutWarning": { + "title": "Le directory dell’host sono escluse dal rollback", + "body": "Il backup comprende il rootfs e i dischi del contenitore inclusi nella copia. Una directory dell’host non viene riportata allo stato precedente, perché altri LXC potrebbero usarne i dati." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: riavviare un’applicazione che si arresta", + "intro": "Proxmox non ha una politica di riavvio: quando termina il processo di un contenitore applicativo, il contenitore resta arrestato. Con il watchdog attivo, un’applicazione che si arresta da sola viene riavviata. Per un’applicazione OCI svolge il ruolo di restart: unless-stopped in un file Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Cosa succede?", + "Con il watchdog attivo" + ], + "rows": [ + [ + "L'applicazione si blocca o il suo processo termina", + "Il contenitore viene riavviato entro pochi secondi" + ], + [ + "Viene fermato o spento da Proxmox, dal Monitor o con pct", + "Resta arrestato finché qualcuno non lo avvia" + ], + [ + "Un backup, una migrazione o un'operazione ProxMenux sta lavorando su di esso", + "Niente è fatto fino a che non finisce" + ], + [ + "Era già arrestato all’avvio dell’host", + "Resta arrestato; lo decide Avvia con Proxmox" + ], + [ + "Continua ad arrestarsi subito dopo l’avvio", + "Per un’applicazione che si arresta pochi secondi dopo l’avvio o che non si avvia, vengono effettuati cinque tentativi, con attese di 30 secondi, 1, 2 e 5 minuti tra un tentativo e l’altro. Poi viene lasciata arrestata e viene inviata una notifica" + ] + ] + } + }, + { + "p": "L’installatore lo chiede in entrambe le modalità e propone il valore dichiarato nella ricetta dell’applicazione. In seguito si può cambiare da Gestisci le applicazioni OCI installate o con l’interruttore Watchdog del contenitore in ProxMenux Monitor; vale per tutti i contenitori di un’applicazione con più contenitori. Un unico servizio dell’host, proxmenux-oci-watchdog.service, sorveglia tutte le applicazioni che lo hanno richiesto; la sua installazione compare nella scheda Modifiche di Audit & Report." + }, + { + "calloutInfo": { + "title": "Ciò che l’host non può distinguere", + "body": "Il codice di uscita dell’applicazione non raggiunge l’host: un’uscita regolare e un arresto anomalo risultano quindi indistinguibili e in entrambi i casi l’applicazione viene riavviata, come con always e unless-stopped in Docker. Un arresto richiesto fuori da Proxmox, con lxc-stop o systemctl, non lascia un’attività di arresto e viene interpretato come un arresto spontaneo." + } + } + ] + }, + { + "id": "restore", + "title": "Backup, ripristino e un altro host", + "intro": "Un backup creato con vzdump o Proxmox Backup Server include il contenitore, i dischi del contenitore selezionati per il backup e la copia del suo contratto. Ciò che l’installazione conserva sull’host non vi è incluso. Gestisci le applicazioni OCI installate rileva i contenitori ripristinati su un host privo del loro contratto, dopo una nuova installazione di Proxmox o su un altro host, e propone di registrarli di nuovo.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Ripristinare i contenitori in Proxmox", + "body": "Dall'archivio di backup, con l'ID originale o con uno qualsiasi libero. Un'applicazione multi-container ha bisogno di tutti i suoi contenitori." + }, + { + "title": "Apri Gestisci applicazioni OCI installate", + "body": "Vengono elencati i contenitori ripristinati e viene proposto il recupero. Il pulsante Recupera nella scheda Aggiornamenti di ProxMenux Monitor apre lo stesso recupero." + }, + { + "title": "Controllare prima di cambiare", + "body": "Il recupero verifica che l’host disponga di tutto ciò che serve a ciascuna applicazione. Se un’applicazione non può essere recuperata interamente, viene lasciata invariata e viene indicato il motivo." + }, + { + "title": "Registra e avvia", + "body": "Il contratto viene registrato su questo host e gli elementi che l’installazione vi conservava vengono riscritti. L’avvio delle applicazioni viene chiesto separatamente: rispondi No se i contenitori originali sono ancora in esecuzione su un altro host." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Finestra di dialogo che elenca i contenitori ripristinati e ne propone il recupero", + "caption": "Il recupero offerto quando si apre Gestione applicazioni OCI installate." + } + }, + { + "table": { + "headers": [ + "Ciò che l’host conservava", + "Dopo il recupero" + ], + "rows": [ + [ + "Contratto dell’istanza", + "Registrato dalla copia presente nel contenitore, verificata rispetto alla configurazione ripristinata da Proxmox" + ], + [ + "Rete privata di un'applicazione multicontainer", + "Creato nuovamente con lo stesso ponte e sottorete; gli indirizzi fissi dei contenitori non cambiano" + ], + [ + "Ordine di avvio di un’applicazione con più contenitori", + "L’hookscript delle dipendenze e il suo contratto vengono reinstallati; gli snippet vengono abilitati nello storage local se nessuno storage li supporta" + ], + [ + "Sysctl di rete e file del monitor dell’host", + "Scritto di nuovo in /etc/pve/proxmenux" + ], + [ + "Runtime NVIDIA", + "L’hook di un contenitore non privilegiato viene reinstallato. Un contenitore privilegiato riceve i file del driver di questo host al posto di quelli dell’host di origine" + ], + [ + "Montaggio Rclone", + "L’hookscript e i programmi che rendono disponibile il mount vengono riscritti, con le stesse viste sull’host" + ], + [ + "Regola del firewall host di un monitor host", + "Viene chiesto di nuovo, per la sua porta web e la sottorete del bridge su questo host" + ], + [ + "lost+found di ogni disco restaurato", + "Viene rimosso se vuoto; il ripristino lo crea e alcune applicazioni non si avviano se è presente tra i loro dati" + ], + [ + "Dischi ripristinati su un altro storage", + "Il contratto viene aggiornato con lo storage su cui si trovano ora" + ] + ] + } + }, + { + "p": "Un container ripristinato con un ID diverso conserva la propria applicazione. Il contratto, il log della console, i parametri sysctl di rete, lo hookscript del montaggio Rclone e l’ordine di avvio di un’applicazione composta da più container vengono registrati usando gli ID assegnati ai container su questo host." + }, + { + "table": { + "headers": [ + "Cosa ferma un recupero", + "Cosa fare" + ], + "rows": [ + [ + "Manca un contenitore di un'applicazione multi-container", + "Ripristina anche quello; l’applicazione viene recuperata per intero" + ], + [ + "La sottorete privata è già utilizzata su questo host", + "Il recupero è annullato e nulla è cambiato: gli indirizzi dei contenitori sono fissi e non vengono spostati in un'altra subnet" + ], + [ + "Una directory host non esiste", + "Montare o crearlo con i suoi dati; un backup del contenitore non include directory host" + ], + [ + "Un dispositivo non esiste su questo host", + "Collegarlo o rimuoverlo dal contenitore in Proxmox" + ], + [ + "Un'applicazione con NVIDIA su un host senza driver", + "Installa il driver NVIDIA e il Container Toolkit" + ], + [ + "Il backup è stato fatto prima che la copia del contratto esistesse", + "Il contenitore rimane come un normale LXC e non viene offerto di nuovo" + ] + ] + } + }, + { + "calloutInfo": { + "title": "Un contenitore che ritorna", + "body": "Un contenitore ripristinato su se stesso da un backup precedente, riportato a uno snapshot o rientrato da un altro nodo porta il contratto dello stato in cui si trova. Se viene selezionato per un’operazione e il suo contratto differisce da quello dell’host, l’operazione non parte e viene proposto il recupero del contenitore." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Risultato del recupero con la rete privata, l’ordine di avvio e i contenitori registrati", + "caption": "Il risultato di un recupero, passo dopo passo." + } + } + ] + }, + { + "id": "remove", + "title": "Rimozione di un'applicazione OCI", + "blocks": [ + { + "p": "Prima della conferma viene creato un riepilogo dalla configurazione effettiva: i contenitori da rimuovere, i dati eliminati insieme a loro, la rete privata liberata e le directory dell’host conservate. La conferma è impostata su No per impostazione predefinita, perché i dati dei dischi eliminati non possono essere recuperati in seguito." + }, + { + "table": { + "headers": [ + "Risorsa", + "Alla rimozione", + "Motivo" + ], + "rows": [ + [ + "rootfs e dischi del contenitore", + "Eliminati", + "Essi appartengono solo al contenitore" + ], + [ + "Directory host", + "Tenuto, con il suo contenuto", + "Altre applicazioni possono utilizzarlo" + ], + [ + "File host del contenitore", + "Eliminati", + "Il log della console, i parametri sysctl di rete, l’hookscript del mount Rclone e la registrazione nella scheda App di ProxMenux Monitor non servono ad altri contenitori" + ], + [ + "File condivisi da più installazioni", + "Eliminato con l'ultima installazione che li utilizza", + "Il file del monitor dell’host e l’hookscript delle dipendenze delle applicazioni con più contenitori" + ], + [ + "Contratto dell’istanza", + "Eliminato dopo una rimozione di successo", + "Non vi è più associato alcun CT" + ], + [ + "Ponte privato di una pila", + "Rilasciato con lo stack", + "Non ha più membri da collegare" + ], + [ + "Rete condivisa dalla suite Arr", + "Rilasciato con l'ultima applicazione della suite", + "Ogni applicazione della suite è indipendente e viene rimossa da sola" + ], + [ + "Un singolo membro di una pila", + "Non rimosso da solo", + "L'intera applicazione viene rimossa, quindi nessun stack viene lasciato incompleto" + ], + [ + "Un contenitore su un altro nodo del cluster", + "Non rimosso", + "I suoi file di registrazione e host sono sul nodo dove è stato installato: viene rimosso lì, dopo la migrazione indietro" + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Riepilogo di rimozione con i container, i dati e le directory host interessate", + "caption": "Il sommario di rimozione, costruito dalla configurazione reale." + } + } + ] + }, + { + "id": "archives", + "title": "Immagini scaricate e spazio host", + "blocks": [ + { + "p": "L’archivio OCI serve a costruire il rootfs; il CT in esecuzione non lo legge. Dopo un’installazione o un aggiornamento, gli archivi scaricati durante l’operazione vengono elencati con le loro dimensioni e ne viene proposta l’eliminazione. Eliminarli libera spazio senza influire sul contenitore o sui suoi dati." + }, + { + "calloutInfo": { + "title": "Gli aggiornamenti non hanno bisogno dell'archivio", + "body": "Senza l'archivio, un aggiornamento risolve il canale salvato e scarica il nuovo digest. Con esso, un archivio viene riutilizzato solo quando il suo riferimento e l'integrità corrispondono a ciò che è richiesto." + } + } + ] + }, + { + "id": "registry", + "title": "Registro e pulizia", + "blocks": [ + { + "p": "Aprendo Gestisci le applicazioni OCI installate, vengono prima rimossi i resti dei contenitori che non esistono su alcun nodo del cluster: il record conservato come cronologia dopo l’eliminazione dall’interfaccia Proxmox e i file sull’host lasciati da una rimozione. Un record con un’operazione incompleta viene conservato, perché il relativo backup potrebbe ancora servire. Volumi e directory dell’host non vengono mai eliminati per deduzione." + } + ] + }, + { + "id": "cluster", + "title": "In un cluster: migrazione e elevata disponibilità", + "intro": "Un contenitore OCI è un normale LXC Proxmox: può essere migrato o gestito da HA come gli altri, con gli stessi limiti. Ciò che gli serve per avviarsi viene conservato dove ogni nodo del cluster può trovarlo.", + "blocks": [ + { + "table": { + "headers": [ + "Parte", + "Su un altro nodo del cluster" + ], + "rows": [ + [ + "Registro della console", + "Il contenitore crea /var/log/proxmenux/oci prima di avviarsi, su qualunque nodo lo esegua. Ogni nodo conserva il log degli avvii eseguiti su quel nodo." + ], + [ + "Sysctl di rete e monitor dell’host", + "Tenuto in /etc/pve/proxmenux, che ogni nodo del cluster condivide, quindi un contenitore migrato li trova." + ], + [ + "Dischi del contenitore", + "Proxmox li sposta con il contenitore. HA richiede che i dati siano disponibili sul nodo di destinazione tramite storage condiviso o una configurazione di replica supportata." + ], + [ + "directory e dispositivi host", + "Valgono le stesse regole degli altri LXC: una directory dell’host deve esistere sul nodo di destinazione ed essere contrassegnata come condivisa, e una GPU, un Coral o una NPU deve essere presente su quel nodo." + ], + [ + "Record ProxMenux", + "Il contratto resta sul nodo in cui l’applicazione è stata installata e ogni nodo legge la copia conservata in /etc/pve/priv/proxmenux/oci. Dopo la migrazione, un’applicazione con un solo contenitore viene registrata sul nodo di destinazione quando si apre Gestisci le applicazioni OCI installate o si avvia un’operazione su di essa. Per un’applicazione con più contenitori viene proposto il recupero, perché la rete privata deve essere creata su quel nodo." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Non per alta disponibilità", + "body": "Un'applicazione multicontainer raggiunge i suoi membri attraverso un ponte privato del nodo su cui è stato installato, quindi i suoi membri rimangono su quel nodo. Un monitor host, come Glances in modalità host o Netdata, monitora il nodo su cui corre; spostandolo monitorerebbe un altro nodo." + } + }, + { + "p": "Quando si recupera l’applicazione, un backup ripristinato fuori dal cluster riceve i file di /etc/pve/proxmenux usati dal contenitore." + } + ] + }, + { + "id": "channel", + "title": "Un rolling tag non è un aggiornamento incustodito", + "blocks": [ + { + "p": "Il catalogo installa il rolling tag pubblicato dal manutentore, ma il digest effettivo viene risolto e registrato e cambia solo con un aggiornamento esplicito, dotato di controlli preliminari e rollback. ProxMenux Monitor confronta il digest installato con quello pubblicato dal registro e segnala, anche con una notifica, la disponibilità di una nuova immagine." + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/monitor.json b/web/messages/it/docs/oci-manager/monitor.json new file mode 100644 index 00000000..ca107f84 --- /dev/null +++ b/web/messages/it/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "Contenitori OCI in ProxMenux Monitor | ProxMenux", + "description": "Ciò che ProxMenux Monitor mostra per un contenitore installato da OCI manager Apps: versioni di applicazione e immagine, nuove immagini, uscita console e terminale Proxmox VE." + }, + "header": { + "title": "In ProxMenux Monitor", + "description": "Un contenitore installato da OCI manager Apps appare in VMs & LXCs come qualsiasi altro LXC. Il suo modal legge il record di installazione: versioni di applicazione e immagine, nuove immagini, uscita console e terminale.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Il record è la fonte", + "body": "OCI manager Apps sa cosa ha installato e da dove viene. Il Monitor legge quel record di installazione invece di interrogare il container, quindi i dati sono gli stessi se il contenitore è in esecuzione o interrotto." + } + }, + { + "table": { + "headers": [ + "Scheda", + "Quali cambiamenti per un contenitore OCI" + ], + "rows": [ + [ + "Stato", + "Un commutatore Watchdog sotto Start al boot: l'applicazione viene riavviata quando si ferma da sola" + ], + [ + "App", + "L'applicazione è identificata dal record, e gli aggiornamenti sono tracciati dall'immagine" + ], + [ + "Aggiornamenti", + "L'immagine viene aggiornata o l'applicazione modificata, con lo stesso flusso del menu OCI" + ], + [ + "Montaggi", + "Dischi del container e directory dell’host, con il relativo utilizzo" + ], + [ + "Log", + "Solo per contenitori OCI: l'uscita della console dell'applicazione" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "App", + "intro": "La scheda App offre l'applicazione installata come rilevamento, con il nome, il logo, la porta e lo schema del record. Registrandolo si apre l'editor con il metodo Immagine OCI (installata da ProxMenux), che non ha bisogno di configurazione.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Applicazione", + "body": "La versione dell'applicazione all'interno dell'immagine, letta dall'immagine stessa: il suo ambiente o la sua etichetta org.opencontainers.image.version. È informativo." + }, + { + "icon": "archive", + "title": "Immagine", + "body": "La data di creazione e digest dell'immagine installata. L'aggiornamento si decide qui: il digest installato viene confrontato con quello che il registro pubblica oggi per lo stesso tag." + } + ] + } + }, + { + "list": { + "items": [ + "Quando il registro pubblica un nuovo digest, la scheda mostra Nuova immagine con la sua data e digest, anche se la versione dell'applicazione al suo interno non è cambiata: una ricostruzione su una base aggiornata è un aggiornamento.", + "Quando i digest coincidono, la scheda mostra Versione.", + "La scheda si collega al repository dell'immagine: il suo progetto GitHub, o la sua pagina Hub Docker per un'immagine ufficiale.", + "Il collegamento di accesso utilizza l'indirizzo LAN del contenitore, anche per il membro principale di un'applicazione multi-container, che ha un secondo indirizzo sulla sua rete privata.", + "Il pulsante Aggiorna dati rilegge il record dell’installazione e il registro delle immagini. Non vengono proposte opzioni per cercare o registrare altre applicazioni: il container contiene esattamente l’applicazione descritta nel suo record." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "Scheda App di un contenitore OCI con la versione dell'applicazione, l'immagine e il link del repository", + "caption": "Scheda app di un contenitore installato da OCI manager Apps." + } + }, + { + "p": "Il controllo viene eseguito una volta al giorno con i controlli di aggiornamento del Monitor e Aggiorna dati lo esegue subito. Una nuova immagine viene inviata come notifica attraverso i canali configurati in Notifiche. L'aggiornamento viene applicato dalla scheda Aggiornamenti." + } + ] + }, + { + "id": "updates", + "title": "Aggiornamenti", + "intro": "Per un contenitore installato da OCI manager Apps la scheda Aggiornamenti mostra l'applicazione con la sua immagine installata e, quando il registro pubblica una, la nuova immagine, nello stesso formato di qualsiasi altra applicazione. Gli strumenti di aggiornamento di pacchetti e applicazioni di un normale LXC non appaiono.", + "blocks": [ + { + "table": { + "headers": [ + "Pulsante", + "Che cosa si apre" + ], + "rows": [ + [ + "Aggiornamento", + "L’aggiornamento di Gestisci applicazioni OCI installate per questo container, nel terminale di Monitor. Può essere eseguito anche senza una nuova immagine; in quel caso non cambia nulla. In un’applicazione composta da più container aggiorna tutti i membri." + ], + [ + "Modifica", + "L'editor dell'applicazione (risorse, rete, percorsi e GPU). In un'applicazione multi-container aggiunge o rimuove i percorsi e i dispositivi aggiuntivi del contenitore di applicazione e, in Immich, cambia ciò che esegue il riconoscimento." + ], + [ + "Recupera", + "Sostituisce l'aggiornamento quando un'operazione sul contenitore è stata interrotta e apre il suo recupero." + ], + [ + "Recupera (container ripristinato)", + "Mostrato per un container ripristinato da un backup che non ha un contratto su questo host, sotto Applicazione OCI ripristinata. Apre il recupero nel terminale di Monitor; Aggiorna e Modifica compaiono dopo la registrazione." + ] + ] + } + }, + { + "p": "Modifiche esterne, directory dell’host e applicazioni composte da più container vengono gestite come nel menu OCI. Per queste applicazioni, pulsanti e opzioni compaiono solo nel container principale. Gli altri membri mostrano la propria immagine, una nota che rimanda al principale per gli aggiornamenti e un pulsante per aprirlo. Alla chiusura del terminale vengono riletti l’immagine, il record e i montaggi." + }, + { + "table": { + "headers": [ + "Opzione", + "Comportamento" + ], + "rows": [ + [ + "Conserva il backup eseguito prima dell'aggiornamento", + "Ogni aggiornamento esegue il backup del container per ripristinarlo se l'aggiornamento non riesce. Con questa opzione, lo stesso backup viene mantenuto nello storage scelto e compare tra i backup del CT; su Proxmox Backup Server viene scritto un backup prima dell'aggiornamento." + ], + [ + "Aggiornamenti pianificati", + "L’immagine viene aggiornata all’ora scelta solo se il registro ne pubblica una nuova, con la possibilità di attendere che abbia 1, 3, 7 o 14 giorni. Un container modificato al di fuori di ProxMenux viene saltato e segnalato." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Log", + "intro": "La scheda Logs appare solo per i contenitori installati da OCI manager Apps, tra Mounts e Backups. Mostra l'uscita standard e l'errore del processo principale dell'immagine, la stessa uscita mostra docker logs per un contenitore Docker.", + "blocks": [ + { + "list": { + "items": [ + "L'output viene mantenuto sull'host in /var/log/proxmenux/oci/VMID.console.log (mode 0600), dal primo avvio e attraverso riavviamenti, in modo da poter essere letto con il contenitore fermato.", + "Il file viene ruotato a 10 MB, mantenendo tre copie compresse (/etc/logrotate.d/proxmenux-oci).", + "Ogni avvio del contenitore è contrassegnato nell'uscita. La scheda mostra l'output dall'ultimo avvio per impostazione predefinita; Cronologia completa mostra anche quelli precedenti.", + "Quando l'applicazione viene rimossa, la sua uscita viene rimossa con essa.", + "Vengono mostrate le ultime 100, 500 o 1000 righe. Mentre il container è in esecuzione, le nuove righe sono seguite in tempo reale; scorrere verso l’alto mette in pausa il seguito e Segui lo riprende.", + "Un filtro mostra solo le linee che contengono un testo e Scarica salva le linee caricate.", + "I codici a colori sono rimossi e una linea che una barra di avanzamento ridisegna è mostrata nel suo stato finale.", + "La scheda legge il file a ogni apertura; non usa una copia in cache." + ] + } + }, + { + "calloutInfo": { + "title": "credenziali di primo avviamento", + "body": "Le immagini che stampano una password generata sul loro primo inizio lo lasciano in questa uscita, come fa docker logs. L'installatore lo legge da lì per mostrarlo nel suo riassunto." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Scheda di log con l'uscita della console di un contenitore OCI, il selettore della linea, il filtro e il pulsante seguente", + "caption": "Uscita console di un contenitore OCI." + } + } + ] + }, + { + "id": "terminal", + "title": "Console Proxmox VE", + "blocks": [ + { + "p": "Un'immagine OCI esegue il proprio processo come PID 1 e nessun servizio di login, quindi la console predefinita di un LXC aprirebbe un terminale in cui nulla risponde. I contenitori installati da OCI manager Apps sono creati con cmode: shell: la console Proxmox VE apre una shell con lxc-attach, l'equivalente di docker exec, con l'applicazione in esecuzione intatta." + }, + { + "list": { + "items": [ + "La shell è quella assegnata a root nell’immagine da /etc/passwd. Se root non ha una shell o l’immagine non ne include alcuna, viene mantenuta la console predefinita.", + "La shell viene eseguita come root all'interno del contenitore, senza una password. Chi può aprirlo è deciso dal permesso VM.Console di Proxmox VE.", + "Il terminale del Monitor entra nel contenitore con pct enter, che funziona allo stesso modo." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Montaggi", + "blocks": [ + { + "p": "La scheda Mounts elenca i dischi container e le directory host del contenitore. L'uso di un disco contenitore su blocco di archiviazione come LVM-thin è letto dal filesystem del disco, che è montato solo all'interno del contenitore." + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/stacks.json b/web/messages/it/docs/oci-manager/stacks.json new file mode 100644 index 00000000..78160aba --- /dev/null +++ b/web/messages/it/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Applicazioni multicontainer | ProxMenux", + "description": "Come OCI manager Apps trasforma un'applicazione con il suo database e la cache in LXCs nativi coordinati: piano, rete privata, gancio di dipendenza, convalida e aggiornamenti transazionali." + }, + "header": { + "title": "Applicazioni con più container", + "description": "Un'applicazione con il suo database, la cache e altri servizi diventa diversi LXC nativi coordinati, installati e aggiornati come uno.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Un'applicazione per l'utente, diversi LXC per Proxmox VE", + "body": "Una definizione multi-container è una voce del catalogo. L'installatore crea un LXC nativo per servizio e mantiene esplicite le proprie dipendenze. Immich, Nextcloud, Paperless-ngx e Tandoor sono installati in questo modo." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Piano di stack", + "app": "Applicazione LXC", + "appNet": "LAN + privato", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "privato" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. Il piano, prima che esista un contenitore", + "blocks": [ + { + "p": "Nessun LXC viene creato mentre la definizione Compose viene interpretata. Un piano completo arriva prima, con ogni membro, immagine, VMID, rete, percorso, segreto, ordine e controllo sanitario. Se il piano non è coerente, l'installazione non inizia." + }, + { + "table": { + "headers": [ + "Elemento di piano", + "Contenuto", + "Controllato prima di creare qualcosa" + ], + "rows": [ + [ + "Membri", + "Applicazione principale, database, cache, machine learning e altre dipendenze", + "VMID unici, ruoli noti e esattamente un membro principale" + ], + [ + "Immagini", + "Riferimento rolling, architettura e digest risolto di ogni servizio", + "Tutte esistono, supportano l’architettura e superano il controllo di integrità OCI" + ], + [ + "Rete", + "Ponte privato, subnet, indirizzo fisso per servizio e accesso LAN per il membro principale", + "Nessuna collisione con ponti o sottorete esistenti e nessun indirizzo ripetuto" + ], + [ + "Persistenza", + "Dischi dei container, directory dell’host, proprietari e backup", + "Nessun percorso sovrapposto, deposito disponibile e permessi dichiarati" + ], + [ + "Segreti", + "password del database, chiavi dell'applicazione e credenziali iniziali", + "Generati una volta e forniti solo ai membri che li usano" + ], + [ + "Ciclo di vita", + "Ordine di inizio, ordine di arresto e un controllo sanitario per membro", + "L'applicazione principale inizia l'ultima e si ferma prima" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Creazione, membro per membro", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Prenota ogni VMID", + "body": "L'inventario Proxmox VE e il registro delle istanze sono controllati. Un CT esistente non è adottato e un contratto che appartiene ancora ad un'altra istanza non viene riutilizzato." + }, + { + "title": "Preparare ogni immagine", + "body": "Tutte le immagini vengono risolte, scaricate e verificate prima che venga creato il primo contenitore." + }, + { + "title": "Creare ogni rootfs", + "body": "I metadati OCI ufficiali sono importati, e l'identità di istanza ProxMenux e il ruolo membro sono aggiunti." + }, + { + "title": "Rete privata", + "body": "Viene creato un ponte e una subnet e ogni membro riceve il suo indirizzo fisso; solo il membro principale riceve anche l'interfaccia LAN." + }, + { + "title": "Persistenza", + "body": "Ogni database e configurazione ottiene il proprio disco contenitore; solo i dati destinati ad essere condivisi utilizzano directory host." + }, + { + "title": "Ambiente", + "body": "Vengono scritti endpoint interni, segreti condivisi e variabili di servizio. L'applicazione raggiunge le sue dipendenze nei loro indirizzi privati riservati." + }, + { + "title": "Registrazione", + "body": "Ogni membro registra la sua configurazione nativa, le modifiche rootfs che gli aggiornamenti devono riprodurre e la sua relazione con lo stack." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Avviare e controllare ogni contenitore", + "intro": "Un LXC creato non significa un servizio pronto. Le dipendenze iniziano in ordine e ognuno passa un controllo specifico al suo servizio.", + "blocks": [ + { + "table": { + "headers": [ + "Servizio", + "Controllo", + "Cosa mostra" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready nel CT con l'host previsto, l'utente e il database", + "Il server accetta le connessioni per il database configurato" + ], + [ + "Redis / Valkey", + "redis-cli o valkey-cli PING contro il suo indirizzo privato", + "Il broker ascolta e risponde" + ], + [ + "Machine learning Immich", + "HTTP GET /ping, oltre a un controllo del runtime GPU selezionato", + "Il servizio risponde e l’accelerazione richiesta non è passata alla CPU" + ], + [ + "Nextcloud", + "GET /status.php con installed=true, maintenance=false e needsDbUpgrade=false", + "Inizializzazione terminata senza migrazione in sospeso" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP sull'indirizzo LAN e vera porta del servizio", + "Il frontend e le sue dipendenze servono l'applicazione" + ], + [ + "Applicazione principale", + "Il punto finale del suo modello, ad esempio /api/server/ping in Immich", + "L'intero stack funziona attraverso l'applicazione che utilizza le dipendenze" + ] + ] + } + }, + { + "calloutWarning": { + "title": "In esecuzione non significa funzionante", + "body": "Lo stato in esecuzione dice solo che esiste il processo LXC. Se il servizio offre un controllo migliore, viene utilizzato un controllo exec o HTTP con un timeout. Se un membro si ferma o non supera la verifica, lo stack non viene dichiarato installato." + } + } + ] + }, + { + "id": "hook", + "title": "4. Il gancio di dipendenza", + "blocks": [ + { + "p": "Il gancio è impostato solo sul contenitore principale di uno stack dipendente. Proxmox VE mantiene lo script come snippet e lo gestisce come hookscript di quel CT. La ricetta dello stack non è scritta nello script: vive in un contratto privato separato." + }, + { + "codeGrid": { + "items": [ + { + "title": "Configurazione principale CT", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Contratto privato (esempio)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Fonte completa di proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "Lo script è lo stesso per ogni stack. VMID, nomi, tipi di controllo e timeout provengono dal contratto privato /etc/pve/priv/proxmenux-stack-VMID.json di ogni stack." + }, + { + "steps": { + "items": [ + { + "title": "Proxmox VE chiama pre-start", + "body": "Prima dell'avvio principale di CT, il gancio viene eseguito con il VMID e la fase del ciclo di vita." + }, + { + "title": "Un blocco per stack", + "body": "flock su /run/lock/proxmenux-stack-VMID.lock previene due sequenze di avvio contemporaneamente." + }, + { + "title": "Il contratto viene letto e convalidato", + "body": "Uno schema noto, dipendenze numeriche, etichette, un exec, http o controllo in esecuzione e un timeout positivo sono necessari." + }, + { + "title": "Dipendenze in ordine", + "body": "Ogni CT deve esistere; se è fermo viene avviato, se è già in esecuzione non viene riavviato." + }, + { + "title": "Attendi che il servizio sia pronto", + "body": "Il controllo viene eseguito ogni due secondi, e il CT è confermato anche di essere ancora in esecuzione." + }, + { + "title": "Il principale CT inizia", + "body": "Quando ogni dipendenza è pronta, pre-start termina e Proxmox VE avvia l'applicazione." + } + ] + } + }, + { + "calloutInfo": { + "title": "Il gancio non ferma le dipendenze", + "body": "Le fasi post-start, pre-stop e post-stop non fanno nulla. Arrestare il CT principale lascia in esecuzione PostgreSQL, Redis, Valkey o il machine learning. L’hook ordina l’avvio; non trasforma diversi LXC in un unico processo." + } + }, + { + "table": { + "headers": [ + "Tipo", + "Esempio", + "Avvii successivi" + ], + "rows": [ + [ + "stack dipendente", + "Immich, Nextcloud, applicazione con PostgreSQL", + "L’hook del CT principale avvia le dipendenze e attende che siano pronte." + ], + [ + "Suite di applicazione", + "Suite Arr", + "Nessun membro principale: ogni LXC segue il proprio onboot" + ], + [ + "Applicazione singola", + "Jellyfin", + "Proxmox VE avvia direttamente LXC" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Controlla sullo stack nel suo complesso", + "blocks": [ + { + "list": { + "items": [ + "Ogni VMID esiste, è unico e mantiene l'identità di istanza prevista.", + "Ogni contratto appartiene allo stesso stack, mantiene il suo ruolo e la sua configurazione nativa registrata.", + "Il membro principale è l’ultimo in start_order e il primo in stop_order.", + "Nessun membro, volume o adattamento rootfs manca.", + "Il gancio punta al snippet ufficiale, il suo contenuto è invariato e il suo contratto corrisponde alla ricetta stack.", + "Le immagini e i digest osservati corrispondono agli archivi OCI preparati.", + "Dispositivi, profili GPU, montaggi, segreti ed endpoint corrispondono ancora ai contratti.", + "Quando tutte le dipendenze superano i controlli, l’endpoint dell’applicazione principale verifica l’integrazione fra i membri." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Gestione di uno stack installato", + "intro": "In Gestisci applicazioni OCI installate, qualsiasi membro porta all’intero stack. Il menu offre Aggiorna tutti i container dell’applicazione, Modifica percorsi e dispositivi aggiuntivi, Ricrea tutti i container con la configurazione salvata e Rimuovi: elimina l’applicazione e i suoi container. Ricrea ricostruisce ogni container dall’immagine originale tramite il digest salvato, senza cercarne una più recente, seguendo gli stessi passaggi di un aggiornamento. È disponibile per gli stack aggiornabili.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Qualsiasi membro", + "body": "Il contratto dei membri nomina il VMID principale e l'elenco completo dei membri." + }, + { + "title": "Lo stack è riproducibile", + "body": "Identità, contratti, gancio, adattamenti e una ripetizione coordinata per quella ricetta sono convalidati." + }, + { + "title": "Immagini prima", + "body": "L'applicazione non viene fermata fino a quando ogni digest è stato risolto, scaricato e verificato." + }, + { + "title": "Arresta i componenti e crea un backup dell'insieme", + "body": "I backup nativi verificati sono presi con lo stack fermato, quindi l'applicazione e i database appartengono allo stesso momento." + }, + { + "title": "Aggiornare e controllare ogni membro", + "body": "Adattamento, rete, montaggi, segreti e dispositivi vengono applicati nuovamente prima del controllo di integrità di ciascun membro." + }, + { + "title": "Pubblica o recupera tutto", + "body": "I nuovi contratti sono pubblicati solo quando ogni membro passa. Se uno fallisce, ogni membro viene ripristinato." + } + ] + } + }, + { + "table": { + "headers": [ + "Modifica", + "Quali cambiamenti", + "Cosa viene conservato" + ], + "rows": [ + [ + "Percorsi e dispositivi aggiuntivi", + "Vengono aggiunti o rimossi dal contenitore di applicazione, che si riavvia. Nulla è ricostruito", + "I dati dell'applicazione, il suo database e gli altri contenitori" + ], + [ + "Che cosa esegue il riconoscimento (Immich)", + "Il contenitore di apprendimento automatico prende l'immagine e i dispositivi del CPU o di un GPU dell'host. L'intera applicazione viene interrotta e aggiornata, come in un aggiornamento", + "La cache del modello, la libreria e il database. Se qualcosa fallisce, i contenitori precedenti e la scelta precedente vengono ripristinati" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Una pila senza un replay coordinato non è aggiornata", + "body": "Se la preparazione di uno stack non può essere riprodotta, l'aggiornamento viene rifiutato prima che lo stack venga interrotto. L'applicazione continua a funzionare come è." + } + } + ] + }, + { + "id": "failure", + "title": "6. Quando qualcosa fallisce", + "blocks": [ + { + "p": "Durante un'installazione, un errore si ferma e rimuove i contenitori incompleti che l'operazione ha creato, e il ponte privato se l'operazione l'ha creata. Una pila non viene pubblicata come valida fino al termine dell'intera sequenza." + }, + { + "p": "Un aggiornamento è transazionale: tutte le immagini sono preparate prima, poi lo stack si ferma, viene eseguito e verificato il backup di ogni membro, e solo allora è sostituito ogni rootfs. I membri iniziano uno per uno con il loro controllo sanitario; se uno fallisce, ogni membro viene ripristinato dalla stessa serie di backup, quindi il database e l'applicazione non appartengono mai a momenti diversi." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Preparare ogni immagine", + "stop": "Stop, primo membro principale", + "backup": "Backup verificato di ogni CT", + "replace": "Sostituisci il rootfs", + "healthy": "Tutti funzionanti?", + "yes": "Sì", + "no": "No", + "commit": "Pubblica i contratti", + "rollback": "Ripristinare ogni membro" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "Che cosa uno stack non include", + "blocks": [ + { + "list": { + "items": [ + "Una suite installata in un unico flusso, come la suite Arr, non è uno stack dipendente: i suoi contenitori non hanno ordine di partenza tra di loro.", + "La rete privata non sostituisce l'autenticazione, TLS o la configurazione di ogni applicazione.", + "Il backup di un LXC non contiene il ponte, il gancio o i contratti del resto dello stack.", + "Prowlarr, Sonarr o Radarr non ricevono indici, profili o fornitori da ProxMenux." + ] + } + } + ] + } + ] +} diff --git a/web/messages/it/docs/oci-manager/storage-network.json b/web/messages/it/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..60b32b16 --- /dev/null +++ b/web/messages/it/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Dati, percorsi e rete | ProxMenux", + "description": "Come OCI manager Apps conserva i dati di un container OCI: dischi del container, directory dell’host, mount con rclone, indirizzi e reti private." + }, + "header": { + "title": "Dati, percorsi e rete", + "description": "Cosa risiede nel rootfs, cosa viene conservato quando lo si sostituisce, come si condividono i dati tra container e come ciascuno ottiene il proprio indirizzo.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "La persistenza si decide prima di creare l’LXC", + "body": "I volumi dichiarati dall’immagine e dal suo file Compose vengono letti prima di creare il container. Ogni percorso da conservare dopo un aggiornamento diventa un punto di mount indipendente dal rootfs. In questo modo, il rootfs contiene solo ciò che appartiene all’immagine e può essere sostituito." + } + } + ] + }, + { + "id": "questions", + "title": "Cosa chiede l’installer", + "intro": "Il modello fornisce i percorsi necessari all’applicazione. Ciascuno viene collocato su un disco del container o in una directory dell’host; prima del riepilogo si possono aggiungere altri percorsi.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Percorsi necessari", + "body": "Vengono elencati /config, /data, librerie, download e tutti i volumi dichiarati dall’applicazione." + }, + { + "title": "Posizione", + "body": "Ogni percorso viene collocato su un disco del container o in una directory già esistente sull’host." + }, + { + "title": "Storage e dimensione", + "body": "Viene creato un disco del container su uno storage Proxmox VE con la dimensione indicata. L’installazione predefinita usa local-lvm; quella avanzata chiede lo storage di ogni percorso subito prima della dimensione e propone quello del rootfs. Il disco appare come vm-VMID-disk-N e viene collegato come mpN." + }, + { + "title": "Directory dell’host", + "body": "La directory dell’host viene indicata tramite il suo percorso. Se non esiste, viene creata e assegnata all’utente dell’host a cui viene mappato quello del container." + }, + { + "title": "Percorsi aggiuntivi", + "body": "Prima dell’installazione si possono aggiungere altre coppie formate da un percorso o volume dell’host e dal relativo percorso nel container." + }, + { + "title": "Riepilogo", + "body": "La mappatura completa viene mostrata prima della creazione del CT e salvata nel contratto dell’istanza." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "Le due posizioni persistenti", + "blocks": [ + { + "table": { + "headers": [ + "Proprietà", + "Disco del container", + "Directory dell’host" + ], + "rows": [ + [ + "Nella configurazione", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Backup del container (vzdump)", + "Incluso con backup=1", + "Non incluso" + ], + [ + "Dimensione", + "Fissa; si aumenta ridimensionando il punto di mount", + "Lo spazio libero del filesystem o del dataset dell’host" + ], + [ + "Altri container", + "Montato solo dal container a cui appartiene", + "La stessa directory può essere montata in più container" + ], + [ + "Snapshot e ripristino", + "Gestiti da Proxmox VE insieme al CT", + "Gestiti sullo storage dell’host" + ], + [ + "Rimozione dell’applicazione", + "Eliminato con il container", + "Conservato con il suo contenuto" + ], + [ + "Spostamento del CT su un altro nodo", + "Si sposta con il CT", + "Lo stesso percorso deve esistere sull’altro nodo" + ] + ] + } + }, + { + "p": "Il rootfs è riservato ai binari e al contenuto dell’immagine. Un aggiornamento o una ricreazione lo sostituisce senza toccare nessuno dei due tipi di punto di mount." + } + ] + }, + { + "id": "example", + "title": "Esempio: un container con entrambe le posizioni", + "blocks": [ + { + "code": { + "title": "Jellyfin installato come CT 151 su local-lvm (estratto)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "Un aggiornamento o una ricreazione sostituisce solo il rootfs: mp0 conserva utenti, librerie e impostazioni, mentre mp1 continua a mostrare gli stessi contenuti multimediali. Il ripristino del backup del CT recupera /config; se necessario, la directory dei contenuti multimediali viene ripristinata dal backup dello storage dell’host." + }, + { + "flow": { + "nodes": [ + { + "label": "Contratto", + "detail": "/config" + }, + { + "label": "Posizione", + "detail": "Disco del container\no directory dell’host" + }, + { + "label": "LXC", + "detail": "Sempre /config\nper l’applicazione" + } + ], + "caption": "L’applicazione vede il percorso dichiarato dall’immagine; cambia solo dove viene memorizzato." + } + } + ] + }, + { + "id": "shared", + "title": "Una directory dell’host, più container", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Directory dell’host", + "config": "Configurazione propria" + } + } + }, + { + "p": "Ogni container conserva la propria configurazione sul suo disco. La libreria o i download si trovano in un’unica directory dell’host, montata nello stesso percorso interno in tutti i container. Così il percorso in cui scrive un’applicazione è lo stesso da cui legge un’altra." + } + ] + }, + { + "id": "rclone", + "title": "Storage cloud tramite l’applicazione rclone", + "intro": "Oltre all’installazione, la scheda di rclone nel catalogo offre Abilita un mount in un container OCI rclone esistente. Viene montato un remote già creato e autorizzato nell’interfaccia web di rclone, che viene poi reso disponibile sull’host. Gli altri container possono usarlo come una directory dell’host.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Container e remote", + "body": "Il VMID del container rclone, il nome esatto del remote e, facoltativamente, un percorso al suo interno." + }, + { + "title": "Nome del mount e cache", + "body": "Il nome del mount e la modalità della cache VFS: off, minimal, writes o full (predefinita)." + }, + { + "title": "Viste pubblicate", + "body": "Una radice comune, /mnt/oci-shared per impostazione predefinita, contiene una vista in lettura e scrittura in /mnt/oci-shared/remotes/NAME e una in sola lettura in /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Attivazione", + "body": "Dopo la conferma, il CT viene arrestato, vengono configurati il suo comando di avvio e un hookscript di Proxmox VE, quindi viene riavviato. L’operazione attende che entrambe le viste siano montate sull’host." + } + ] + } + }, + { + "calloutInfo": { + "title": "Se il mount non viene attivato", + "body": "Viene ripristinata la configurazione precedente del container, che viene quindi riavviato. In questo modo, se l’attivazione fallisce, rclone resta com’era." + } + } + ] + }, + { + "id": "network", + "title": "Indirizzi e reti", + "intro": "Un’applicazione con un solo container ha bisogno di un indirizzo. Un’applicazione con più container richiede anche una rete stabile tra i suoi componenti.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Applicazione con un solo container", + "body": "Si sceglie il bridge di rete e si imposta DHCP oppure un indirizzo fisso in formato CIDR. L’LXC ha un proprio indirizzo e il riepilogo mostra gli URL completi dei servizi." + }, + { + "icon": "waypoints", + "title": "Applicazione con più container", + "body": "Viene individuata una sottorete libera e creato un bridge privato persistente. Ogni componente — applicazione, database e cache — riceve un indirizzo fisso su quella rete." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Indirizzo raggiungibile\nsolo per il servizio principale" + }, + { + "label": "LXC principale", + "detail": "Web / API\nLAN + rete privata" + }, + { + "label": "Rete privata", + "detail": "PostgreSQL · Valkey · ML\nindirizzi fissi" + } + ], + "caption": "Le dipendenze comunicano sulla rete privata e non hanno un indirizzo sulla LAN." + } + }, + { + "p": "Il contratto dello stack conserva bridge, sottorete, indirizzi e relazioni tra i servizi. L’aggiornamento dell’applicazione riutilizza la stessa topologia per tutti i componenti. ProxMenux Monitor apre il container principale al suo indirizzo LAN, non a quello della rete privata." + } + ] + }, + { + "id": "ownership", + "title": "Proprietà di file e directory", + "blocks": [ + { + "list": { + "items": [ + "Le nuove directory vengono create con UID e GID a cui viene mappato l’LXC non privilegiato.", + "Il proprietario delle directory esistenti non viene modificato ricorsivamente.", + "Socket, file di sistema e percorsi sensibili non vengono proposti come directory generiche dell’host." + ] + } + } + ] + }, + { + "id": "backup", + "title": "Cosa contiene un backup del container", + "blocks": [ + { + "table": { + "headers": [ + "Elemento", + "Nel backup vzdump del CT", + "Dove viene conservato" + ], + "rows": [ + [ + "Rootfs OCI", + "Sì", + "Nel backup del CT; può anche essere ricreato dall’immagine e dal contratto" + ], + [ + "Disco del container con backup=1", + "Sì", + "Il backup del CT" + ], + [ + "Directory dell’host", + "No", + "Il backup dello storage dell’host" + ], + [ + "Contratto dell’istanza", + "Sì, la copia conservata nel CT", + "/.proxmenux/oci-record.json nel CT; le copie aggiuntive dell’host e del cluster sono conservate separatamente" + ], + [ + "Applicazione con più container", + "Ogni componente nel proprio backup", + "I backup di tutti i componenti, più il contratto dello stack e il suo bridge di rete sull’host" + ] + ] + } + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/architecture.json b/web/messages/pt/docs/oci-manager/architecture.json new file mode 100644 index 00000000..3e6229c5 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "Como é adaptada uma imagem OCI | ProxMenux", + "description": "Do repositório da imagem e do respetivo ficheiro Compose a um modelo que pode ser revisto, um plano de implementação e um LXC nativo do Proxmox VE, sem Docker no seu interior." + }, + "header": { + "title": "Como é adaptada uma imagem OCI", + "description": "Do repositório da imagem e do respetivo ficheiro Compose a um modelo que pode ser revisto, um plano de implementação e um LXC nativo, sem instalar Docker no seu interior.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "O processo de adaptação", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Repositório da imagem", + "converter": "Conversor", + "template": "Modelo JSON", + "blockers": "Sem impedimentos?", + "no": "Não", + "yes": "Sim", + "review": "Revisão / overlay", + "plan": "Plano de implementação", + "lxc": "LXC nativo" + } + } + }, + { + "p": "O conversor lê a imagem e o ficheiro Compose publicados pelo projeto e gera um modelo JSON. Se houver requisitos ainda não adaptados que impeçam a instalação, o modelo é revisto antes de ser publicado no catálogo. Um ficheiro de ajustes revistos, chamado overlay, resolve esses impedimentos. Só os modelos sem impedimentos pendentes são propostos para instalação." + } + ] + }, + { + "id": "template", + "title": "O que o modelo preserva", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Identidade da imagem", + "body": "Repositório, tag de atualização contínua, arquitetura, digest resolvido e revisão de origem." + }, + { + "icon": "braces", + "title": "Contrato do contentor", + "body": "Entrypoint, Cmd, ambiente, utilizador, diretório de trabalho, sinal de paragem, portas e volumes." + }, + { + "icon": "layers", + "title": "Adaptação ao Proxmox VE", + "body": "Recursos, segurança, pontos de montagem, dispositivos, sysctls, verificações de estado e as adaptações necessárias em cada caso, com a respetiva justificação." + }, + { + "icon": "shield", + "title": "Compatibilidade", + "body": "Chaves suportadas, requisitos por adaptar que impedem a instalação e estado de cada validação." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "O OCI fornece o processo; o Compose fornece o ambiente", + "blocks": [ + { + "table": { + "headers": [ + "Origem", + "Exemplo", + "Resultado nativo" + ], + "rows": [ + [ + "Metadados OCI", + "Entrypoint, Cmd, User", + "O Proxmox VE importa-os ao criar o CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Entradas de ambiente LXC, mpN e devN" + ], + [ + "Perfil do ProxMenux", + "GPU, verificação de estado, credenciais", + "Perguntas e adaptações revistas" + ], + [ + "Utilizador", + "VMID, armazenamento, rede", + "O contrato da instância" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "O que acontece durante a instalação", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Resolver a referência", + "body": "O registo é consultado, a arquitetura do host é selecionada e é fixado o digest exato para o qual aponta a tag de atualização contínua." + }, + { + "title": "Descarregar e verificar", + "body": "O Skopeo descarrega a imagem como um arquivo OCI. Antes de criar qualquer recurso, cada camada é verificada através do respetivo digest e descomprimida. Se a descarga estiver danificada, é repetida uma vez antes de a instalação ser interrompida." + }, + { + "title": "Criar o sistema de ficheiros", + "body": "O pct create cria o rootfs a partir do arquivo e preserva os metadados oficiais do processo da imagem." + }, + { + "title": "Ligar os recursos", + "body": "São aplicados os volumes, a rede, o ambiente, os dispositivos e os perfis de segurança declarados." + }, + { + "title": "Consola", + "body": "A saída da consola do contentor é guardada no host. A consola do Proxmox VE abre uma shell se a imagem incluir uma." + }, + { + "title": "Verificar", + "body": "No primeiro arranque, aguarda-se que o contentor tenha um endereço e que o serviço responda. Em caso de falha, a instalação não é apresentada como concluída com sucesso." + }, + { + "title": "Registar a configuração", + "body": "A configuração efetiva é guardada no contrato da instância utilizado nas atualizações e recriações." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Exemplo: uma imagem com /config e /downloads", + "intro": "Uma definição Compose habitual e a configuração do Proxmox VE que dela resulta. Os caminhos esperados pela aplicação não mudam.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (excerto)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "O mp0 é um segundo disco pertencente ao contentor, com o nome vm-VMID-disk-N no armazenamento selecionado. É montado em /config e, com backup=1, incluído na cópia de segurança do contentor. O mp1 não cria um disco: monta o diretório do host /srv/downloads em /downloads dentro do LXC. A porta 8080 não é mapeada: o LXC tem um endereço próprio e o serviço responde nesse endereço." + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/custom-image.json b/web/messages/pt/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..d8fc1551 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Instalar uma imagem fora do catálogo | ProxMenux", + "description": "Adapte uma imagem OCI a partir de um ficheiro Compose, de um URL, de um comando docker run ou de uma referência de imagem e reveja o que o ProxMenux consegue reproduzir antes de a instalar." + }, + "header": { + "title": "Instalar uma imagem fora do catálogo", + "description": "A sua imagem é adaptada a partir do ficheiro Compose, de um comando docker run ou apenas da referência. O resultado é apresentado para revisão antes de criar qualquer LXC.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "O mesmo conversor e o mesmo instalador", + "body": "A opção Instalar uma imagem fora do catálogo utiliza o mesmo conversor e instalador dos modelos do catálogo. A diferença é que o contrato é gerado nesse momento a partir da definição fornecida e validado antes de criar qualquer LXC." + } + } + ] + }, + { + "id": "sources", + "title": "Como é descrita a imagem", + "intro": "O primeiro ecrã pergunta como é descrita a imagem. Quatro opções recebem uma definição a adaptar, uma quinta utiliza apenas o nome da imagem e outras três leem um oci-compose já escrito.", + "blocks": [ + { + "table": { + "headers": [ + "Opção", + "O que é lido" + ], + "rows": [ + [ + "Colar o ficheiro Compose no terminal", + "Cole o conteúdo YAML no terminal e termine com Ctrl+D." + ], + [ + "Ler o ficheiro Compose deste host", + "Um caminho no nó, /root/docker-compose.yml por predefinição. O ficheiro tem de existir e não ultrapassar 256 KiB." + ], + [ + "Descarregar o ficheiro Compose de um endereço", + "Um endereço http:// ou https:// que disponibilize o YAML em bruto. São descarregados no máximo 256 KiB." + ], + [ + "Colar o comando docker run no terminal", + "O comando publicado pelo projeto. São adaptadas as portas, o ambiente, os volumes, os dispositivos, as capabilities, o utilizador, a memória partilhada e as restantes opções suportadas." + ], + [ + "Apenas a referência da imagem, sem ficheiro Compose", + "Uma referência como ghcr.io/user/application:latest. O registo é consultado e os metadados OCI da imagem são preservados." + ], + [ + "Colar um oci-compose no terminal", + "Cole o JSON no terminal e termine com Ctrl+D. Não é convertido: é validado e instalado tal como está escrito." + ], + [ + "Ler um oci-compose deste host", + "Um ficheiro JSON no nó, /root/oci-compose.json por predefinição, com um máximo de 2 MiB." + ], + [ + "Descarregar um oci-compose de um endereço", + "Um endereço http:// ou https:// que disponibilize o ficheiro JSON. São descarregados no máximo 2 MiB." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Menu para escolher como descrever uma imagem fora do catálogo", + "caption": "Formas de descrever uma imagem fora do catálogo." + } + } + ] + }, + { + "id": "reference-only", + "title": "O que fornece apenas a referência da imagem", + "intro": "A imagem contém os metadados do seu processo, mas não todo o ambiente que um ficheiro Compose costuma acrescentar.", + "blocks": [ + { + "table": { + "headers": [ + "Lido da imagem", + "Ausente da imagem", + "Consequência" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir e o ambiente incorporado", + "Variáveis presentes apenas na documentação", + "São acrescentadas na instalação ou em Modificar" + ], + [ + "Volumes declarados pela imagem", + "Diretórios do host indicados apenas no ficheiro Compose", + "Os caminhos adicionais são acrescentados antes da instalação" + ], + [ + "Portas EXPOSE", + "O URL, o protocolo ou a verificação funcional do serviço", + "São confirmados a porta e o método de verificação do serviço" + ], + [ + "Arquiteturas e digest no registo", + "Dispositivos, privilégios ou dependências externas", + "Não são ativados sem uma definição que os solicite" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Rever antes de instalar", + "intro": "Após adaptar a definição, o ProxMenux apresenta como a interpretou antes de perguntar o VMID, os recursos ou o armazenamento.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analisar", + "body": "São lidos os serviços, a imagem, as portas, os caminhos, o ambiente, os dispositivos, a segurança e a verificação de estado." + }, + { + "title": "Consultar o registo", + "body": "A imagem tem de existir no registo e são apresentadas as arquiteturas publicadas. Um registo privado é consultado com a conta guardada para ele." + }, + { + "title": "Listar o que não é aplicado", + "body": "São listados os labels, as redes Docker, as definições Swarm e outras chaves sem efeito num LXC." + }, + { + "title": "Bloquear opções sem equivalente seguro", + "body": "As chaves sem equivalente seguro aparecem em Opções sem equivalente e impedem a continuação da instalação. Nenhuma é descartada sem aviso." + }, + { + "title": "Pedir segredos", + "body": "As variáveis cujo nome indica uma palavra-passe, token ou chave são pedidas como dados sensíveis durante a instalação." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Resumo da interpretação do ficheiro Compose pelo ProxMenux", + "caption": "Resumo apresentado antes de o instalador perguntar o VMID e os recursos." + } + }, + { + "p": "Após aceitar o resumo, segue-se o mesmo assistente do catálogo: nome, modo predefinido ou avançado, VMID, CPU, memória, rede, arranque com o nó, caminhos persistentes e adicionais, dispositivos compatíveis e resumo final." + } + ] + }, + { + "id": "examples", + "title": "Exemplos de entrada", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "Um oci-compose escrito à mão", + "intro": "Um oci-compose é o ficheiro JSON a partir do qual se instala uma aplicação. O catálogo guarda um por aplicação. O mesmo formato pode ser escrito à mão e instalado com esta opção.", + "blocks": [ + { + "p": "Num ficheiro escrito à mão, o autor define a imagem, as portas, as variáveis, os volumes e as perguntas da instalação. O ProxMenux completa o resto ao ler o ficheiro. O formato permite expressar o que o Compose não prevê: o texto de uma pergunta, uma palavra-passe gerada durante a instalação, um perfil de GPU, o proprietário de cada volume, a verificação do primeiro arranque ou as credenciais apresentadas no fim." + }, + { + "steps": { + "items": [ + { + "title": "Verificar", + "body": "Os ficheiros com erros não são instalados. Cada erro indica o campo afetado; se o JSON for inválido, são indicadas a linha e a coluna." + }, + { + "title": "Rever", + "body": "Um ficheiro válido apresenta o que instala: imagem, endereço web, portas, caminhos persistentes, variáveis pedidas pelo instalador e ficheiros que serão escritos." + }, + { + "title": "Instalar", + "body": "A instalação segue o mesmo processo das aplicações do catálogo, com definições predefinidas ou avançadas." + } + ] + } + }, + { + "calloutWarning": { + "title": "Um ficheiro que define os privilégios", + "body": "Um oci-compose determina o que é montado no contentor e com que privilégios. A revisão mostra-o antes da instalação: instale apenas ficheiros de fontes de confiança." + } + }, + { + "p": "O README do motor OCI descreve o formato bloco a bloco, com dois exemplos completos. O registo de uma instalação também pode ser lido desta forma: apenas é utilizado o modelo original, sem nenhuma das respostas dessa instalação." + } + ] + }, + { + "id": "private-registries", + "title": "Imagens em registos privados", + "intro": "Uma imagem num registo privado, como um pacote privado do GitHub ou um repositório privado do Docker Hub, é consultada com uma conta guardada no ProxMenux.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "O registo recusa o acesso à imagem", + "body": "Quando o registo indica que a imagem requer uma conta, a revisão propõe iniciar sessão nesse registo e consulta novamente a imagem." + }, + { + "title": "Iniciar sessão", + "body": "Um utilizador e uma palavra-passe ou token com permissão para descarregar a imagem. O registo verifica-os e só são guardados se forem aceites." + }, + { + "title": "Instalar e manter atualizado", + "body": "A conta é utilizada para descarregar a imagem na instalação e também nas atualizações, em Modificar, Recriar, no watchdog e na verificação de atualizações do Monitor, em qualquer nó do cluster." + } + ] + } + }, + { + "p": "Contas de registos, no menu principal do OCI manager Apps, apresenta as contas guardadas por registo e utilizador, nunca os seus segredos. Cada conta permite verificar, descarregar ou instalar uma imagem, atualizar as credenciais ou removê-las." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io): o utilizador é a conta GitHub e a palavra-passe é um token de acesso pessoal com a permissão read:packages.", + "Docker Hub (docker.io): o utilizador é a conta Docker Hub e a palavra-passe é um token de acesso com permissão de leitura.", + "Outros registos, incluindo os que usam uma porta própria como registry.example.com:5000: o utilizador e a palavra-passe ou token fornecidos por esse registo." + ] + } + }, + { + "calloutInfo": { + "title": "Onde são guardadas as contas", + "body": "Em /etc/pve/priv/proxmenux/registry-auth.json, legível apenas por root, na mesma pasta onde o Proxmox guarda as palavras-passe de armazenamento. /etc/pve é partilhado por todos os nós do cluster, pelo que uma conta adicionada num nó é utilizada em todos. A cópia do host inclui o ficheiro e a desinstalação do ProxMenux remove-o de todos os nós." + } + } + ] + }, + { + "id": "limits", + "title": "Definições que não são instaladas", + "blocks": [ + { + "list": { + "items": [ + "Um Dockerfile sem imagem publicada: primeiro é necessário construir a imagem e publicá-la num registo OCI.", + "Um ficheiro Compose com vários serviços: é bloqueado porque esta opção instala um único serviço por contentor, mesmo que vários serviços utilizem a mesma imagem.", + "configs, segredos externos ou formatos de dispositivo que não possam ser adaptados sem ambiguidade.", + "Substituições de shell como $(command): é necessário escrever diretamente o valor resultante.", + "Opções docker run não reconhecidas e --env-file: utilize antes um ficheiro Compose com valores explícitos." + ] + } + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/hardware.json b/web/messages/pt/docs/oci-manager/hardware.json new file mode 100644 index 00000000..7f98c0e9 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Dispositivos e aceleração ProxMenux", + "description": "Como o OCI Manager Apps passa GPU, NVIDIA, Coral, USB, FUSE e outros dispositivos para um recipiente OCI como recursos Proxmox VE nativos validados." + }, + "header": { + "title": "Dispositivos e aceleração", + "description": "GPU, NVIDIA, Coral, USB, FUSE e dispositivos de bloco tornam-se os recursos de Proxmox VE nativos validados do recipiente.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "O dispositivo de que a aplicação necessita, não a máquina inteira", + "blocks": [ + { + "p": "Um dispositivo solicitado pelo arquivo Compose ou pelo perfil da aplicação torna-se uma entrada de devN ou uma montagem LXC. Pedir um GPU, um dispositivo USB ou um Coral não torna o recipiente privilegiado." + }, + { + "flow": { + "nodes": [ + { + "label": "Inventário da máquina", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "fornecedor e\npermissões verificadas" + }, + { + "label": "LXC", + "detail": "mesmo dispositivo\nGID efetivo" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "De onde vem o pedido do dispositivo", + "blocks": [ + { + "table": { + "headers": [ + "Origem", + "O que é lido", + "O que o instalador faz" + ], + "rows": [ + [ + "Docker Compose", + "Pedidos de devices, group_add, deploy.resources e NVIDIA", + "Cada requisito torna-se um pedido de dispositivo mostrado para revisão" + ], + [ + "Perfil do catálogo", + "O suporte que o aplicativo realmente tem para GPU, Coral, OpenCL, USB ou FUSE", + "Apenas são oferecidas as opções validadas para essa imagem" + ], + [ + "Metadados e documentação da imagem", + "VA-API, Selkies, LinuxServer mods ou o tempo de execução NVIDIA", + "As variáveis documentadas e preparação são adicionadas" + ], + [ + "Seleção do usuário", + "Apenas CPU, Intel/AMD, OpenCL, NVIDIA ou um dispositivo opcional", + "A seleção é armazenada no contrato de instância" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Os dispositivos detectados não são anexados automaticamente", + "body": "O host é inventariado, mas apenas dispositivos declarados pelo arquivo Compose ou por um perfil compatível são oferecidos e anexados. Um GPU, dongle USB ou Coral presente no host não é exposto a cada LXC." + } + } + ] + }, + { + "id": "identify", + "title": "Como o dispositivo da máquina é identificado", + "intro": "Antes de o LXC ser modificado, o dispositivo é lido no host e corresponde ao perfil escolhido.", + "blocks": [ + { + "table": { + "headers": [ + "Tipo", + "Identidade", + "Validação" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* e /sys/class/drm/NODE/device/vendor", + "Um dispositivo de caracteres com o fornecedor 0x8086 (Intel) ou 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "O nó de renderização, mais /dev/kfd quando o perfil precisa dele", + "Existência, tipo, fornecedor, permissões e compatibilidade declarada" + ], + [ + "AMD ROCm", + "O nó de renderização, /dev/kfd e a geração que o controlador de computação relata em /sys/class/kfd/kfd/topology/nodes/*/properties", + "Uma geração para a qual a imagem ROCm do aplicativo inclui kernels de computação — as GPUs RDNA2 citadas e as mais recentes — é oferecida sem adaptação. As outras GPUs dessas famílias, como Radeon 680M e 780M, são oferecidas como experimentais e nunca são a opção sugerida: o ROCm não as suporta oficialmente, o aplicativo as apresenta como a geração de sua família (HSA_OVERRIDE_GFX_VERSION) e o instalador pede confirmação antes de baixar a imagem maior. Uma GPU mais antiga, como a integrada de um Ryzen 5000U, não é oferecida e o Immich mantém o reconhecimento na CPU." + ], + [ + "NVIDIA", + "nvidia-smi e nvidia-container-cli", + "GPU, UUID, PCI bus, versão driver, Toolkit, /dev/nvidia* nós, binários e bibliotecas" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N e sua ligação em /sys/dev/char/MAJOR:MINOR", + "Nó de caracteres, major/menor, proprietário, GID e permissões" + ], + [ + "USB e serial", + "/dev/ttyUSB*, /dev/ttyACM* ou /dev/bus/usb/BBB/DDD", + "Nó de caracteres; para USB também fornecedor, produto e série quando sysfs publica-los" + ], + [ + "KVM, TUN, FUSE, vídeo e SCSI genérico", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN ou /dev/sgN", + "Caminho suportado, tipo de nó e permissões efetivas" + ], + [ + "Unidade óptica", + "/dev/srN", + "Um dispositivo de bloco" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "O que acontece durante a instalação", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "O modelo oferece seus perfis", + "body": "Por exemplo, CPU apenas, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL ou NVIDIA. As opções pertencem à imagem, não a um menu comum." + }, + { + "title": "Um perfil é escolhido", + "body": "Ele define os nós do dispositivo, ambiente, mods ou tempo de execução que a aplicação precisa." + }, + { + "title": "É proposto um caminho", + "body": "Para DRM, /dev/dri/renderD128, que pode ser alterado em um host com vários nós de renderização. Para USB ou serial, o nó específico é selecionado." + }, + { + "title": "Validação", + "body": "Existência, tipo, fornecedor permitido, permissões e GID são verificados. Um desencontro impede a operação." + }, + { + "title": "O contrato está escrito", + "body": "Caminho, modo, GID, acesso de escrita e perfil são gravados para atualizações e recriações." + }, + { + "title": "Anexar e testar", + "body": "pct set adiciona a entrada devN e o acesso é então verificado dentro do LXC. As imagens LinuxServer também são verificadas como usuário abc." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Como aparece na configuração LXC", + "intro": "Valores ilustrativos: dev0 e dev1 são as atribuições de slots livres Proxmox VE, e renderD128, apex_0 e o GID dependem do hardware do nó.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Um dispositivo USB específico", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "O GID é lido com stat no host e escrito na entrada devN; os grupos render e video não são assumidos como tendo um número fixo. O dispositivo mantém o mesmo caminho /dev dentro do LXC, onde os próprios mecanismos da aplicação procuram por ele." + } + ] + }, + { + "id": "profiles", + "title": "Perfis que uma imagem pode oferecer", + "blocks": [ + { + "table": { + "headers": [ + "Perfil", + "Configuração resultante", + "Oferecido quando" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "Nó de renderização /dev/dri", + "O aplicativo suporta aceleração de vídeo" + ], + [ + "OpenCL", + "Nó de renderização, /dev/kfd quando necessário e o mod oficial", + "A imagem ou perfil documenta- a" + ], + [ + "NVIDIA", + "Dispositivos de driver e bibliotecas da máquina", + "O host tem um driver funcionando e o NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 ou o barramento USB", + "O perfil declara suporte ao Coral (Frigate)" + ], + [ + "USB, série, FUSE", + "Um único dispositivo, uma árvore validada ou uma funcionalidade LXC", + "O contrato pede-o." + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Requisito do nó: NVIDIA Container Toolkit", + "body": "Um driver funcional em Proxmox VE não é suficiente para dar um NVIDIA GPU a uma imagem OCI. O OCI Manager Apps usa nvidia-container-cli, do NVIDIA Container Toolkit, para identificar os dispositivos e obter os binários e bibliotecas que correspondem ao driver carregado." + } + }, + { + "p": "O ProxMenux NVIDIA instalador instala o NVIDIA Container Toolkit do repositório oficial NVIDIA juntamente com o driver. Ele verifica seus quatro pacotes, valida o nvidia-container-cli e registra o resultado na registro de alterações do Audit & Report." + }, + { + "p": "Estes dois comandos na máquina mostram se o driver e o Toolkit estão disponíveis:" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "Em uma máquina onde o driver foi instalado por outros meios, o Toolkit é instalado a partir do repositório estável oficial:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "O inventário que o OCI manager Apps usa é a saída de:" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "A configuração do tempo de execução do Docker não está envolvida", + "body": "Os contêineres são LXC nativos e não usam daemon Docker, portanto nvidia-ctk runtime configure --runtime=docker não faz parte do processo: o ProxMenux consulta nvidia-container-cli diretamente e configura os dispositivos e montagens LXC. Os comandos e as plataformas suportadas são mantidos no guia de instalação do NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventário do driver", + "body": "nvidia-container-cli lista os nós do dispositivo, binários, firmware e bibliotecas do driver instalado." + }, + { + "icon": "refresh", + "title": "Nenhuma versão fixa", + "body": "O modelo não nomeia arquivos de bibliotecas. O perfil é gerado a partir da máquina atual." + }, + { + "icon": "shield", + "title": "Montagens somente de leitura", + "body": "As bibliotecas host são montadas somente para leitura em vez de serem copiadas para o recipiente." + }, + { + "icon": "hardDrive", + "title": "Alterações do driver", + "body": "Após uma mudança de driver, o inventário é gerado novamente antes do início dos LXCs afetados." + } + ] + } + }, + { + "code": { + "title": "Resultado NVIDIA (simplificado)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Passar apenas /dev/nvidia0 não é suficiente. Os componentes de espaço de usuário do driver carregado são montados somente de leitura, e nvidia-smi então é executado dentro do LXC para comparar GPU, UUID, PCI bus e versão com o inventário host." + } + ] + }, + { + "id": "usb", + "title": "Coral USB, serial e USB", + "blocks": [ + { + "calloutWarning": { + "title": "Numeração USB pode mudar", + "body": "Um caminho como o /dev/bus/usb/003/004 pode mudar quando o dispositivo é reconectado ou a máquina reinicia. O perfil registra fornecedor, produto e série quando estão disponíveis, mas um novo endereço de barramento não é remapeado automaticamente." + } + }, + { + "p": "Um periférico é dado pelo seu nó específico: /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 ou /dev/bus/usb/BBB/DDD. Não é aceite passar todo o /dev. O Coral é oferecido apenas para aplicações cujo perfil o declara." + } + ] + }, + { + "id": "trees", + "title": "Árvores de dispositivos e recursos LXC", + "blocks": [ + { + "table": { + "headers": [ + "Pedido", + "Configuração resultante", + "Escopo" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd ou /dev/bus/usb", + "Cada nó de caracteres da árvore recebe sua própria entrada devN com o modo host e GID", + "Apenas a árvore solicitada, e não o resto do /dev" + ], + [ + "/dev/fuse", + "O nó e, quando o perfil precisa dele, o recurso fuse=1", + "O FUSE sozinho não publica montagens para outros LXCs" + ], + [ + "/dev/net/tun", + "Uma entrada devN no mesmo caminho dentro do LXC", + "A configuração VPN ou rede permanece na aplicação" + ], + [ + "/dev/kvm", + "Uma entrada devN validada", + "Oferecido apenas quando o contrato o solicitar" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Confirmações por nível de risco", + "blocks": [ + { + "p": "Dispositivos específicos, privilégio opcional, privilégio exigido, relaxamento AppArmor ou seccomp e acesso ao espaço de nomes PID do hospedeiro são tratados como casos separados, não sob uma etiqueta genérica privilegiada. Cada opção com risco é explicada e confirmada durante a instalação." + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/index.json b/web/messages/pt/docs/oci-manager/index.json new file mode 100644 index 00000000..8249fc49 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Execute imagens OCI oficiais como contentores LXC nativos do Proxmox VE, com dados persistentes, acesso a dispositivos, aplicações com vários contentores e atualizações transacionais." + }, + "header": { + "title": "OCI manager Apps", + "description": "As imagens OCI oficiais são executadas como contentores LXC nativos do Proxmox VE. A imagem continua a ser a publicada pelo respetivo responsável; o ProxMenux reproduz o ambiente que o Docker Compose teria criado à sua volta.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "Uma imagem OCI, um LXC nativo", + "body": "O Docker Engine não é executado dentro dos contentores. O Proxmox VE importa o sistema de ficheiros da imagem e os seus metadados OCI. A aplicação passa a ser o processo principal de um LXC nativo, gerido com as mesmas ferramentas dos restantes contentores do nó." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Origem", + "detail": "Imagem OCI\nCompose e documentação" + }, + { + "label": "ProxMenux", + "detail": "Contrato JSON\nvalidação e planeamento" + }, + { + "label": "Proxmox VE", + "detail": "LXC nativo\nvolumes e dispositivos" + } + ], + "caption": "É utilizada a aplicação original; o seu ambiente é descrito de forma declarativa e reproduzido." + } + } + ] + }, + { + "id": "where", + "title": "Onde encontrar", + "intro": "No nó Proxmox, como root, abra OCI manager Apps (Technology Preview) no menu principal do ProxMenux. O primeiro ecrã apresenta:", + "blocks": [ + { + "list": { + "items": [ + "Pesquisar aplicações: pesquisar por nome em todo o catálogo.", + "Todas as aplicações: apresentar o catálogo completo e o número de aplicações.", + "Gerir aplicações OCI instaladas: atualizar, modificar, remover ou recuperar as aplicações já instaladas.", + "Contas de registos: gerir as contas utilizadas para instalar e atualizar imagens de registos privados.", + "Instalar uma imagem fora do catálogo: adaptar um ficheiro Compose, um comando docker run ou uma referência de imagem.", + "As categorias do catálogo, cada uma com o respetivo número de aplicações." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Ecrã principal do OCI manager Apps com pesquisa, catálogo completo, gestão de aplicações, imagens fora do catálogo e categorias.", + "caption": "Ecrã principal do OCI manager Apps." + } + }, + { + "p": "Ao selecionar uma aplicação, são apresentadas a descrição, a imagem utilizada e duas opções de instalação. Instalar com as definições predefinidas pergunta apenas o essencial: armazenamento, localização dos dados, endereço e arranque. Instalar com as definições avançadas percorre todas as decisões por ordem: VMID e recursos, armazenamento de cada caminho, bridge de rede e dispositivos." + }, + { + "p": "As aplicações efetivamente testadas apresentam Verificado nesta vista e um ✓ nas listas, quer o teste tenha sido realizado no laboratório do ProxMenux quer pela comunidade." + } + ] + }, + { + "id": "beta", + "title": "Por que mantém a designação Technology Preview", + "blocks": [ + { + "p": "O OCI manager Apps mantém a designação Technology Preview que o Proxmox VE atribui aos contentores de aplicações criados a partir de imagens OCI, cujo suporte foi introduzido no Proxmox VE 9.1. A designação refere-se à tecnologia em que o OCI manager Apps se baseia, não ao motor do ProxMenux: o comportamento destes contentores ainda pode mudar entre versões do Proxmox VE. Será mantida até que o Proxmox VE considere esse suporte estável." + }, + { + "p": "O motor do OCI manager Apps é testado no laboratório do ProxMenux: instalação, atualização, modificação, recriação, cópia de segurança, restauro, migração e watchdog. Cada aplicação é verificada separadamente. As que foram efetivamente testadas apresentam Verificado e um ✓ nas listas." + } + ] + }, + { + "id": "translation", + "title": "Como o Docker descreve os requisitos e como o OCI manager Apps os concretiza", + "blocks": [ + { + "table": { + "headers": [ + "Requisito", + "Como é expresso no Docker", + "Como é concretizado pelo OCI manager Apps" + ], + "rows": [ + [ + "Executar a aplicação", + "image, entrypoint, command", + "O rootfs OCI e o processo principal do LXC" + ], + [ + "Preservar a configuração", + "volumes: /config", + "Um volume persistente mpN incluído na cópia de segurança ou um diretório do host." + ], + [ + "Disponibilizar o serviço", + "ports", + "Um endereço próprio para o LXC e o URL de acesso ao serviço." + ], + [ + "Utilizar hardware", + "devices, group_add", + "Entradas devN com o GID efetivo do host e um perfil validado." + ], + [ + "Ligar as dependências", + "networks, depends_on", + "Uma bridge de rede privada, endereços fixos, ordem de arranque e verificações de estado." + ], + [ + "Atualizar", + "Descarregar com pull e recriar", + "Um novo rootfs com o mesmo contrato de persistência." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "O que é preservado e o que muda", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Preservado", + "body": "A imagem oficial, os caminhos internos, o ambiente, o processo de arranque e a documentação de utilização." + }, + { + "icon": "boxes", + "title": "Adaptado", + "body": "O ambiente de execução: rede, persistência, dispositivos, permissões e dependências utilizam os mecanismos LXC nativos do Proxmox VE." + }, + { + "icon": "shield", + "title": "Sem equivalente", + "body": "Uma opção Compose sem equivalente seguro é identificada como impedimento à instalação. Os modelos com impedimentos não são propostos para instalação e as opções sensíveis exigem confirmação durante o assistente." + }, + { + "icon": "refresh", + "title": "Registado", + "body": "O digest da imagem, os recursos, os caminhos, a rede, os dispositivos e a pertença a uma stack são guardados no contrato da instância. As atualizações e recriações reproduzem esse contrato." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Páginas desta secção", + "blocks": [ + { + "next": { + "items": [ + { + "label": "Como é adaptada uma imagem OCI", + "href": "/docs/oci-manager/architecture", + "tail": "Da imagem e do respetivo ficheiro Compose a um LXC nativo." + }, + { + "label": "Instalar uma imagem fora do catálogo", + "href": "/docs/oci-manager/custom-image", + "tail": "Um ficheiro Compose, um comando docker run ou uma referência de imagem." + }, + { + "label": "Dados, caminhos e rede", + "href": "/docs/oci-manager/storage-network", + "tail": "Volumes do contentor, diretórios do host, montagens com rclone e endereços." + }, + { + "label": "Dispositivos e aceleração", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB e outros dispositivos." + }, + { + "label": "Aplicações com vários contentores", + "href": "/docs/oci-manager/stacks", + "tail": "Aplicação, base de dados e cache em LXC coordenados." + }, + { + "label": "Instalar, atualizar e modificar", + "href": "/docs/oci-manager/lifecycle", + "tail": "O contrato da instância e as respetivas operações." + }, + { + "label": "No ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versões, atualizações, saída da consola e terminal." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/lifecycle.json b/web/messages/pt/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..ece467a3 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Instalar, atualizar e modificar | ProxMenux", + "description": "O contrato de instância de um contentor OCI e as operações que o utilizam: atualização, modificação, remoção, recuperação de uma operação interrompida e recuperação após um restauro neste ou noutro host." + }, + "header": { + "title": "Instalar, atualizar e modificar", + "description": "Cada instância mantém um contrato reprodutível, permitindo substituir o seu rootfs sem perder a configuração ou os dados persistentes.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "O contrato de instância", + "blocks": [ + { + "p": "Após a instalação, a configuração efetiva é guardada em /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json. Não é uma cópia do ficheiro Compose original: é o contrato reprodutível do LXC existente neste host." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "Uma cópia do contrato fica dentro do contentor, em /.proxmenux/oci-record.json, e só pode ser lida pelo utilizador root do contentor. Assim, uma cópia de segurança do contentor inclui sempre o contrato que estava em vigor nesse momento. Uma segunda cópia é mantida em /etc/pve/priv/proxmenux/oci, partilhado por todos os nós do cluster e legível apenas por root do host. Ambas são escritas após cada instalação, atualização e alteração." + } + ] + }, + { + "id": "manage", + "title": "Gerenciar aplicativos OCI instalados", + "intro": "Esta opção da tela principal lista as instâncias registradas com sua aplicação e imagem. Cada um é verificado em relação ao seu contrato antes de qualquer ação; um CT que já não corresponde ao seu registro não é modificado nem excluído. A mesma atualização e o mesmo editor são oferecidos na aba Atualizações do ProxMenux Monitor.", + "blocks": [ + { + "table": { + "headers": [ + "Opção", + "O que muda", + "O que é guardado" + ], + "rows": [ + [ + "Atualizar a imagem com a configuração salva", + "O rootfs é substituído pela imagem que o canal salvo publica hoje", + "Contrato, discos de contêiner, diretórios de host, rede e dispositivos" + ], + [ + "Modificar: editar recursos, rede, caminhos e GPU", + "O editor abre com o contrato atual; o CT é reconstruído com as alterações", + "Os dados dos discos de contentores e pastas da máquina" + ], + [ + "Watchdog: reinicie o aplicativo quando ele falhar", + "Se o aplicativo é iniciado novamente quando ele pára por conta própria. Nada é reconstruído.", + "Toda a instalação; somente a escolha é salva no contrato" + ], + [ + "Remover: eliminar a aplicação e os seus contentores", + "O LXC, ou cada membro de uma pilha, e seus contratos são excluídos", + "Directórios da máquina, com o seu conteúdo" + ] + ] + } + }, + { + "p": "Para uma aplicação com vários contentores, o menu oferece Atualizar todos os contentores da aplicação, Modificar caminhos e dispositivos adicionais, Recriar todos os contentores com a configuração guardada, o watchdog e a remoção. Modificar adiciona ou remove caminhos e dispositivos adicionais do contentor da aplicação sem recriar nada e, no Immich, altera o componente que executa o reconhecimento. Recriar reconstrói cada contentor a partir da imagem com que foi instalado, sem procurar uma mais recente." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Lista de aplicativos OCI instalados e as opções de atualização, modificação e remoção", + "caption": "Gerencie aplicativos OCI instalados." + } + } + ] + }, + { + "id": "update", + "title": "Uma atualização transacional", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Usuário", + "registry": "Registo de OCI", + "update": "Atualizar instância", + "resolve": "Resolver a etiqueta", + "verify": "Verificar o arquivo e executar as verificações prévias", + "backup": "Pare e faça backup do CT", + "import": "Importar o novo rootfs", + "reapply": "Aplicar o contrato novamente", + "healthy": "íntegro", + "failure": "falha", + "commit": "Contrato publicado com o novo digest", + "restored": "A instância anterior foi restaurada" + } + } + }, + { + "list": { + "items": [ + "Quando o registro ainda serve o digest instalado, nada é baixado e a instância não é tocada.", + "A nova imagem é verificada camada a camada antes de parar o CT. Uma transferência que chega danificada é repetida uma segunda vez; uma imagem já em cache que não passa na verificação é transferida novamente.", + "As definições alteradas no Proxmox VE após a instalação (memória, swap, núcleos, limite e prioridade da CPU, arranque com o nó) são mantidas e incluídas no novo contrato.", + "A interface LAN adicionada pela suite Arr e pelas aplicações com vários contentores (net1) e a ordem de arranque (startup) são repostas sem alterações no novo contentor.", + "Qualquer outra diferença entre o CT e o seu contrato interrompe a atualização antes de parar o CT." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Antes", + "detail": "rootfs A\n/config mp0\nDirectório da máquina /media" + }, + { + "label": "Atualizar", + "detail": "substitui apenas\no rootfs" + }, + { + "label": "Após", + "detail": "rootfs B\n/config mp0\nDirectório da máquina /media" + } + ] + } + }, + { + "p": "Antes de parar seja o que for, a operação verifica o espaço necessário para a cópia de segurança: os dados utilizados nos volumes incluídos, mais uma margem. A cópia é guardada junto ao registo da aplicação, no disco de sistema do host. Se não houver espaço suficiente, o menu pede outro armazenamento do host que aceite cópias de segurança. A cópia é criada aí e eliminada no fim da operação. A escolha fica guardada para essa aplicação, incluindo as atualizações agendadas. Se nenhum armazenamento tiver espaço suficiente, a operação é recusada e a aplicação fica como estava. O espaço necessário para a nova imagem também é verificado antes da transferência." + } + ] + }, + { + "id": "recovery", + "title": "Recuperar uma operação interrompida", + "intro": "Uma atualização e uma mudança usam uma transação persistente. Se o processo, o terminal ou o nó são interrompidos após a parada do CT, a operação não é tomada como terminada.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Marcador pendente", + "body": "Ao abrir Gerir aplicações OCI instaladas, o estado guardado mostra que a substituição nunca chegou a ser publicada." + }, + { + "title": "Ver o estado", + "body": "Mostra a fase atingida sem alterar contentores ou dados." + }, + { + "title": "Recuperar a instalação anterior", + "body": "Restaura a cópia de segurança nativa verificada, criada antes da substituição, e o contrato anterior." + }, + { + "title": "Pilha como unidade", + "body": "Para um aplicativo multi-contentor, cada membro é recuperado do mesmo ponto de transação, não apenas o selecionado." + } + ] + } + }, + { + "calloutWarning": { + "title": "Os diretórios da máquina estão fora do rollback", + "body": "A cópia de segurança abrange o rootfs e os discos do contentor nela incluídos. Um diretório do host não é reposto no estado anterior, pois outros LXC podem utilizar os seus dados." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: reiniciar uma aplicação que pára", + "intro": "O Proxmox não tem uma política de reinício: quando termina o processo de um contentor de aplicação, o contentor fica parado. Com o watchdog ativo, uma aplicação que para por si própria é iniciada novamente. Para uma aplicação OCI, faz o que restart: unless-stopped faz num ficheiro Compose.", + "blocks": [ + { + "table": { + "headers": [ + "O que acontece?", + "Com o watchdog ativo" + ], + "rows": [ + [ + "O aplicativo trava ou seu processo termina", + "O contentor é iniciado novamente em segundos" + ], + [ + "É parado ou desligado do Proxmox, do Monitor ou com pct", + "Fica parado até alguém o iniciar" + ], + [ + "Um backup, uma migração ou uma operação ProxMenux está trabalhando nele", + "Nada é feito até que isso acabe." + ], + [ + "Já estava parado quando o host arrancou", + "Fica parado; isso é determinado por Iniciar com o Proxmox" + ], + [ + "Ele continua parando logo após começar", + "Uma aplicação que volta a parar segundos depois de arrancar, ou que não arranca, tem cinco tentativas de início, com intervalos de 30 segundos, 1, 2 e 5 minutos. Depois fica parada e é enviada uma notificação" + ] + ] + } + }, + { + "p": "O instalador faz esta pergunta nos dois modos de instalação e propõe o valor declarado na receita da aplicação. Mais tarde, a opção pode ser alterada em Gerir aplicações OCI instaladas ou no interruptor Watchdog do contentor em ProxMenux Monitor; aplica-se a todos os contentores de uma aplicação com vários contentores. Um único serviço do host, proxmenux-oci-watchdog.service, vigia todas as aplicações que o pediram; a sua instalação aparece no separador Alterações de Audit & Report." + }, + { + "calloutInfo": { + "title": "O que a máquina não pode dizer", + "body": "O código de saída da aplicação não chega ao host, pelo que uma saída normal e uma falha são indistinguíveis e ambas provocam um reinício, como always e unless-stopped no Docker. Uma paragem pedida fora do Proxmox, com lxc-stop ou systemctl, não deixa uma tarefa de paragem e é interpretada como uma paragem espontânea." + } + } + ] + }, + { + "id": "restore", + "title": "Backup, restauração e outra máquina", + "intro": "Uma cópia de segurança feita com vzdump ou Proxmox Backup Server inclui o contentor, os discos do contentor selecionados para a cópia e a cópia do seu contrato. O que a instalação guarda no host não faz parte dela. Gerir aplicações OCI instaladas deteta contentores restaurados num host sem o respetivo contrato, após uma nova instalação do Proxmox ou noutro host, e oferece o novo registo.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Restaurar os contentores no Proxmox", + "body": "A partir do armazenamento de cópias de segurança, com o ID original ou qualquer ID livre. Uma aplicação com vários contentores precisa de todos eles." + }, + { + "title": "Abrir Gerenciar aplicativos OCI instalados", + "body": "Os contentores restaurados são listados e é oferecida a recuperação. O botão Recuperar do separador Atualizações de ProxMenux Monitor abre a mesma recuperação." + }, + { + "title": "Verificar antes de mudar", + "body": "A recuperação verifica se o host tem tudo o que cada aplicação precisa. Uma aplicação que não possa ser recuperada por completo fica como estava, com indicação do motivo." + }, + { + "title": "Registar e iniciar", + "body": "O contrato está registrado neste host e o que a instalação manteve nele é escrito novamente. Iniciar as aplicações é uma pergunta separada, respondida com Não quando os recipientes originais ainda estão em execução em outro host." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Diálogo que lista os contentores restaurados e oferece a sua recuperação", + "caption": "A recuperação oferecida quando Gerenciar aplicativos OCI instalados abre." + } + }, + { + "table": { + "headers": [ + "O que o host guardava", + "Após a recuperação" + ], + "rows": [ + [ + "Contrato de instância", + "Registado a partir da cópia no contentor, verificada face à configuração restaurada pelo Proxmox" + ], + [ + "Rede privada de uma aplicação multi-contentor", + "Criado novamente com a mesma ponte e sub-rede; os endereços fixos dos contentores não mudam" + ], + [ + "Ordem de arranque de uma aplicação com vários contentores", + "O hookscript de dependências e o seu contrato são instalados novamente; os snippets são ativados no armazenamento local quando nenhum armazenamento os aceita" + ], + [ + "Sysctls de rede e ficheiro de monitorização da máquina", + "Escrito novamente em /etc/pve/proxmenux" + ], + [ + "Ambiente de execução NVIDIA", + "O hook de um contentor sem privilégios é instalado novamente. Um contentor privilegiado recebe os ficheiros do controlador deste host em vez dos do host de origem" + ], + [ + "Montagem Rclone", + "O hookscript e os programas que publicam a montagem são escritos novamente, com as mesmas vistas na máquina" + ], + [ + "Regra da firewall do host para um monitor do host", + "É pedida novamente, para a porta web e a sub-rede da bridge neste host" + ], + [ + "lost+found de cada disco restaurado", + "É removido quando está vazio; um restauro cria-o e algumas aplicações não arrancam se estiver presente nos seus dados" + ], + [ + "Discos restaurados em outro armazenamento", + "O contrato é atualizado para o armazenamento em que eles estão agora" + ] + ] + } + }, + { + "p": "Um recipiente restaurado com outro ID mantém sua aplicação. O contrato, seu log de console, seus sysctls de rede, o hookscript de uma montagem Rclone e a ordem de início de uma aplicação multi-contentor são registrados com os IDs que os contêineres têm neste host." + }, + { + "table": { + "headers": [ + "O que impede uma recuperação", + "O que fazer" + ], + "rows": [ + [ + "Falta um contentor de uma aplicação com vários contentores", + "Restaure-o também; o aplicativo é recuperado inteiro" + ], + [ + "A sub- rede privada já é usada nesta máquina", + "A recuperação é cancelada e nada é alterado: os endereços dos contentores são fixados e não são movidos para outra subrede" + ], + [ + "Não existe uma pasta da máquina", + "Montar ou criar com os seus dados; uma cópia de segurança do contentor não inclui pastas da máquina" + ], + [ + "Não existe um dispositivo nesta máquina", + "Ligue-o ou remova-o do contentor no Proxmox" + ], + [ + "Um aplicativo com NVIDIA em uma máquina sem o driver", + "Instale o controlador NVIDIA e o Container Toolkit" + ], + [ + "O backup foi feito antes da cópia do contrato existir", + "O recipiente permanece como um LXC normal e não é oferecido novamente" + ] + ] + } + }, + { + "calloutInfo": { + "title": "Um contentor que volta", + "body": "Um contentor restaurado sobre si próprio a partir de uma cópia de segurança anterior, reposto num snapshot ou devolvido de outro nó transporta o contrato do estado em que se encontra. Se for selecionado para uma operação e o seu contrato diferir do deste host, a operação não começa e é oferecida a recuperação do contentor." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Resultado da recuperação com a rede privada, a ordem de início e os contentores registados", + "caption": "Resultado de uma recuperação, passo a passo." + } + } + ] + }, + { + "id": "remove", + "title": "Removendo uma aplicação OCI", + "blocks": [ + { + "p": "Antes da confirmação é criado um resumo da configuração real: os contentores removidos, os dados eliminados com eles, a rede privada libertada e os diretórios do host mantidos. A confirmação tem Não como resposta predefinida, pois os dados dos discos eliminados não podem ser recuperados depois." + }, + { + "table": { + "headers": [ + "Recurso", + "Ao remover", + "Justificação" + ], + "rows": [ + [ + "rootfs e discos de contentores", + "Removido", + "Pertencem apenas ao recipiente." + ], + [ + "Pasta da máquina", + "Mantido, com o seu conteúdo", + "Outras aplicações podem utilizá-lo" + ], + [ + "Ficheiros do contentor no host", + "Removido", + "O registo da consola, os parâmetros sysctl de rede, o hookscript da montagem Rclone e o registo no separador App do ProxMenux Monitor não servem outros contentores" + ], + [ + "Ficheiros partilhados por várias instalações", + "Excluído com a última instalação que os usa", + "O arquivo host-monitor e o hookscript de dependência de aplicativos multi-contentor" + ], + [ + "Contrato de instância", + "Excluído após uma remoção bem- sucedida", + "Nenhum CT está mais associado a ele" + ], + [ + "Ponte privada de uma pilha", + "Libertada com a pilha", + "Não tem mais membros para conectar" + ], + [ + "Rede partilhada pela suite Arr", + "Libertada com a última aplicação da suite", + "Cada aplicação da suite é independente e é removida por conta própria" + ], + [ + "Um único membro de uma pilha", + "Não removido isoladamente", + "O aplicativo inteiro é removido, então nenhuma pilha fica incompleta" + ], + [ + "Um recipiente em outro nó do cluster", + "Não removido", + "Seus arquivos de registro e host estão no nó onde foi instalado: ele é removido lá, depois de migrar de volta" + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Resumo de remoção com os recipientes, dados e diretórios do host afetados", + "caption": "O resumo de remoção, construído a partir da configuração real." + } + } + ] + }, + { + "id": "archives", + "title": "Imagens transferidas e espaço da máquina", + "blocks": [ + { + "p": "O arquivo OCI é usado para criar o rootfs; o CT em execução não o lê. Após uma instalação ou atualização, são listados os arquivos transferidos nessa operação, com o respetivo tamanho, e é oferecida a sua eliminação. Eliminá-los liberta espaço sem afetar o contentor ou os seus dados." + }, + { + "calloutInfo": { + "title": "As atualizações não precisam do arquivo", + "body": "Sem o arquivo, uma atualização resolve o canal salvo e baixa o novo digest. Com ele, um arquivo é reutilizado apenas quando sua referência e integridade correspondem ao que é solicitado." + } + } + ] + }, + { + "id": "registry", + "title": "Registo e limpeza", + "blocks": [ + { + "p": "Ao abrir Gerir aplicações OCI instaladas, são primeiro removidos os restos de contentores que não existem em nenhum nó do cluster: o registo guardado como histórico após a eliminação na interface do Proxmox e os ficheiros do host deixados por uma remoção. Um registo com uma operação incompleta é mantido, pois a sua cópia de segurança ainda pode ser necessária. Os volumes e diretórios do host nunca são eliminados por dedução." + } + ] + }, + { + "id": "cluster", + "title": "Em um cluster: migração e alta disponibilidade", + "intro": "Um contentor OCI é um LXC normal do Proxmox: pode ser migrado ou gerido por HA como os restantes, com as mesmas limitações. O que precisa para arrancar é guardado onde todos os nós do cluster o encontram.", + "blocks": [ + { + "table": { + "headers": [ + "Parte", + "Em outro nó do cluster" + ], + "rows": [ + [ + "Registo da consola", + "O contentor cria /var/log/proxmenux/oci antes de arrancar, no nó que o executa. Cada nó mantém o registo dos arranques que executou." + ], + [ + "Sysctls de rede e monitor host", + "Mantido em /etc/pve/proxmenux, que cada nó do cluster compartilha, então um contêiner migrado os encontra." + ], + [ + "Discos de contentores", + "O Proxmox move-os com o contentor. O HA precisa dos dados disponíveis no nó de destino, através de armazenamento partilhado ou de uma configuração de replicação suportada." + ], + [ + "Directórios e dispositivos da máquina", + "As mesmas regras que qualquer LXC: um diretório host deve existir no nó alvo e ser marcado como compartilhado, e um GPU, Coral ou NPU deve estar presente lá." + ], + [ + "Registo ProxMenux", + "O contrato permanece no nó onde a aplicação foi instalada, e todos os nós leem a cópia mantida em /etc/pve/priv/proxmenux/oci. Uma aplicação com um só contentor que migra é registada no nó de destino ao abrir Gerir aplicações OCI instaladas ou ao iniciar uma operação para ela. Uma aplicação com vários contentores é oferecida para recuperação nesse nó, pois a sua rede privada tem de ser criada aí." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Não para alta disponibilidade", + "body": "Uma aplicação multi-contentor atinge seus membros através de uma ponte privada do nó onde foi instalado, então seus membros permanecem nesse nó. Um monitor host, como o Glances no modo host ou o Netdata, monitora o nó em que ele é executado; movendo-o monitoraria outro nó." + } + }, + { + "p": "Um backup restaurado fora do cluster recebe os arquivos de /etc/pve/proxmenux que o recipiente usa quando o aplicativo é recuperado." + } + ] + }, + { + "id": "channel", + "title": "Uma etiqueta rolling não é uma atualização sem supervisão", + "blocks": [ + { + "p": "O catálogo instala a etiqueta rolling publicada pelo responsável, mas o digest efetivo é resolvido e registado e só muda através de uma atualização explícita, com verificações prévias e rollback. O ProxMenux Monitor compara o digest instalado com o publicado pelo registo e apresenta um aviso e uma notificação quando há uma nova imagem disponível." + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/monitor.json b/web/messages/pt/docs/oci-manager/monitor.json new file mode 100644 index 00000000..68a4775b --- /dev/null +++ b/web/messages/pt/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "Contêineres OCI no ProxMenux Monitor | ProxMenux", + "description": "O que o ProxMenux Monitor mostra para um container instalado pelo OCI Manager Apps: versões de aplicativos e imagens, novas imagens, saída do console e o terminal Proxmox VE." + }, + "header": { + "title": "No ProxMenux Monitor", + "description": "Um recipiente instalado pelo OCI Manager Apps aparece em VMs & LXCs como qualquer outro LXC. Seu modal lê o registro de instalação: versões de aplicação e imagem, novas imagens, saída de console e terminal.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "O registro é a fonte", + "body": "O OCI Manager Apps sabe o que ele instalou e de onde veio. O Monitor lê esse registro de instalação em vez de sondar o recipiente, por isso os dados são os mesmos se o recipiente está em execução ou parado." + } + }, + { + "table": { + "headers": [ + "Página", + "O que muda para um recipiente OCI" + ], + "rows": [ + [ + "Estado", + "Um interruptor Watchdog em Iniciar no arranque: o aplicativo é iniciado novamente quando pára por conta própria" + ], + [ + "Aplicativo", + "O aplicativo é identificado a partir do registro, e as atualizações são monitoradas pela imagem" + ], + [ + "Atualizações", + "A imagem é atualizada ou a aplicação modificada, com o mesmo fluxo que o menu OCI" + ], + [ + "Montagens", + "Discos de contentores e pastas de máquinas, com a sua utilização" + ], + [ + "Registros", + "Apenas para recipientes OCI: a saída do console da aplicação" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "Aplicativo", + "intro": "A aba App oferece o aplicativo instalado como uma detecção, com o nome, logotipo, porta e esquema do registro. Registrando-o abre o editor com o método Imagem OCI (instalada por ProxMenux), que não precisa de configuração.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Aplicação", + "body": "A versão da aplicação dentro da imagem, lida a partir da imagem em si: o seu ambiente ou a sua etiqueta org.opencontainers.image.version. É informativo." + }, + { + "icon": "archive", + "title": "Imagem", + "body": "A data de compilação e o digest da imagem instalada. A atualização é decidida aqui: o digest instalado é comparado com o que o registro publica hoje para a mesma tag." + } + ] + } + }, + { + "list": { + "items": [ + "Quando o registro publica um novo digest, o cartão mostra Nova imagem com sua data e digest, mesmo que a versão do aplicativo dentro não mudou: uma reconstrução em uma base atualizada é uma atualização.", + "Quando os digests coincidem, o cartão mostra Versão.", + "O cartão faz links para o repositório da imagem: seu projeto GitHub, ou sua página Docker Hub para uma imagem oficial.", + "O link de acesso usa o endereço LAN do recipiente, também para o principal membro de um aplicativo multi-contentor, que tem um segundo endereço em sua rede privada.", + "O botão Atualizar dados lê novamente o registro de instalação e o registro de imagens. Não são oferecidas opções para procurar ou registrar outro aplicativo: o contêiner contém exatamente o aplicativo de seu registro." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "Página de aplicação de um contentor OCI com a versão da aplicação, a imagem e o link do repositório", + "caption": "Página da aplicação de um recipiente instalado pelo OCI Manager Apps." + } + }, + { + "p": "A verificação é executada uma vez por dia com as verificações de atualização do Monitor e o Atualizar dados a executa imediatamente. Uma nova imagem é enviada como uma notificação através dos canais configurados em Notificações. A atualização é aplicada na aba Atualizações." + } + ] + }, + { + "id": "updates", + "title": "Atualizações", + "intro": "Para um contêiner instalado pelo OCI Manager Apps a aba Atualizações mostra o aplicativo com sua imagem instalada e, quando o registro publica uma, a nova imagem, no mesmo formato que qualquer outra aplicação. Os atualizadores de pacotes e aplicativos de um LXC comum não aparecem.", + "blocks": [ + { + "table": { + "headers": [ + "Botão", + "O que abre" + ], + "rows": [ + [ + "Atualizar", + "A atualização de Gerenciar aplicativos OCI instalados para este contêiner, no terminal do Monitor. Pode ser executada com ou sem uma nova imagem; sem imagem nova, nada muda. Em um aplicativo com vários contêineres, atualiza todos os membros." + ], + [ + "Modificar", + "O editor da aplicação (recursos, rede, caminhos e GPU). Em uma aplicação multi-contentor adiciona ou remove os caminhos e dispositivos extras do recipiente de aplicação e, em Immich, altera o que executa o reconhecimento." + ], + [ + "Recuperar", + "Substitui a atualização quando uma operação no recipiente foi interrompida e abre sua recuperação." + ], + [ + "Recuperar (contêiner restaurado)", + "Exibido para um contêiner restaurado de backup que não tem contrato neste host, em Aplicativo OCI restaurado. Abre a recuperação no terminal do Monitor; Atualizar e Modificar aparecem após o registro." + ] + ] + } + }, + { + "p": "Alterações externas, diretórios host e aplicativos multi-contentores são tratados como no menu OCI. Em uma aplicação multi-contentor, os botões e as opções aparecem apenas no recipiente principal; os outros membros mostram sua imagem, uma nota de que eles são atualizados do recipiente principal e um botão que a abre. Quando o terminal fecha, a imagem, o registro e as montagens são lidos novamente." + }, + { + "table": { + "headers": [ + "Opção", + "Comportamento" + ], + "rows": [ + [ + "Manter o backup feito antes da atualização", + "Cada atualização faz backup do recipiente para restaurá-lo se a atualização falhar. Com esta opção, o mesmo backup é mantido no armazenamento escolhido e aparece entre os backups do CT; em Proxmox Backup Server um backup é escrito antes da atualização." + ], + [ + "Atualizações agendadas", + "A imagem é atualizada no horário escolhido somente se o registro publicar uma nova, com a opção de esperar que ela tenha 1, 3, 7 ou 14 dias. Um contêiner com alterações feitas fora do ProxMenux é ignorado e reportado." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Registros", + "intro": "A guia Logs aparece apenas para recipientes instalados pelo OCI Manager Apps, entre Mounts e Backups. Ele mostra a saída padrão e erro do processo principal da imagem, a mesma saída docker logs mostra para um recipiente Docker.", + "blocks": [ + { + "list": { + "items": [ + "A saída é mantida no host em /var/log/proxmenux/oci/VMID.console.log (modo 0600), desde o primeiro início e através de reinícios, para que possa ser lido com o recipiente parado.", + "O arquivo é rodado em 10 MB, mantendo três cópias comprimidas (/etc/logrotate.d/proxmenux-oci).", + "Cada início do recipiente é marcado na saída. A aba mostra a saída desde o último início por padrão; Histórico completo também mostra as anteriores.", + "Quando o aplicativo é removido, sua saída é removida com ele.", + "São exibidas as últimas 100, 500 ou 1000 linhas. Enquanto o contêiner está em execução, as novas linhas são acompanhadas ao vivo; rolar para cima pausa o acompanhamento e Acompanhar o retoma.", + "Um filtro mostra apenas as linhas que contêm um texto, e Baixar salva as linhas carregadas.", + "Os códigos de cores são removidos e uma linha que uma barra de progresso redesenha é mostrada em seu estado final.", + "A aba lê o arquivo a cada abertura, sem usar uma cópia em cache." + ] + } + }, + { + "calloutInfo": { + "title": "Credenciais de primeiro início", + "body": "Imagens que imprimem uma senha gerada em seu primeiro início deixam-na nesta saída, como docker logs faz. O instalador lê-o de lá para o mostrar em seu resumo." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Aba Logs com a saída do console de um contêiner OCI, seletor de linhas, filtro e botão de acompanhamento", + "caption": "Saída do console de um recipiente OCI." + } + } + ] + }, + { + "id": "terminal", + "title": "Consola Proxmox VE", + "blocks": [ + { + "p": "Uma imagem OCI executa seu próprio processo como PID 1 e nenhum serviço de login, então o console padrão de um LXC abriria um terminal onde nada responde. Containers instalados pelo OCI Manager Apps são criados com cmode: shell: o console Proxmox VE abre uma shell com lxc-attach, o equivalente a docker exec, com a aplicação em execução intocada." + }, + { + "list": { + "items": [ + "O shell é o que /etc/passwd atribui ao root na imagem. Se root não tiver shell ou a imagem não incluir nenhum, o console padrão será mantido.", + "O shell é root dentro do recipiente, sem senha. Quem pode abri-lo é decidido pela permissão VM.Console de Proxmox VE.", + "O terminal do Monitor entra no recipiente com pct enter, que funciona da mesma forma." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Montagens", + "blocks": [ + { + "p": "A aba Montagens lista os discos do contêiner e seus diretórios do host. O uso de um disco em armazenamento de blocos, como LVM-thin, é lido do sistema de arquivos desse disco, montado apenas dentro do contêiner." + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/stacks.json b/web/messages/pt/docs/oci-manager/stacks.json new file mode 100644 index 00000000..9a1c3625 --- /dev/null +++ b/web/messages/pt/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Aplicações multi-contêineres ProxMenux", + "description": "Como o OCI Manager Apps transforma um aplicativo com seu banco de dados e cache em LXCs nativos coordenados: plano, rede privada, gancho de dependência, validação e atualizações transacionais." + }, + "header": { + "title": "Aplicações com vários contentores", + "description": "Uma aplicação com seu banco de dados, cache e outros serviços torna-se vários LXCs nativos coordenados, instalados e atualizados como um.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Um aplicativo para o usuário, vários LXCs para Proxmox VE", + "body": "Uma definição multi-contentor é uma entrada do catálogo. O instalador cria um LXC nativo por serviço e mantém suas dependências explícitas. Immich, Nextcloud, Paperless-ngx e Tandoor são instalados desta forma." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Plano de pilha", + "app": "Aplicação LXC", + "appNet": "LAN + privado", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "privado" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. O plano, antes de existir qualquer recipiente", + "blocks": [ + { + "p": "Nenhum LXC é criado enquanto a definição Compose é interpretada. Um plano completo vem em primeiro lugar, com cada membro, imagem, VMID, rede, caminho, segredo, ordem e verificação de saúde. Se o plano não for consistente, a instalação não começa." + }, + { + "table": { + "headers": [ + "Elemento do plano", + "Conteúdo", + "Verificado antes de criar qualquer coisa" + ], + "rows": [ + [ + "Membros", + "Aplicação principal, banco de dados, cache, aprendizado de máquina e outras dependências", + "VMIDs exclusivos, papéis conhecidos e exatamente um membro principal" + ], + [ + "Imagens", + "Referência rolling, arquitetura e digest resolvido de cada serviço", + "Todos existem, suportam a arquitetura e passam pela verificação de integridade do OCI" + ], + [ + "Rede", + "Ponte privada, sub-rede, endereço fixo por serviço e acesso LAN para o membro principal", + "Sem colisão com pontes ou subredes existentes e sem endereço repetido" + ], + [ + "Persistência", + "Discos de container, diretórios de host, proprietários e backup", + "Nenhum caminho sobreposto, armazenamento disponível e permissões declaradas" + ], + [ + "Segredos", + "Senha do banco de dados, chaves do aplicativo e credenciais iniciais", + "Gerado uma vez e dado apenas aos membros que os usam" + ], + [ + "Ciclo de vida", + "Ordem de início, ordem de parada e uma verificação de funcionamento por membro", + "A aplicação principal começa por último e pára primeiro" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Criação, membro por membro", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Reserve cada VMID", + "body": "O inventário Proxmox VE e o registro de instância são verificados. Um CT existente não é adotado e um contrato que ainda pertence a outra instância não é reutilizado." + }, + { + "title": "Preparar cada imagem", + "body": "Todas as imagens são resolvidas, baixadas e verificadas antes da criação do primeiro container." + }, + { + "title": "Criar cada rootfs", + "body": "Os metadados oficiais do OCI são importados, e a identidade da instância ProxMenux e o papel de membro são adicionados." + }, + { + "title": "Rede privada", + "body": "Uma ponte e uma sub-rede são criadas e cada membro recebe seu endereço fixo; apenas o membro principal também recebe a interface LAN." + }, + { + "title": "Persistência", + "body": "Cada banco de dados e configuração obtém seu próprio disco de contêiner; somente os dados destinados a serem compartilhados usam diretórios de host." + }, + { + "title": "Ambiente", + "body": "Endpoints internos, segredos compartilhados e variáveis de serviço são escritos. O aplicativo atinge suas dependências em seus endereços privados reservados." + }, + { + "title": "Registro", + "body": "Cada membro registra sua configuração nativa, as mudanças rootfs que as atualizações têm que reproduzir e sua relação com a pilha." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Iniciar e verificar cada recipiente", + "intro": "Um LXC criado não significa um serviço pronto. As dependências começam em ordem e cada um passa uma verificação específica para o seu serviço.", + "blocks": [ + { + "table": { + "headers": [ + "Serviço", + "Verificação", + "O que mostra" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready no CT com o host, usuário e banco de dados esperados", + "O servidor aceita conexões para o banco de dados configurado" + ], + [ + "Redis / Valkey", + "redis-cli ou valkey-cli PING contra o seu endereço privado", + "O broker escuta e responde" + ], + [ + "aprendizado de máquina Immich", + "HTTP GET /ping, mais uma verificação do tempo de execução GPU selecionado", + "O serviço responde e a aceleração solicitada não passou a usar a CPU" + ], + [ + "Nextcloud", + "GET /status.php com installed=true, maintenance=false e needsDbUpgrade=false", + "A inicialização terminou sem migração pendente" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP no endereço LAN e porto real do serviço", + "O frontend e suas dependências servem a aplicação" + ], + [ + "Aplicação principal", + "O ponto final do seu modelo, por exemplo, /api/server/ping em Immich", + "A pilha inteira funciona através da aplicação que usa as dependências" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Em execução não significa funcionando corretamente", + "body": "O estado em execução apenas diz que o processo LXC existe. Quando o serviço oferece uma verificação melhor, é utilizado uma verificação exec ou HTTP com tempo limite. Se um membro para ou falha na verificação, a pilha não é declarada instalada." + } + } + ] + }, + { + "id": "hook", + "title": "4. O gancho de dependência", + "blocks": [ + { + "p": "O gancho é definido apenas no recipiente principal de uma pilha dependente. Proxmox VE mantém o script como um trecho e o executa como o hookscript daquele CT. A receita da pilha não é escrita no script: ela vive em um contrato privado separado." + }, + { + "codeGrid": { + "items": [ + { + "title": "Configuração principal do CT", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Contrato privado (exemplo)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Fonte completa de proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "O script é o mesmo para cada pilha. VMIDs, nomes, tipos de verificação e timeouts vêm do contrato privado /etc/pve/priv/proxmenux-stack-VMID.json de cada pilha." + }, + { + "steps": { + "items": [ + { + "title": "O Proxmox VE chama pre-start", + "body": "Antes do início do CT principal, o gancho funciona com o seu VMID e a fase do ciclo de vida." + }, + { + "title": "Um bloqueio por pilha", + "body": "flock em /run/lock/proxmenux-stack-VMID.lock impede duas sequências de início ao mesmo tempo." + }, + { + "title": "O contrato é lido e validado", + "body": "São exigidos um esquema conhecido, dependências numéricas, rótulos, uma verificação exec, http ou running e um tempo limite positivo." + }, + { + "title": "Dependências em ordem", + "body": "Cada CT deve existir; um parado é iniciado e um já em execução não é reiniciado." + }, + { + "title": "Esperar o serviço ficar pronto", + "body": "A verificação é executada a cada dois segundos, e o CT também é confirmado para estar ainda em execução." + }, + { + "title": "O CT principal começa", + "body": "Quando cada dependência está pronta, termina pré-inicial e o Proxmox VE inicia a aplicação." + } + ] + } + }, + { + "calloutInfo": { + "title": "O gancho não para dependências", + "body": "As fases pós-início, pré-parar e pós-parar não fazem nada. Parar o CT principal deixa PostgreSQL, Redis, Valkey ou aprendizado de máquina em execução. O gancho ordena o início; ele não transforma vários LXCs em um processo." + } + }, + { + "table": { + "headers": [ + "Tipo", + "Exemplo", + "Início posterior" + ], + "rows": [ + [ + "Pilha dependente", + "Immich, Nextcloud, aplicação com PostgreSQL", + "O gancho do CT principal inicia as dependências e espera por eles" + ], + [ + "Conjunto de aplicativos", + "Conjunto de aplicações Arr", + "Nenhum membro principal: cada LXC segue o seu próprio onboot" + ], + [ + "Aplicação única", + "Jellyfin", + "Proxmox VE inicia esse LXC diretamente" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Verifica a pilha como um todo", + "blocks": [ + { + "list": { + "items": [ + "Cada VMID existe, é único e mantém a identidade de instância esperada.", + "Cada contrato pertence à mesma pilha, mantém seu papel e sua configuração nativa gravada.", + "O membro principal é o último em start_order e o primeiro em stop_order.", + "Nenhum membro, volume ou adaptação rootfs está faltando.", + "O gancho aponta para o trecho oficial, seu conteúdo é inalterado e seu contrato corresponde à receita da pilha.", + "As imagens e digests observados correspondem aos arquivos OCI preparados.", + "Dispositivos, perfis GPU, montagens, segredos e endpoints ainda correspondem aos contratos.", + "Após cada dependência passar, o ponto final da aplicação principal verifica a integração entre os membros." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Gerenciando uma pilha instalada", + "intro": "Em Gerenciar aplicativos OCI instalados, qualquer membro leva à pilha inteira. O menu oferece Atualizar todos os contêineres do aplicativo, Modificar caminhos e dispositivos extras, Recriar todos os contêineres com a configuração salva e Remover: excluir o aplicativo e seus contêineres. Recriar reconstrói cada contêiner da imagem original pelo digest salvo, sem procurar uma mais nova, seguindo os mesmos passos de uma atualização. A opção aparece para pilhas que podem ser atualizadas.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Qualquer membro", + "body": "O contrato dos membros nomeia o VMID principal e a lista completa de membros." + }, + { + "title": "A pilha é reprodutível", + "body": "Identificações, contratos, gancho, adaptações e uma repetição coordenada para essa receita são validadas." + }, + { + "title": "Primeiro as imagens", + "body": "O aplicativo não é parado até que cada digest tenha sido resolvido, baixado e verificado." + }, + { + "title": "Parar e fazer backup do conjunto", + "body": "Os backups nativos verificados são feitos com a pilha parada, então a aplicação e os bancos de dados pertencem ao mesmo momento." + }, + { + "title": "Atualizar e verificar cada membro", + "body": "Adaptação, rede, montagens, segredos e dispositivos são novamente aplicados antes do exame de saúde de cada membro." + }, + { + "title": "Publicar ou recuperar tudo", + "body": "Os novos contratos só são publicados quando todos os membros passam. Se alguém falhar, cada membro é restaurado." + } + ] + } + }, + { + "table": { + "headers": [ + "Modificar", + "O que muda", + "O que é guardado" + ], + "rows": [ + [ + "Caminhos e dispositivos adicionais", + "Eles são adicionados ou removidos do recipiente de aplicação, que reinicia. Nada é reconstruído.", + "Os dados da aplicação, da sua base de dados e dos outros contentores" + ], + [ + "O que executa o reconhecimento (Immich)", + "O recipiente de aprendizado de máquina leva a imagem e os dispositivos do CPU ou de um GPU do hospedeiro. Todo o aplicativo é parado e atualizado, como em uma atualização", + "O cache do modelo, a biblioteca e o banco de dados. Se algo falhar, os recipientes anteriores e a escolha anterior são restaurados" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Uma pilha sem uma repetição coordenada não é atualizada", + "body": "Se a preparação de uma pilha não puder ser reproduzida, a atualização é recusada antes de a pilha ser parada. A aplicação continua a correr como está." + } + } + ] + }, + { + "id": "failure", + "title": "6. Quando algo falha", + "blocks": [ + { + "p": "Durante uma instalação, um erro para e remove os contêineres incompletos que a operação criou, e a ponte privada se a operação criou. Uma pilha não é publicada como válida até que toda a sequência termine." + }, + { + "p": "Uma atualização é transacional: todas as imagens são preparadas primeiro, depois a pilha pára, cada membro é feito backup e o backup verificado, e só então é substituído cada rootfs. Os membros começam um a um com seu exame de saúde; se um falha, cada membro é restaurado a partir do mesmo conjunto de backups, de modo que o banco de dados eo aplicativo nunca pertencem a momentos diferentes." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Preparar cada imagem", + "stop": "Parar, membro principal primeiro", + "backup": "Cópia de segurança verificada de cada CT", + "replace": "Substituir rootfs", + "healthy": "Todos funcionando corretamente?", + "yes": "Sim", + "no": "Não", + "commit": "Publicar contratos", + "rollback": "Restaurar cada membro" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "O que uma pilha não inclui", + "blocks": [ + { + "list": { + "items": [ + "Uma suíte instalada em um fluxo, como a suíte Arr, não é uma pilha dependente: seus recipientes não têm ordem de início entre eles.", + "A rede privada não substitui a autenticação, TLS ou a configuração de cada aplicativo.", + "O backup de um LXC não contém a ponte, o gancho ou os contratos do resto da pilha.", + "Prowlarr, Sonarr ou Radarr não recebem indexadores, perfis ou provedores de ProxMenux." + ] + } + } + ] + } + ] +} diff --git a/web/messages/pt/docs/oci-manager/storage-network.json b/web/messages/pt/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..3fb8a7ee --- /dev/null +++ b/web/messages/pt/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Dados, caminhos e rede | ProxMenux", + "description": "Como o OCI manager Apps preserva os dados de um contentor OCI: discos do contentor, diretórios do host, montagens com rclone, endereços e redes privadas." + }, + "header": { + "title": "Dados, caminhos e rede", + "description": "O que fica no rootfs, o que é preservado quando este é substituído, como os dados são partilhados entre contentores e como cada um obtém o seu endereço.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "A persistência é decidida antes de criar o LXC", + "body": "Os volumes declarados pela imagem e pelo respetivo ficheiro Compose são lidos antes de criar o contentor. Cada caminho que deva ser preservado após uma atualização torna-se um ponto de montagem independente do rootfs. Assim, o rootfs contém apenas o que pertence à imagem e pode ser substituído." + } + } + ] + }, + { + "id": "questions", + "title": "O que o instalador pergunta", + "intro": "O modelo fornece os caminhos necessários à aplicação. Cada um é colocado num disco do contentor ou num diretório do host, e podem ser acrescentados mais caminhos antes do resumo.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Caminhos necessários", + "body": "São apresentados /config, /data, bibliotecas, descargas e todos os volumes declarados pela aplicação." + }, + { + "title": "Localização", + "body": "Cada caminho é colocado num disco do contentor ou num diretório já existente no host." + }, + { + "title": "Armazenamento e tamanho", + "body": "É criado um disco do contentor num armazenamento do Proxmox VE com o tamanho indicado. A instalação predefinida utiliza local-lvm; a avançada pergunta o armazenamento de cada caminho imediatamente antes do tamanho e propõe o do rootfs. O disco aparece como vm-VMID-disk-N e é associado como mpN." + }, + { + "title": "Diretório do host", + "body": "O diretório do host é indicado pelo seu caminho. Se não existir, é criado e atribuído ao utilizador do host para o qual é mapeado o utilizador do contentor." + }, + { + "title": "Caminhos adicionais", + "body": "Antes de instalar, podem ser acrescentados mais pares de caminho ou volume do host e caminho dentro do contentor." + }, + { + "title": "Resumo", + "body": "O mapeamento completo é apresentado antes de criar o CT e guardado no contrato da instância." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "As duas localizações persistentes", + "blocks": [ + { + "table": { + "headers": [ + "Propriedade", + "Disco do contentor", + "Diretório do host" + ], + "rows": [ + [ + "Na configuração", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Cópia de segurança do contentor (vzdump)", + "Incluído, com backup=1", + "Não incluído" + ], + [ + "Tamanho", + "Fixo; aumenta-se redimensionando o ponto de montagem", + "O espaço livre do sistema de ficheiros ou dataset do host" + ], + [ + "Outros contentores", + "Montado apenas pelo contentor a que pertence", + "O mesmo diretório pode ser montado em vários contentores" + ], + [ + "Snapshots e restauro", + "Geridos pelo Proxmox VE juntamente com o CT", + "Geridos no armazenamento do host" + ], + [ + "Ao remover a aplicação", + "Eliminado com o contentor", + "Preservado com o seu conteúdo" + ], + [ + "Ao mover o CT para outro nó", + "É movido com o CT", + "O mesmo caminho tem de existir no outro nó" + ] + ] + } + }, + { + "p": "O rootfs fica reservado para os binários e o conteúdo da imagem. Uma atualização ou recriação substitui-o sem tocar em nenhum dos dois tipos de ponto de montagem." + } + ] + }, + { + "id": "example", + "title": "Exemplo: um contentor com ambas as localizações", + "blocks": [ + { + "code": { + "title": "Jellyfin instalado como CT 151 em local-lvm (excerto)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "Uma atualização ou recriação substitui apenas o rootfs: o mp0 preserva utilizadores, bibliotecas e definições, e o mp1 continua a apresentar os mesmos ficheiros multimédia. Restaurar a cópia do CT recupera /config; se necessário, o diretório multimédia é restaurado a partir da cópia de segurança do armazenamento do host." + }, + { + "flow": { + "nodes": [ + { + "label": "Contrato", + "detail": "/config" + }, + { + "label": "Localização", + "detail": "Disco do contentor\nou diretório do host" + }, + { + "label": "LXC", + "detail": "Sempre /config\npara a aplicação" + } + ], + "caption": "A aplicação vê o caminho declarado pela imagem; só muda o local onde é armazenado." + } + } + ] + }, + { + "id": "shared", + "title": "Um diretório do host, vários contentores", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Diretório do host", + "config": "Configuração própria" + } + } + }, + { + "p": "Cada contentor guarda a sua configuração no respetivo disco. A biblioteca ou as descargas ficam num único diretório do host, montado no mesmo caminho interno em todos os contentores. Assim, o caminho onde uma aplicação escreve é o mesmo de onde outra lê." + } + ] + }, + { + "id": "rclone", + "title": "Armazenamento na nuvem através da aplicação rclone", + "intro": "Além da instalação, a ficha do rclone no catálogo permite Ativar uma montagem num contentor OCI de rclone existente. É montado um armazenamento remoto já criado e autorizado na interface web do rclone e disponibilizado no host. Outros contentores podem utilizá-lo como um diretório do host.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Contentor e armazenamento remoto", + "body": "O VMID do contentor de rclone, o nome exato do armazenamento remoto e, opcionalmente, um caminho dentro dele." + }, + { + "title": "Nome da montagem e cache", + "body": "O nome da montagem e o modo de cache VFS: off, minimal, writes ou full (predefinido)." + }, + { + "title": "Vistas disponibilizadas", + "body": "Uma raiz comum, /mnt/oci-shared por predefinição, contém uma vista de leitura e escrita em /mnt/oci-shared/remotes/NAME e uma vista só de leitura em /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Ativação", + "body": "Após confirmação, o CT é parado, o seu comando de arranque e um hookscript do Proxmox VE são configurados e o CT é iniciado novamente. A operação aguarda até ambas as vistas estarem montadas no host." + } + ] + } + }, + { + "calloutInfo": { + "title": "Se a montagem não for ativada", + "body": "A configuração anterior do contentor é restaurada e este é iniciado novamente. Assim, se a ativação falhar, o rclone fica como estava." + } + } + ] + }, + { + "id": "network", + "title": "Endereços e redes", + "intro": "Uma aplicação com um só contentor precisa de um endereço. Uma aplicação com vários contentores também precisa de uma rede estável entre os seus membros.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Aplicação com um só contentor", + "body": "Escolhe-se a bridge de rede e DHCP ou um endereço fixo em formato CIDR. O LXC tem um endereço próprio e o resumo apresenta os URL completos dos serviços." + }, + { + "icon": "waypoints", + "title": "Aplicação com vários contentores", + "body": "É encontrada uma sub-rede livre e criada uma bridge privada persistente. Cada membro — aplicação, base de dados e cache — recebe nela um endereço fixo." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Endereço acessível\napenas para o serviço principal" + }, + { + "label": "LXC principal", + "detail": "Web / API\nLAN + rede privada" + }, + { + "label": "Rede privada", + "detail": "PostgreSQL · Valkey · ML\nendereços fixos" + } + ], + "caption": "As dependências comunicam pela rede privada e não têm um endereço na LAN." + } + }, + { + "p": "O contrato da stack guarda a bridge, a sub-rede, os endereços e as relações entre serviços. Ao atualizar a aplicação, a mesma topologia é reutilizada para todos os membros. O ProxMenux Monitor abre o contentor principal no seu endereço LAN, não no da rede privada." + } + ] + }, + { + "id": "ownership", + "title": "Proprietários de ficheiros e diretórios", + "blocks": [ + { + "list": { + "items": [ + "Os novos diretórios são criados com o UID e o GID para os quais é mapeado o LXC sem privilégios.", + "O proprietário dos diretórios existentes não é alterado de forma recursiva.", + "Sockets, ficheiros de sistema e caminhos sensíveis não são propostos como diretórios genéricos do host." + ] + } + } + ] + }, + { + "id": "backup", + "title": "O que contém uma cópia de segurança do contentor", + "blocks": [ + { + "table": { + "headers": [ + "Elemento", + "Na cópia vzdump do CT", + "Onde é guardado" + ], + "rows": [ + [ + "Rootfs OCI", + "Sim", + "Na cópia do CT; também pode ser reconstruído a partir da imagem e do contrato" + ], + [ + "Disco do contentor com backup=1", + "Sim", + "A cópia do CT" + ], + [ + "Diretório do host", + "Não", + "A cópia de segurança do armazenamento do host" + ], + [ + "Contrato da instância", + "Sim, a cópia guardada no CT", + "/.proxmenux/oci-record.json dentro do CT; as cópias adicionais do host e do cluster são guardadas separadamente" + ], + [ + "Aplicação com vários contentores", + "Cada membro na sua própria cópia", + "As cópias de todos os membros, mais o contrato da stack e a respetiva bridge no host" + ] + ] + } + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/architecture.json b/web/messages/sk/docs/oci-manager/architecture.json new file mode 100644 index 00000000..657730c7 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "Ako sa prispôsobí obraz OCI | ProxMenux", + "description": "Od repozitára obrazu a jeho súboru Compose cez skontrolovateľnú šablónu a plán nasadenia až po natívny LXC v Proxmox VE, bez Dockeru v kontajneri." + }, + "header": { + "title": "Ako sa prispôsobí obraz OCI", + "description": "Od repozitára obrazu a jeho súboru Compose cez skontrolovateľnú šablónu a plán nasadenia až po natívny LXC, bez inštalácie Dockeru v kontajneri.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "Postup prispôsobenia", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Repozitár obrazu", + "converter": "Konvertor", + "template": "Šablóna JSON", + "blockers": "Bez prekážok?", + "no": "Nie", + "yes": "Áno", + "review": "Kontrola / overlay", + "plan": "Plán nasadenia", + "lxc": "Natívny LXC" + } + } + }, + { + "p": "Konvertor načíta obraz a súbor Compose zverejnené projektom a vytvorí šablónu JSON. Ak niektoré požiadavky ešte nie sú prispôsobené a bránia inštalácii, šablóna pred zverejnením v katalógu prejde kontrolou. Súbor overených úprav, nazývaný overlay, tieto prekážky vyrieši. Na inštaláciu sa ponúkajú iba šablóny bez nevyriešených prekážok." + } + ] + }, + { + "id": "template", + "title": "Čo šablóna zachováva", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Identita obrazu", + "body": "Repozitár, priebežne aktualizovaná značka, architektúra, zistený digest a revízia zdroja." + }, + { + "icon": "braces", + "title": "Kontrakt kontajnera", + "body": "Entrypoint, Cmd, prostredie, používateľ, pracovný adresár, signál na zastavenie, porty a zväzky." + }, + { + "icon": "layers", + "title": "Prispôsobenie pre Proxmox VE", + "body": "Zdroje, zabezpečenie, prípojné body, zariadenia, sysctls, kontroly stavu a potrebné úpravy pre každú položku vrátane ich zdôvodnenia." + }, + { + "icon": "shield", + "title": "Kompatibilita", + "body": "Podporované kľúče, neprispôsobené požiadavky brániace inštalácii a stav každej kontroly." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "OCI poskytuje proces, Compose poskytuje prostredie", + "blocks": [ + { + "table": { + "headers": [ + "Zdroj", + "Príklad", + "Natívny výsledok" + ], + "rows": [ + [ + "Metadáta OCI", + "Entrypoint, Cmd, User", + "Proxmox VE ich importuje pri vytváraní CT" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Záznamy prostredia LXC, mpN a devN" + ], + [ + "Profil ProxMenux", + "GPU, kontrola stavu, prihlasovacie údaje", + "Otázky a overené úpravy" + ], + [ + "Používateľ", + "VMID, úložisko, sieť", + "Kontrakt inštancie" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Čo sa deje počas inštalácie", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Zistiť konkrétny obraz", + "body": "Vyhľadá sa obraz v registri, vyberie sa architektúra hostiteľa a zaznamená sa konkrétny digest, na ktorý odkazuje priebežne aktualizovaná značka." + }, + { + "title": "Stiahnuť a overiť", + "body": "Skopeo stiahne obraz ako archív OCI. Pred vytvorením akýchkoľvek prostriedkov sa každá vrstva overí podľa jej digestu a rozbalí. Ak je stiahnutý súbor poškodený, sťahovanie sa ešte raz zopakuje, až potom sa inštalácia zastaví." + }, + { + "title": "Vytvoriť súborový systém", + "body": "pct create vytvorí rootfs z archívu a zachová oficiálne metadáta procesu obrazu." + }, + { + "title": "Pripojiť prostriedky", + "body": "Pripoja sa deklarované zväzky a zariadenia a nastavia sa sieť, prostredie a bezpečnostné profily." + }, + { + "title": "Konzola", + "body": "Výstup konzoly kontajnera sa uchováva na hostiteľovi. Konzola Proxmox VE otvorí shell, ak ho obraz obsahuje." + }, + { + "title": "Overiť", + "body": "Pri prvom spustení sa čaká na pridelenie adresy a odpoveď služby. V prípade zlyhania sa inštalácia neoznačí za úspešnú." + }, + { + "title": "Zaznamenať konfiguráciu", + "body": "Výsledná konfigurácia sa uloží do kontraktu inštancie, ktorý sa používa pri aktualizáciách a opätovnom vytvorení." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Príklad: obraz s /config a /downloads", + "intro": "Bežná definícia Compose a konfigurácia Proxmox VE, ktorá z nej vznikne. Cesty očakávané aplikáciou sa nemenia.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (úryvok)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "mp0 je druhý disk patriaci kontajneru, na zvolenom úložisku pomenovaný vm-VMID-disk-N. Pripája sa do /config a pri backup=1 je súčasťou zálohy kontajnera. mp1 nevytvára disk: pripája adresár hostiteľa /srv/downloads do /downloads v LXC. Port 8080 sa nemapuje: LXC má vlastnú adresu a služba odpovedá na nej." + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/custom-image.json b/web/messages/sk/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..7122dedf --- /dev/null +++ b/web/messages/sk/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Inštalácia obrazu mimo katalógu | ProxMenux", + "description": "Prispôsobte obraz OCI zo súboru Compose, URL, príkazu docker run alebo odkazu na obraz a pred inštaláciou skontrolujte, čo ProxMenux dokáže reprodukovať." + }, + "header": { + "title": "Nainštalovať obraz mimo katalógu", + "description": "Vlastný obraz sa prispôsobí zo súboru Compose, príkazu docker run alebo iba z odkazu na obraz. Výsledok sa zobrazí na kontrolu skôr, než sa vytvorí akýkoľvek LXC.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Rovnaký konvertor a inštalátor", + "body": "Voľba Nainštalovať obraz mimo katalógu používa rovnaký konvertor a inštalátor ako šablóny katalógu. Rozdiel je v tom, že kontrakt sa vytvorí až vtedy zo zadanej definície a overí sa pred vytvorením akéhokoľvek LXC." + } + } + ] + }, + { + "id": "sources", + "title": "Ako je obraz opísaný", + "intro": "Úvodná obrazovka sa pýta, ako je obraz opísaný. Štyri možnosti poskytnú definíciu na prispôsobenie, piata používa iba názov obrazu a ďalšie tri načítajú už napísaný oci-compose.", + "blocks": [ + { + "table": { + "headers": [ + "Voľba", + "Čo sa načíta" + ], + "rows": [ + [ + "Vložiť súbor Compose do terminálu", + "Vložte obsah YAML do terminálu a ukončite ho pomocou Ctrl+D." + ], + [ + "Načítať súbor Compose z tohto hostiteľa", + "Cesta na uzle, predvolene /root/docker-compose.yml. Súbor musí existovať a nesmie presiahnuť 256 KiB." + ], + [ + "Stiahnuť súbor Compose z adresy", + "Adresa http:// alebo https://, ktorá poskytuje nespracovaný YAML. Stiahne sa najviac 256 KiB." + ], + [ + "Vložiť príkaz docker run do terminálu", + "Príkaz zverejnený projektom. Prispôsobia sa porty, prostredie, zväzky, zariadenia, capabilities, používateľ, zdieľaná pamäť a ďalšie podporované voľby." + ], + [ + "Iba odkaz na obraz, bez súboru Compose", + "Odkaz, napríklad ghcr.io/user/application:latest. Vyhľadá sa obraz v registri a zachovajú sa jeho metadáta OCI." + ], + [ + "Vložiť oci-compose do terminálu", + "Vložte JSON do terminálu a ukončite ho pomocou Ctrl+D. Nekonvertuje sa: overí sa a nainštaluje tak, ako je napísaný." + ], + [ + "Načítať oci-compose z tohto hostiteľa", + "Súbor JSON na uzle, predvolene /root/oci-compose.json, s veľkosťou najviac 2 MiB." + ], + [ + "Stiahnuť oci-compose z adresy", + "Adresa http:// alebo https://, ktorá poskytuje súbor JSON. Stiahnu sa najviac 2 MiB." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Menu výberu spôsobu opisu obrazu mimo katalógu", + "caption": "Spôsoby opisu obrazu mimo katalógu." + } + } + ] + }, + { + "id": "reference-only", + "title": "Čo poskytuje samotný odkaz na obraz", + "intro": "Obraz obsahuje metadáta svojho procesu, nie však celé prostredie, ktoré zvyčajne dopĺňa súbor Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Načítané z obrazu", + "Nie je v obraze", + "Dôsledok" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir a zahrnuté prostredie", + "Premenné uvedené iba v dokumentácii", + "Pridajú sa pri inštalácii alebo cez Upraviť" + ], + [ + "Zväzky deklarované obrazom", + "Adresáre hostiteľa uvedené iba v súbore Compose", + "Ďalšie cesty sa pridajú pred inštaláciou" + ], + [ + "Porty EXPOSE", + "URL, protokol alebo funkčná kontrola služby", + "Potvrdí sa port a spôsob kontroly služby" + ], + [ + "Architektúry a digest v registri", + "Zariadenia, oprávnenia alebo externé závislosti", + "Bez požiadavky v definícii sa nepovolia" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Kontrola pred inštaláciou", + "intro": "Po prispôsobení definície ProxMenux zobrazí, ako ju pochopil, ešte pred otázkami na VMID, zdroje a úložisko.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analyzovať", + "body": "Načítajú sa služby, obraz, porty, cesty, prostredie, zariadenia, zabezpečenie a kontroly stavu." + }, + { + "title": "Overiť obraz v registri", + "body": "Obraz musí existovať v registri a zobrazia sa jeho publikované architektúry. Súkromný register sa načíta pomocou účtu uloženého pre daný register." + }, + { + "title": "Uviesť nepoužité voľby", + "body": "Vypíšu sa štítky, siete Docker, nastavenia Swarm a ďalšie kľúče, ktoré nemajú na LXC vplyv." + }, + { + "title": "Zablokovať voľby bez bezpečného ekvivalentu", + "body": "Kľúče bez bezpečného ekvivalentu sa zobrazia v časti Voľby bez ekvivalentu a inštalácia nepokračuje. Žiadny sa nevynechá bez upozornenia." + }, + { + "title": "Vyžiadať tajné údaje", + "body": "Premenné, ktorých názov označuje heslo, token alebo kľúč, sa pri inštalácii vyžiadajú ako citlivé údaje." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Súhrn interpretácie súboru Compose v ProxMenux", + "caption": "Súhrn zobrazený pred otázkami na VMID a zdroje." + } + }, + { + "p": "Po potvrdení súhrnu nasleduje rovnaký postup ako v katalógu: názov, predvolený alebo rozšírený režim, VMID, CPU, pamäť, sieť, spúšťanie s uzlom, trvalé a ďalšie cesty, kompatibilné zariadenia a záverečný súhrn." + } + ] + }, + { + "id": "examples", + "title": "Príklady vstupu", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "Ručne napísaný oci-compose", + "intro": "oci-compose je súbor JSON, z ktorého sa inštaluje aplikácia. Katalóg uchováva jeden pre každú aplikáciu. Rovnaký formát možno napísať ručne a nainštalovať touto voľbou.", + "blocks": [ + { + "p": "V ručne napísanom súbore autor určí obraz, porty, premenné, zväzky a otázky pri inštalácii. ProxMenux pri načítaní doplní zvyšok. Formát vyjadrí aj to, čo Compose neobsahuje: text otázky, heslo vytvorené pri inštalácii, profil GPU, vlastníka každého zväzku, kontrolu prvého spustenia alebo prihlasovacie údaje zobrazené na konci." + }, + { + "steps": { + "items": [ + { + "title": "Overiť", + "body": "Súbor s chybami sa nenainštaluje. Každá chyba označí príslušné pole; pri neplatnom JSON sa uvedie riadok a stĺpec." + }, + { + "title": "Skontrolovať", + "body": "Platný súbor zobrazí, čo nainštaluje: obraz, webovú adresu, porty, trvalé cesty, premenné požadované pri inštalácii a súbory, ktoré zapíše." + }, + { + "title": "Nainštalovať", + "body": "Inštalácia je rovnaká ako pri aplikáciách katalógu, s predvolenými alebo rozšírenými nastaveniami." + } + ] + } + }, + { + "calloutWarning": { + "title": "Súbor, ktorý určuje oprávnenia", + "body": "oci-compose určuje, čo sa v kontajneri pripojí a s akými oprávneniami. Kontrola to ukáže pred inštaláciou: používajte iba súbory z dôveryhodných zdrojov." + } + }, + { + "p": "Formát je opísaný po blokoch s dvoma úplnými príkladmi v README enginu OCI. Rovnakým spôsobom možno načítať aj záznam inštalácie: použije sa iba pôvodná šablóna, nie odpovede z danej inštalácie." + } + ] + }, + { + "id": "private-registries", + "title": "Obrazy v súkromných registroch", + "intro": "Obraz zo súkromného registra, napríklad súkromný balík GitHub alebo repozitár Docker Hub, sa načíta pomocou účtu uloženého v ProxMenux.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Register odmietne prístup k obrazu", + "body": "Ak register oznámi, že obraz vyžaduje účet, kontrola ponúkne prihlásenie a potom obraz načíta znova." + }, + { + "title": "Prihlásiť sa", + "body": "Používateľské meno a heslo alebo token s oprávnením na stiahnutie obrazu. Register ich overí a uložia sa iba pri úspešnom overení." + }, + { + "title": "Nainštalovať a udržiavať aktuálne", + "body": "Účet sa použije na stiahnutie obrazu pri inštalácii aj pri aktualizáciách, úpravách, opätovnom vytvorení, vo watchdogu a pri kontrole aktualizácií Monitora na ľubovoľnom uzle klastra." + } + ] + } + }, + { + "p": "Účty registrov v hlavnom menu OCI manager Apps zobrazujú uložené účty podľa registra a používateľa, nikdy nie ich tajné údaje. Z každého účtu možno overiť, stiahnuť alebo nainštalovať obraz a aktualizovať alebo odstrániť prihlasovacie údaje." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io): používateľ je účet GitHub a heslo je osobný prístupový token s oprávnením read:packages.", + "Docker Hub (docker.io): používateľ je účet Docker Hub a heslo je prístupový token s oprávnením na čítanie.", + "Ostatné registre, aj s vlastným portom, napríklad registry.example.com:5000: používateľské meno a heslo alebo token poskytnuté daným registrom." + ] + } + }, + { + "calloutInfo": { + "title": "Kde sa účty ukladajú", + "body": "V /etc/pve/priv/proxmenux/registry-auth.json, ktorý môže čítať iba root, v rovnakom adresári ako heslá úložísk Proxmox. /etc/pve zdieľajú všetky uzly klastra, preto sa účet pridaný na jednom uzle používa na všetkých. Záloha hostiteľa tento súbor obsahuje a odinštalovanie ProxMenux ho odstráni zo všetkých uzlov." + } + } + ] + }, + { + "id": "limits", + "title": "Definície, ktoré sa nenainštalujú", + "blocks": [ + { + "list": { + "items": [ + "Dockerfile bez publikovaného obrazu: obraz sa musí najprv zostaviť a zverejniť v registri OCI.", + "Súbor Compose s viacerými službami sa zablokuje, pretože táto voľba inštaluje iba jednu službu do jedného kontajnera, aj keď viaceré služby používajú rovnaký obraz.", + "configs, externé tajné údaje alebo formáty zariadení, ktoré nemožno jednoznačne prispôsobiť.", + "Nahradenia shellu, napríklad $(command): namiesto nich treba zapísať výslednú hodnotu.", + "Nerozpoznané voľby docker run a --env-file: namiesto nich použite súbor Compose s výslovne uvedenými hodnotami." + ] + } + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/hardware.json b/web/messages/sk/docs/oci-manager/hardware.json new file mode 100644 index 00000000..f6a5bf36 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Zariadenia a zrýchlenie ProxMenux", + "description": "Ako OCI manager Apps sprístupňuje GPU, NVIDIA, Coral, USB, FUSE a ďalšie zariadenia kontajneru OCI ako overené natívne zdroje Proxmox VE." + }, + "header": { + "title": "Zariadenia a akcelerácia", + "description": "GPU, NVIDIA, Coral, USB, FUSE a blokové zariadenia sa stávajú overenými natívnymi zdrojmi Proxmox VE pre kontajner.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "Zariadenie, ktoré aplikácia potrebuje, nie celý hostiteľ", + "blocks": [ + { + "p": "Zariadenie požadované súborom Compose alebo aplikačným profilom sa stáva konkrétnou položkou devN alebo LXC pripojením. Požiadanie o GPU, USB zariadenie alebo Coral nerobí kontajner privilegovaným." + }, + { + "flow": { + "nodes": [ + { + "label": "Inventár hostiteľa", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "výrobca a\noverenie oprávnení" + }, + { + "label": "LXC", + "detail": "rovnaké zariadenie\nefektívne GID" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "Odkiaľ pochádza požiadavka na zariadenie", + "blocks": [ + { + "table": { + "headers": [ + "Zdroj", + "Čo sa číta", + "Čo robí inštalátor" + ], + "rows": [ + [ + "Docker Compose", + "devices, group_add, deploy.resources a NVIDIA žiadosti", + "Každá požiadavka sa stáva požiadavkou zariadenia zobrazenou na preskúmanie" + ], + [ + "Katalógový profil", + "Podpora GPU, Coral, OpenCL, USB alebo FUSE, ktorú aplikácia skutočne má", + "Ponúkajú sa len možnosti overené pre tento obraz" + ], + [ + "Metadáta a dokumentácia obrazu", + "VA-API, Selkies, LinuxServer mods alebo NVIDIA runtime", + "Pridávajú sa zdokumentované premenné a príprava" + ], + [ + "Výber užívateľa", + "iba CPU, Intel/AMD, OpenCL, NVIDIA alebo voliteľné zariadenie", + "Výber je uložený v inštančnej zmluve" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Zistené zariadenia sa nepripájajú automaticky", + "body": "Na hostiteľovi sa vytvorí inventár, ale ponúknu a pripoja sa len zariadenia deklarované v súbore Compose alebo kompatibilnom profile. GPU, USB adaptér či Coral prítomný na hostiteľovi sa nesprístupní každému LXC." + } + } + ] + }, + { + "id": "identify", + "title": "Ako sa identifikuje hostiteľské zariadenie", + "intro": "Pred úpravou LXC sa zariadenie číta na hostiteľovi a zodpovedá zvolenému profilu.", + "blocks": [ + { + "table": { + "headers": [ + "Typ", + "Identita", + "Overenie" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* a /sys/class/drm/NODE/device/vendor", + "Znakové zariadenie s výrobcom 0x8086 (Intel) alebo 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "Renderovací uzol a /dev/kfd, ak ho profil potrebuje", + "Existencia, typ, výrobca, oprávnenia a deklarovaná zlučiteľnosť" + ], + [ + "AMD ROCm", + "Renderovací uzol, /dev/kfd a generácia, ktorú výpočtový ovládač hlási v /sys/class/kfd/kfd/topology/nodes/*/properties", + "Generácia, pre ktorú obraz ROCm aplikácie obsahuje výpočtové jadrá — uvedené GPU RDNA2 a novšie — sa ponúkne bez úprav. Ostatné GPU týchto rodín, napríklad Radeon 680M a 780M, sa ponúkajú ako experimentálne a nikdy nie sú navrhnutou voľbou: ROCm ich oficiálne nepodporuje, aplikácia ich prezentuje ako generáciu danej rodiny (HSA_OVERRIDE_GFX_VERSION) a inštalátor pred stiahnutím väčšieho obrazu vyžiada potvrdenie. Staršia GPU, napríklad integrovaná grafika Ryzen 5000U, sa neponúkne a Immich ponechá rozpoznávanie na CPU." + ], + [ + "NVIDIA", + "nvidia-smi a nvidia-container-cli", + "GPU, UUID, PCI bus, verzia ovládača, Toolkit, uzly /dev/nvidia*, binárne súbory a knižnice" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N a jeho odkaz na /sys/dev/char/MAJOR:MINOR", + "Uzol znakového zariadenia, čísla major/minor, vlastník, GID a oprávnenia" + ], + [ + "USB a sériové", + "/dev/ttyUSB*, /dev/ttyACM* alebo /dev/bus/usb/BBB/DDD", + "Uzol znakového zariadenia; pri USB aj výrobca, produkt a sériové číslo, ak ich sysfs zverejňuje" + ], + [ + "KVM, TUN, FUSE, video a generické SCSI", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN alebo /dev/sgN", + "Podporovaná cesta, typ uzla a účinné práva" + ], + [ + "Optická mechanika", + "/dev/srN", + "Blokové zariadenie" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Čo sa stane počas inštalácie", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Šablóna ponúka svoje profily", + "body": "Napríklad len CPU, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL alebo NVIDIA. Možnosti patria k obrazu, nie k spoločnému menu." + }, + { + "title": "Vyberie sa profil", + "body": "Určuje uzly zariadení, prostredie, mody alebo runtime, ktoré aplikácia potrebuje." + }, + { + "title": "Navrhuje sa cesta", + "body": "Pre DRM, /dev/dri/renderD128, ktoré môžu byť zmenené na hostiteľa s niekoľkými renderových uzlov. Pre USB alebo sériový je vybraný konkrétny uzol." + }, + { + "title": "Overenie", + "body": "Existencia, typ, povolený výrobca, povolenia a GID sú kontrolované. Nesúlad zastaví operáciu." + }, + { + "title": "Zapíše sa kontrakt", + "body": "Cesta, režim, GID, zápis a profil sa zaznamenávajú pre aktualizácie a opätovné vytvorenia." + }, + { + "title": "Pripojenie a skúška", + "body": "pct set pridáva položku devN a prístup sa potom kontroluje vo vnútri LXC. LinuxServer obrazy sú tiež kontrolované ako používateľ abc." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Ako sa objavuje v konfigurácii LXC", + "intro": "Ilustračné hodnoty: dev0 a dev1 sú voľné pozície priradené Proxmox VE. renderD128, apex_0 a GID závisia od hardvéru uzla.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "Špecifické USB zariadenie", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "GID sa načíta na hostiteľovi pomocou stat a zapíše do položky devN; nepredpokladá sa, že skupiny render a video majú pevné číslo. Zariadenie má v LXC rovnakú cestu /dev, na ktorej ho vyhľadávajú vlastné mechanizmy aplikácie." + } + ] + }, + { + "id": "profiles", + "title": "Profily, ktoré môže obraz ponúknuť", + "blocks": [ + { + "table": { + "headers": [ + "Profil", + "Výsledná konfigurácia", + "Ponúknuté, keď" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "Renderovací uzol /dev/dri", + "Aplikácia podporuje video zrýchlenie" + ], + [ + "OpenCL", + "Render uzol, /dev/kfd v prípade potreby a oficiálny mod", + "Obraz alebo profil ho dokumentuje" + ], + [ + "NVIDIA", + "Zariadenia a knižnice ovládača na hostiteľovi", + "Hostiteľ má funkčný ovládač a NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 alebo zbernica USB", + "Profil vyhlasuje Coral podporu (Frigate)" + ], + [ + "USB, sériové, FUSE", + "Jediné zariadenie, validovaný strom alebo funkcia LXC", + "Zmluva o ňu žiada" + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Požiadavka na uzol: NVIDIA Container Toolkit", + "body": "Funkčný ovládač na Proxmox VE nestačí na sprístupnenie GPU NVIDIA obrazu OCI. OCI manager Apps používa nvidia-container-cli zo súpravy NVIDIA Container Toolkit na identifikáciu zariadení a získanie binárnych súborov a knižníc zodpovedajúcich načítanému ovládaču." + } + }, + { + "p": "Inštalátor NVIDIA nástroja ProxMenux nainštaluje NVIDIA Container Toolkit z oficiálneho repozitára NVIDIA spolu s ovládačom. Skontroluje jeho štyri balíky, overí nvidia-container-cli a zaznamená výsledok do denníka zmien Audit a správa." + }, + { + "p": "Tieto dva príkazy na hostiteľovi ukazujú, či je k dispozícii ovládač a súbor nástrojov:" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "Na hostiteľovi, kde bol ovládač nainštalovaný inými prostriedkami, je Toolkit nainštalovaný z oficiálneho stabilného úložiska:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "Inventár, ktorý používa OCI manager Apps, je výstupom príkazu:" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "Konfigurácia Runtime Docker nie je zahrnutá", + "body": "Kontajnery sú natívne LXC a nepoužívajú démona Docker, takže nvidia-ctk runtime configure --runtime=docker nehrá žiadnu úlohu: ProxMenux priamo volá nvidia-container-cli a zapíše zariadenia a pripojenia LXC. Príkazy a podporované platformy sú uvedené v návode na inštaláciu NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventár z ovládača", + "body": "nvidia-container-cli uvádza uzly zariadenia, binárne súbory, firmware a knižnice nainštalovaného ovládača." + }, + { + "icon": "refresh", + "title": "Bez pevnej verzie", + "body": "Šablóna nepomenuje súbory knižnice. Profil je generovaný z aktuálneho hostiteľa." + }, + { + "icon": "shield", + "title": "Pripojenia iba na čítanie", + "body": "Hostiteľské knižnice sú namontované iba na čítanie namiesto kopírovania do kontajnera." + }, + { + "icon": "hardDrive", + "title": "Zmeny ovládača", + "body": "Po zmene ovládača sa inventár znovu vygeneruje pred začiatkom postihnutých LXC." + } + ] + } + }, + { + "code": { + "title": "Výsledok NVIDIA (zjednodušený)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Sprístupniť len /dev/nvidia0 nestačí. Komponenty užívateľského priestoru načítaného ovládača sú namontované len na čítanie a nvidia-smi potom beží vnútri LXC pre porovnanie GPU, UUID, PCI zbernice a verzie s inventárom hostiteľa." + } + ] + }, + { + "id": "usb", + "title": "USB, sériové a USB Coral", + "blocks": [ + { + "calloutWarning": { + "title": "Číslovanie USB sa môže zmeniť", + "body": "Cesta ako /dev/bus/usb/003/004 sa môže zmeniť po opätovnom pripojení zariadenia alebo reštarte hostiteľa. Profil zaznamená výrobcu, produkt a sériové číslo, ak sú dostupné, ale nová adresa zbernice sa automaticky nepremapuje." + } + }, + { + "p": "Periférne zariadenie je daný jeho konkrétnym uzlom: /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 alebo /dev/bus/usb/BBB/DDD. Sprístupnenie celého /dev nie je akceptovaný. Coral je ponúkaný len pre aplikácie, ktorých profil deklaruje." + } + ] + }, + { + "id": "trees", + "title": "Stromy zariadení a funkcie LXC", + "blocks": [ + { + "table": { + "headers": [ + "Žiadosť", + "Výsledná konfigurácia", + "Rozsah" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd alebo /dev/bus/usb", + "Každý uzol znakového zariadenia v strome dostane vlastnú položku devN s režimom a GID hostiteľa", + "Len požadovaný strom, nie zvyšok /dev" + ], + [ + "/dev/fuse", + "Uzol a, keď ho profil potrebuje, funkcia fuse=1", + "Samotná FUSE nepublikuje pripojenia do iných LXC" + ], + [ + "/dev/net/tun", + "Položka devN s rovnakou cestou vo vnútri LXC", + "VPN alebo konfigurácia siete zostáva v aplikácii" + ], + [ + "/dev/kvm", + "Potvrdená položka devN", + "Ponúkané iba vtedy, keď o to zmluva požiada" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Potvrdenie podľa úrovne rizika", + "blocks": [ + { + "p": "Konkrétne zariadenia, voliteľné privilégium, požadované privilégium, AppArmor alebo seccomp relaxácia a prístup k mennému priestoru PID hostiteľa sa považujú za samostatné prípady, nie pod jedným generickým privilegovaným označením. Každá možnosť s rizikom sa vysvetľuje a potvrdzuje počas inštalácie." + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/index.json b/web/messages/sk/docs/oci-manager/index.json new file mode 100644 index 00000000..00fea885 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Spúšťajte oficiálne obrazy OCI ako natívne kontajnery LXC v Proxmox VE, s trvalými údajmi, prístupom k zariadeniam, aplikáciami s viacerými kontajnermi a transakčnými aktualizáciami." + }, + "header": { + "title": "OCI manager Apps", + "description": "Oficiálne obrazy OCI bežia ako natívne kontajnery LXC v Proxmox VE. Používa sa rovnaký obraz, aký zverejňuje jeho správca; ProxMenux vytvorí prostredie, ktoré by okolo neho pripravil Docker Compose.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "Jeden obraz OCI, jeden natívny LXC", + "body": "V kontajneroch nebeží Docker Engine. Proxmox VE importuje súborový systém obrazu a jeho metadáta OCI. Aplikácia sa stane hlavným procesom natívneho LXC, ktorý sa spravuje rovnakými nástrojmi ako ostatné kontajnery uzla." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Zdroj", + "detail": "Obraz OCI\nCompose a dokumentácia" + }, + { + "label": "ProxMenux", + "detail": "Kontrakt JSON\noverenie a plánovanie" + }, + { + "label": "Proxmox VE", + "detail": "Natívny LXC\nzväzky a zariadenia" + } + ], + "caption": "Používa sa pôvodná aplikácia; jej prostredie sa deklaratívne opíše a následne znovu vytvorí." + } + } + ] + }, + { + "id": "where", + "title": "Kde ho nájdete", + "intro": "Na uzle Proxmox otvorte ako root položku OCI manager Apps (Technology Preview) v hlavnom menu ProxMenux. Úvodná obrazovka ponúka:", + "blocks": [ + { + "list": { + "items": [ + "Vyhľadať aplikácie – vyhľadávanie podľa názvu v celom katalógu.", + "Všetky aplikácie – úplný katalóg s počtom aplikácií.", + "Spravovať nainštalované aplikácie OCI – aktualizovať, upravovať, odstraňovať alebo obnovovať existujúce inštalácie.", + "Účty registrov – správa účtov na inštaláciu a aktualizáciu obrazov zo súkromných registrov.", + "Nainštalovať obraz mimo katalógu – prispôsobiť súbor Compose, príkaz docker run alebo odkaz na obraz.", + "Kategórie katalógu, každá s počtom aplikácií." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Hlavná obrazovka OCI manager Apps s vyhľadávaním, úplným katalógom, správou aplikácií, obrazmi mimo katalógu a kategóriami.", + "caption": "Hlavná obrazovka OCI manager Apps." + } + }, + { + "p": "Po výbere aplikácie sa zobrazí jej opis, používaný obraz a dva režimy inštalácie. Inštalovať s predvolenými nastaveniami sa pýta len na základné voľby: úložisko, umiestnenie údajov, adresu a spustenie. Inštalovať s rozšírenými nastaveniami postupne prejde všetky rozhodnutia: VMID a zdroje, úložisko pre každú cestu, sieťový most a zariadenia." + }, + { + "p": "Aplikácie, ktoré boli skutočne otestované, majú v tomto zobrazení označenie Overené a v zoznamoch značku ✓. Platí to pre testy v laboratóriu ProxMenux aj pre testy komunity." + } + ] + }, + { + "id": "beta", + "title": "Prečo má označenie Technology Preview", + "blocks": [ + { + "p": "OCI manager Apps ponecháva označenie Technology Preview, ktoré Proxmox VE používa pre aplikačné kontajnery vytvorené z obrazov OCI. Ich podpora bola zavedená v Proxmox VE 9.1. Označenie sa týka technológie, na ktorej OCI manager Apps beží, nie jadra ProxMenuxu: správanie týchto kontajnerov sa ešte môže meniť medzi vydaniami Proxmox VE. Označenie zostane, kým Proxmox VE túto podporu neoznačí za stabilnú." + }, + { + "p": "Funkcie OCI manager Apps sa testujú v laboratóriu ProxMenux: inštalácia, aktualizácia, úpravy, opätovné vytvorenie, zálohovanie, obnova, migrácia a watchdog. Každá aplikácia sa overuje samostatne. Skutočne otestované aplikácie majú označenie Overené a v zoznamoch značku ✓." + } + ] + }, + { + "id": "translation", + "title": "Ako Docker opisuje požiadavky a OCI manager Apps ich realizuje", + "blocks": [ + { + "table": { + "headers": [ + "Požiadavka", + "Vyjadrenie v Dockeri", + "Realizácia v OCI manager Apps" + ], + "rows": [ + [ + "Spustiť aplikáciu", + "image, entrypoint, command", + "Rootfs obrazu OCI a hlavný proces LXC" + ], + [ + "Zachovať konfiguráciu", + "volumes: /config", + "Trvalý zväzok mpN zahrnutý do zálohy alebo adresár na hostiteľovi." + ], + [ + "Sprístupniť službu", + "ports", + "Vlastná adresa pre LXC a URL na prístup k službe." + ], + [ + "Použiť hardvér", + "devices, group_add", + "Záznamy devN s efektívnym GID hostiteľa a overeným profilom." + ], + [ + "Prepojiť závislosti", + "networks, depends_on", + "Súkromný sieťový most, pevné adresy, poradie spúšťania a kontroly stavu." + ], + [ + "Aktualizovať", + "Stiahnuť cez pull a znovu vytvoriť", + "Nový rootfs so zachovaným kontraktom trvalých údajov." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "Čo zostáva a čo sa mení", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Zachované", + "body": "Oficiálny obraz, jeho vnútorné cesty, prostredie, proces spustenia a dokumentácia používania." + }, + { + "icon": "boxes", + "title": "Prispôsobené", + "body": "Prostredie behu: sieť, trvalé údaje, zariadenia, oprávnenia a závislosti využívajú natívne mechanizmy LXC v Proxmox VE." + }, + { + "icon": "shield", + "title": "Bez ekvivalentu", + "body": "Voľba Compose bez bezpečného ekvivalentu sa označí ako prekážka inštalácie. Šablóny s takými prekážkami sa na inštaláciu neponúkajú a citlivé voľby vyžadujú potvrdenie v sprievodcovi." + }, + { + "icon": "refresh", + "title": "Uložené", + "body": "Digest obrazu, zdroje, cesty, sieť, zariadenia a príslušnosť k zostave sa ukladajú do kontraktu inštancie. Aktualizácie aj opätovné vytvorenie tento kontrakt zachovávajú." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Stránky tejto časti", + "blocks": [ + { + "next": { + "items": [ + { + "label": "Ako sa prispôsobí obraz OCI", + "href": "/docs/oci-manager/architecture", + "tail": "Od obrazu a jeho súboru Compose k natívnemu LXC." + }, + { + "label": "Inštalácia obrazu mimo katalógu", + "href": "/docs/oci-manager/custom-image", + "tail": "Súbor Compose, príkaz docker run alebo odkaz na obraz." + }, + { + "label": "Údaje, cesty a sieť", + "href": "/docs/oci-manager/storage-network", + "tail": "Zväzky kontajnera, adresáre hostiteľa, pripojenia cez rclone a adresy." + }, + { + "label": "Zariadenia a akcelerácia", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB a ďalšie zariadenia." + }, + { + "label": "Aplikácie s viacerými kontajnermi", + "href": "/docs/oci-manager/stacks", + "tail": "Aplikácia, databáza a vyrovnávacia pamäť v koordinovaných LXC." + }, + { + "label": "Inštalácia, aktualizácia a úpravy", + "href": "/docs/oci-manager/lifecycle", + "tail": "Kontrakt inštancie a operácie s ním." + }, + { + "label": "V ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Verzie, aktualizácie, výstup konzoly a terminál." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/lifecycle.json b/web/messages/sk/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..5f345b34 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Inštalácia, aktualizácia a úpravy | ProxMenux", + "description": "Kontrakt inštancie kontajnera OCI a operácie, ktoré ho používajú: aktualizácia, úprava, odstránenie, zotavenie z prerušenej operácie a obnovenie prevádzky po obnove zo zálohy na tomto alebo inom hostiteľovi." + }, + "header": { + "title": "Inštalácia, aktualizácia a úpravy", + "description": "Každá inštancia uchováva reprodukovateľný kontrakt, takže jej rootfs možno nahradiť bez straty konfigurácie alebo trvalých dát.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "Kontrakt inštancie", + "blocks": [ + { + "p": "Po inštalácii sa výsledná konfigurácia uloží do /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json. Nejde o kópiu pôvodného súboru Compose: je to reprodukovateľný kontrakt kontajnera LXC, ktorý existuje na tomto hostiteľovi." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "Kópia kontraktu sa nachádza v kontajneri v /.proxmenux/oci-record.json a môže ju čítať iba používateľ root kontajnera. Záloha kontajnera preto vždy obsahuje kontrakt platný v čase zálohovania. Druhá kópia je uložená v /etc/pve/priv/proxmenux/oci, ktorý zdieľajú všetky uzly klastra a môže ho čítať iba root hostiteľa. Obe kópie sa zapisujú po každej inštalácii, aktualizácii a zmene." + } + ] + }, + { + "id": "manage", + "title": "Spravovať nainštalované aplikácie OCI", + "intro": "Táto možnosť na hlavnej obrazovke zobrazí registrované inštancie s ich aplikáciou a obrazom. Pred každou akciou sa každá inštancia porovná so svojím kontraktom; CT, ktorý už nezodpovedá svojmu záznamu, sa neupraví ani neodstráni. Rovnaká aktualizácia a rovnaký editor sú dostupné na karte Aktualizácie v ProxMenux Monitor.", + "blocks": [ + { + "table": { + "headers": [ + "Možnosť", + "Čo sa zmení?", + "Čo sa uchováva" + ], + "rows": [ + [ + "Aktualizovať obraz pomocou uloženej konfigurácie", + "Rootfs sa nahradí obrazom, ktorý uložený kanál aktuálne publikuje", + "Zmluva, kontajnerové disky, hostiteľské adresáre, siete a zariadenia" + ], + [ + "Upraviť: upraviť zdroje, sieť, cesty a GPU", + "Editor sa otvorí s aktuálnym kontraktom; CT sa znova vytvorí so zmenami", + "Dáta kontajnerových diskov a hostiteľských adresárov" + ], + [ + "Watchdog: reštartovať aplikáciu pri havárii", + "Určuje, či sa aplikácia znova spustí, keď sa sama zastaví. Nič sa nevytvára znova", + "Celá inštalácia; len voľba je uložená v zmluve" + ], + [ + "Odstrániť: odstrániť aplikáciu a jej kontajnery", + "LXC alebo všetci členovia zostavy a ich kontrakty sa odstránia", + "Hostiteľské adresáre s obsahom" + ] + ] + } + }, + { + "p": "Pre aplikáciu s viacerými kontajnermi menu ponúka Aktualizovať všetky kontajnery aplikácie, Upraviť ďalšie cesty a zariadenia, Znovu vytvoriť všetky kontajnery s uloženou konfiguráciou, watchdog a odstránenie. Úprava pridáva alebo odstraňuje ďalšie cesty a zariadenia aplikačného kontajnera bez opätovného vytvárania. V Immich tiež mení zariadenie, ktoré vykonáva rozpoznávanie. Opätovné vytvorenie zostaví každý kontajner z pôvodne nainštalovaného obrazu bez vyhľadávania novšieho." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Zoznam nainštalovaných aplikácií OCI s možnosťami aktualizácie, úpravy a odstránenia", + "caption": "Spravovať nainštalované aplikácie OCI." + } + } + ] + }, + { + "id": "update", + "title": "Transakčná aktualizácia", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Používateľ", + "registry": "Register OCI", + "update": "Aktualizovať inštanciu", + "resolve": "Zistiť obraz podľa značky", + "verify": "Overiť archív a vykonať predbežné kontroly", + "backup": "Zastaviť a zálohovať CT", + "import": "Importovať nové rootfs", + "reapply": "Znova použiť kontrakt", + "healthy": "v poriadku", + "failure": "zlyhanie", + "commit": "Kontrakt zverejnený s novým digestom", + "restored": "Predchádzajúca inštancia obnovená" + } + } + }, + { + "list": { + "items": [ + "Ak register stále poskytuje nainštalovaný digest, nič sa nesťahuje a inštancia zostáva nezmenená.", + "Nový obraz sa overí vrstvu po vrstve ešte pred zastavením CT. Poškodené stiahnutie sa zopakuje druhýkrát; obraz už uložený vo vyrovnávacej pamäti, ktorý neprejde kontrolou, sa stiahne znova.", + "Nastavenia zmenené v Proxmox VE po inštalácii (pamäť, swap, jadrá, limit CPU, priorita CPU, spustenie spolu s uzlom) sa zachovajú a prenesú do nového kontraktu.", + "Rozhranie LAN, ktoré pridáva súprava Arr a aplikácie s viacerými kontajnermi (net1), aj poradie spúšťania (startup) sa bez zmeny prenesú do nového kontajnera.", + "Akýkoľvek iný rozdiel medzi CT a jeho zmluvou zastaví aktualizáciu pred zastavením CT." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Pred", + "detail": "rootfs A\n/config mp0\n/media hostiteľský adresár" + }, + { + "label": "Aktualizácia", + "detail": "nahradí iba\nrootfs" + }, + { + "label": "Po", + "detail": "rootfs B\n/config mp0\n/media hostiteľský adresár" + } + ] + } + }, + { + "p": "Pred zastavením čohokoľvek sa overí dostatok miesta pre zálohu: využité dáta na zálohovaných zväzkoch plus rezerva. Záloha sa zapisuje vedľa záznamu aplikácie na systémový disk hostiteľa. Ak tam nie je dosť miesta, ponuka vyžiada iné úložisko hostiteľa podporujúce zálohy. Záloha sa vytvorí tam a po skončení operácie sa odstráni. Výber sa zapamätá pre danú aplikáciu aj pre plánované aktualizácie. Ak žiadne úložisko nemá dosť miesta, operácia sa odmietne a aplikácia zostane nezmenená. Rovnako sa pred stiahnutím overí miesto pre nový obraz." + } + ] + }, + { + "id": "recovery", + "title": "Zotavenie z prerušenej operácie", + "intro": "Aktualizácia aj úprava používajú trvalo uloženú transakciu. Ak sa proces, terminál alebo uzol preruší po zastavení CT, operácia sa nepovažuje za dokončenú.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Značka nedokončenej operácie", + "body": "Keď sa otvorí správa nainštalovaných aplikácií OCI, uložený stav ukazuje, že výmena nebola nikdy zverejnená." + }, + { + "title": "Zobraziť stav", + "body": "Zobrazí dosiahnutú fázu bez zmeny kontajnerov alebo dát." + }, + { + "title": "Obnoviť predchádzajúcu inštaláciu", + "body": "Obnoví overenú natívnu zálohu vytvorenú pred výmenou aj predchádzajúci kontrakt." + }, + { + "title": "Zostavy ako celok", + "body": "V prípade aplikácie s viacerými kontajnermi je každý člen obnovený z rovnakého transakčného bodu, nielen z vybraného." + } + ] + } + }, + { + "calloutWarning": { + "title": "Hostiteľské adresáre sú mimo návratu do pôvodného stavu", + "body": "Záloha zahŕňa rootfs a disky kontajnera, ktoré sú do nej zahrnuté. Hostiteľský adresár sa nevracia do predchádzajúceho stavu, pretože jeho dáta môžu používať iné kontajnery LXC." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: reštartovanie aplikácie, ktorá sa zastaví", + "intro": "Proxmox nemá politiku reštartovania: keď sa proces aplikačného kontajnera skončí, kontajner zostane zastavený. Pri zapnutom watchdogu sa aplikácia, ktorá sa sama zastaví, znova spustí. Pre aplikáciu OCI zabezpečuje to, čo restart: unless-stopped v súbore Compose.", + "blocks": [ + { + "table": { + "headers": [ + "Čo sa stane?", + "So zapnutým watchdogom" + ], + "rows": [ + [ + "Aplikácia havaruje alebo jej proces končí", + "Kontajner sa znova spustí do niekoľkých sekúnd" + ], + [ + "Zastaví sa alebo vypne cez Proxmox, Monitor alebo pomocou pct", + "Zostane zastavený, kým ho niekto nespustí" + ], + [ + "Pracuje na tom záloha, migrácia alebo operácia ProxMenux", + "Nič sa nerobí, kým to neskončí." + ], + [ + "Bol už zastavený pri spustení hostiteľa", + "Zostane zastavený; rozhoduje o tom Spustiť s Proxmox" + ], + [ + "Opakovane sa zastavuje hneď po spustení", + "Aplikácia, ktorá sa zastaví v priebehu niekoľkých sekúnd po spustení alebo sa nespustí vôbec, sa skúsi spustiť päťkrát s odstupmi 30 sekúnd, 1, 2 a 5 minút. Potom zostane zastavená a odošle sa oznámenie" + ] + ] + } + }, + { + "p": "Inštalátor sa na túto možnosť pýta v oboch režimoch inštalácie a navrhuje hodnotu uvedenú v recepte aplikácie. Neskôr sa mení cez Spravovať nainštalované aplikácie OCI alebo prepínačom Watchdog kontajnera v ProxMenux Monitor a platí pre všetky kontajnery aplikácie. Jediná služba hostiteľa, proxmenux-oci-watchdog.service, sleduje všetky aplikácie, ktoré si to vyžiadali; jej inštalácia je uvedená na karte Zmeny v Audit & Report." + }, + { + "calloutInfo": { + "title": "Čo hostiteľ nevie povedať", + "body": "Návratový kód aplikácie sa k hostiteľovi nedostane, takže riadne ukončenie a pád vyzerajú rovnako a v oboch prípadoch nasleduje reštart, podobne ako pri always a unless-stopped v Dockeri. Zastavenie vyžiadané mimo Proxmox pomocou lxc-stop alebo systemctl nevytvorí úlohu zastavenia a považuje sa za samovoľné zastavenie." + } + } + ] + }, + { + "id": "restore", + "title": "Zálohovanie, obnova a ďalší hostiteľ", + "intro": "Záloha vytvorená pomocou vzdump alebo Proxmox Backup Server obsahuje kontajner, disky kontajnera vybrané na zálohovanie a kópiu jeho kontraktu. To, čo inštalácia uchováva na hostiteľovi, do nej nepatrí. Spravovať nainštalované aplikácie OCI rozpozná kontajnery obnovené na hostiteľovi, ktorý nemá ich kontrakt, napríklad po novej inštalácii Proxmox alebo na inom hostiteľovi, a ponúkne ich opätovnú registráciu.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Obnoviť kontajnery v Proxmoxe", + "body": "Zo zálohovacieho úložiska, s pôvodným ID alebo ľubovoľným voľným ID. Aplikácia s viacerými kontajnermi potrebuje všetky svoje kontajnery." + }, + { + "title": "Otvoriť Spravovať nainštalované aplikácie OCI", + "body": "Zobrazia sa obnovené kontajnery a ponúkne sa obnovenie. Tlačidlo Obnoviť na karte Aktualizácie ProxMenux Monitor otvorí rovnaké obnovenie." + }, + { + "title": "Pred zmenou skontrolujte", + "body": "Obnova overí, či hostiteľ má všetko, čo každá aplikácia potrebuje. Aplikácia, ktorú nemožno obnoviť ako celok, zostane nezmenená a uvedie sa dôvod." + }, + { + "title": "Registrácia a spustenie", + "body": "Kontrakt sa zaregistruje na tomto hostiteľovi a znova sa zapíše to, čo na ňom inštalácia uchovávala. Spustenie aplikácií je samostatná otázka, na ktorú treba odpovedať Nie, ak pôvodné kontajnery stále bežia na inom hostiteľovi." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Dialóg, ktorý uvádza obnovené kontajnery a ponúka ich obnoviť", + "caption": "Obnova ponúknutá po otvorení Spravovať nainštalované aplikácie OCI." + } + }, + { + "table": { + "headers": [ + "Čo si hostiteľ nechal?", + "Po zotavení" + ], + "rows": [ + [ + "Kontrakt inštancie", + "Zaregistruje sa z kópie v kontajneri, ktorá sa porovná s konfiguráciou obnovenou systémom Proxmox" + ], + [ + "Súkromná sieť multikontajnerovej aplikácie", + "Znova sa vytvorí s rovnakým mostom a podsieťou; pevné adresy kontajnerov sa nezmenia" + ], + [ + "Poradie spúšťania aplikácie s viacerými kontajnermi", + "Hookscript závislostí a jeho kontrakt sa znova nainštalujú; ak žiadne úložisko nepodporuje snippets, povolia sa na úložisku local" + ], + [ + "Sieťové sysctls a súbor host-monitor", + "Znova sa zapíšu do /etc/pve/proxmenux" + ], + [ + "Runtime NVIDIA", + "Hook neprivilegovaného kontajnera sa znova nainštaluje. Privilegovaný kontajner dostane súbory ovládača tohto hostiteľa namiesto súborov pôvodného hostiteľa" + ], + [ + "Pripojenie Rclone", + "Hookscript a programy, ktoré sprístupňujú pripojenie, sa znova zapíšu s rovnakými pohľadmi na hostiteľovi" + ], + [ + "Pravidlo firewallu hostiteľa pre monitor hostiteľa", + "Znova sa vyžiada súhlas pre webový port a podsieť mosta na tomto hostiteľovi" + ], + [ + "lost+found z každého obnoveného disku", + "Odstráni sa, ak je prázdny; obnova ho vytvorí a niektoré aplikácie sa nespustia, ak je medzi ich dátami" + ], + [ + "Disky obnovené na inom úložisku", + "Kontrakt sa aktualizuje podľa úložiska, na ktorom sa teraz nachádzajú" + ] + ] + } + }, + { + "p": "Kontajner obnovený s iným ID si zachová svoju aplikáciu. Kontrakt, záznam konzoly, sieťové nastavenia sysctl, hookscript pripojenia Rclone a poradie spúšťania viackontajnerovej aplikácie sa zaregistrujú s ID, ktoré majú kontajnery na tomto hostiteľovi." + }, + { + "table": { + "headers": [ + "Čo bráni obnove", + "Čo urobiť" + ], + "rows": [ + [ + "Chýba kontajner aplikácie s viacerými kontajnermi", + "Obnovte aj tento kontajner; aplikácia sa obnovuje ako celok" + ], + [ + "Súkromná podsieť sa už používa na tomto hostiteľovi", + "Obnova sa zruší a nič sa nezmení: adresy kontajnerov sú pevné a nepresúvajú sa do inej podsiete" + ], + [ + "Adresár hostiteľa neexistuje", + "Pripojiť alebo vytvoriť s jeho dátami; záloha kontajnera nezahŕňa adresáre hostiteľa" + ], + [ + "Zariadenie neexistuje na tomto hostiteľovi", + "Pripojte ho alebo ho odstráňte z kontajnera v Proxmox" + ], + [ + "Aplikácia s NVIDIA na hostiteľovi bez ovládača", + "Nainštalujte ovládač NVIDIA a Container Toolkit" + ], + [ + "Záloha bola vytvorená skôr, než existovala kópia kontraktu", + "Kontajner zostáva ako bežný LXC a nie je opäť ponúkaný" + ] + ] + } + }, + { + "calloutInfo": { + "title": "Kontajner, ktorý sa vráti", + "body": "Kontajner obnovený s pôvodným ID zo staršej zálohy, vrátený do snímky alebo presunutý späť z iného uzla nesie kontrakt svojho aktuálneho stavu. Ak sa pri výbere na operáciu jeho kontrakt líši od kontraktu na tomto hostiteľovi, operácia sa nespustí a ponúkne sa obnova kontajnera." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Výsledok obnovy so súkromnou sieťou, poradím spúšťania a zaregistrovanými kontajnermi", + "caption": "Výsledok zotavenia, krok za krokom." + } + } + ] + }, + { + "id": "remove", + "title": "Odstránenie aplikácie OCI", + "blocks": [ + { + "p": "Pred potvrdením je zostavený súhrn zo skutočnej konfigurácie: kontajnery, ktoré sú odstránené, údaje s nimi vymazané, súkromná sieť, ktorá je uvoľnená a hostiteľské adresáre, ktoré sú uchovávané. Potvrdenie je predvolene Nie, pretože údaje z vymazaných diskov nie je možné následne obnoviť." + }, + { + "table": { + "headers": [ + "Prostriedok", + "Po odstránení", + "Dôvod" + ], + "rows": [ + [ + "rootfs a kontajnerové disky", + "Odstránia sa", + "Patria len do kontajnera." + ], + [ + "Adresár hostiteľa", + "Zachová sa aj s obsahom", + "Iné aplikácie ho môžu používať" + ], + [ + "Hostiteľské súbory kontajnera", + "Odstránia sa", + "Jeho záznam konzoly, sieťové parametre sysctl, hookscript pripojenia Rclone a registrácia na karte App v ProxMenux Monitor neslúžia žiadnemu inému kontajneru" + ], + [ + "Súbory zdieľané viacerými inštaláciami", + "Vymazané s poslednou inštaláciou, ktorá ich používa", + "Súbor monitora hostiteľa a hookscript závislostí aplikácií s viacerými kontajnermi" + ], + [ + "Kontrakt inštancie", + "Odstráni sa po úspešnom odstránení aplikácie", + "Už s ním nie je spojený žiadny CT" + ], + [ + "Súkromný most zostavy", + "Uvoľní sa spolu so zostavou", + "Nemá žiadnych členov na pripojenie" + ], + [ + "Sieť zdieľaná súpravou Arr", + "Uvoľní sa s poslednou aplikáciou súpravy", + "Každá aplikácia súpravy je nezávislá a odstraňuje sa samostatne" + ], + [ + "Jeden člen zostavy", + "Neodstránené samostatne", + "Odstráni sa celá aplikácia, aby nezostala neúplná zostava" + ], + [ + "Kontajner na inom uzle klastra", + "Neodstránené", + "Jeho záznam a súbory na hostiteľovi sú na uzle, kde bol nainštalovaný: odstráni sa tam po migrácii späť" + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Prehľad odstránenia s dotknutými kontajnermi, dátami a adresármi hostiteľa", + "caption": "Prehľad odstránenia, postavený z reálnej konfigurácie." + } + } + ] + }, + { + "id": "archives", + "title": "Stiahnuté obrazy a miesto na hostiteľovi", + "blocks": [ + { + "p": "Archív OCI slúži na vytvorenie rootfs; bežiaci CT ho nečíta. Po inštalácii alebo aktualizácii sa zobrazia archívy stiahnuté počas danej operácie aj s ich veľkosťou a ponúkne sa ich odstránenie. Odstránením sa uvoľní miesto bez vplyvu na kontajner alebo jeho dáta." + }, + { + "calloutInfo": { + "title": "Aktualizácie nepotrebujú archív", + "body": "Bez archívu aktualizácia vyrieši uložený kanál a stiahne nový digest. S ním sa archív znova použije iba vtedy, keď sa jeho referencia a integrita zhodujú s požadovaným." + } + } + ] + }, + { + "id": "registry", + "title": "Register a čistenie", + "blocks": [ + { + "p": "Pri otvorení Spravovať nainštalované aplikácie OCI sa najskôr odstránia pozostatky kontajnerov, ktoré už neexistujú na žiadnom uzle klastra: uložený záznam kontajnera odstráneného cez rozhranie Proxmox, uchovávaný ako história, a súbory hostiteľa, ktoré zostali po odstránení. Záznam s nedokončenou operáciou sa zachová, pretože jeho záloha môže byť ešte potrebná. Zväzky ani adresáre hostiteľa sa nikdy neodstraňujú na základe domnienok." + } + ] + }, + { + "id": "cluster", + "title": "V klastri: migrácia a vysoká dostupnosť", + "intro": "Kontajner OCI je bežný LXC Proxmox: možno ho migrovať alebo spravovať pomocou HA ako ostatné, s rovnakými obmedzeniami. Všetko potrebné na spustenie je uložené tam, kde to nájde každý uzol klastra.", + "blocks": [ + { + "table": { + "headers": [ + "Časť", + "Na inom uzle klastra" + ], + "rows": [ + [ + "Konzolový log", + "Kontajner pred spustením vytvorí /var/log/proxmenux/oci na uzle, ktorý ho spúšťa. Každý uzol uchováva záznam spustení, ktoré vykonal." + ], + [ + "Sieťové parametre sysctl a monitor hostiteľa", + "Uchovávajú sa v /etc/pve/proxmenux, ktorý zdieľajú všetky uzly klastra, takže migrovaný kontajner ich nájde." + ], + [ + "Kontajnerové disky", + "Proxmox ich presúva s kontajnerom. HA potrebuje mať ich dáta dostupné na cieľovom uzle cez zdieľané úložisko alebo podporovanú konfiguráciu replikácie." + ], + [ + "Hostiteľské adresáre a zariadenia", + "Rovnaké pravidlá ako všetky LXC: hostiteľský adresár musí existovať na cieľovom uzle a musí byť označený ako zdieľaný a musí tam byť prítomný GPU, Coral alebo NPU." + ], + [ + "Záznam ProxMenux", + "Kontrakt zostáva na uzle, kde bola aplikácia nainštalovaná, a každý uzol číta kópiu v /etc/pve/priv/proxmenux/oci. Migrovaná aplikácia s jedným kontajnerom sa zaregistruje na cieľovom uzle po otvorení Spravovať nainštalované aplikácie OCI alebo po spustení operácie pre danú aplikáciu. Aplikácia s viacerými kontajnermi sa tam ponúkne na obnovu, pretože jej súkromná sieť sa musí vytvoriť na tomto uzle." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Nie je určené pre vysokú dostupnosť", + "body": "Aplikácia s viacerými kontajnermi komunikuje so svojimi členmi cez súkromný most uzla, na ktorom bola nainštalovaná, preto jej členovia zostávajú na tomto uzle. Monitor hostiteľa, napríklad Glances v režime hostiteľa alebo Netdata, sleduje uzol, na ktorom beží; presunutím by sledoval iný uzol." + } + }, + { + "p": "Pri obnove aplikácie dostane záloha obnovená mimo klastra súbory z /etc/pve/proxmenux, ktoré kontajner používa." + } + ] + }, + { + "id": "channel", + "title": "Priebežne aktualizovaná značka neznamená bezobslužnú aktualizáciu", + "blocks": [ + { + "p": "Katalóg nainštaluje priebežne aktualizovanú značku, ktorú publikuje správca, ale skutočný digest sa zistí a zaznamená a zmení sa iba explicitnou aktualizáciou s predbežnou kontrolou a možnosťou návratu späť. ProxMenux Monitor porovná nainštalovaný digest s digestom publikovaným registrom a zobrazí aj odošle upozornenie, keď je dostupný nový obraz." + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/monitor.json b/web/messages/sk/docs/oci-manager/monitor.json new file mode 100644 index 00000000..e8b44003 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "Kontajnery OCI v ProxMenux Monitor | ProxMenux", + "description": "Čo ProxMenux Monitor zobrazuje pre kontajner nainštalovaný cez OCI manager Apps: verzie aplikácie a obrazu, nové obrazy, výstup konzoly a terminál Proxmox VE." + }, + "header": { + "title": "V ProxMenux Monitore", + "description": "Kontajner nainštalovaný cez OCI manager Apps sa zobrazuje v časti VM a LXC ako každý iný LXC. Jeho detail načíta záznam inštalácie: verzie aplikácie a obrazu, nové obrazy, výstup konzoly a terminál.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Záznam je zdroj", + "body": "OCI manager Apps vie, čo nainštaloval a odkiaľ to pochádza. Monitor načíta tento záznam inštalácie namiesto vyhľadávania aplikácie v kontajneri. Tieto údaje sú preto rovnaké pri spustenom aj zastavenom kontajneri." + } + }, + { + "table": { + "headers": [ + "Karta", + "Čo sa mení pri kontajneri OCI" + ], + "rows": [ + [ + "Stav", + "Prepínač Watchdog pod Start at boot: aplikácia sa znova spustí, keď sa sama zastaví" + ], + [ + "Aplikácia", + "Aplikácia je identifikovaná zo záznamu a aktualizácie sú sledované obrazom" + ], + [ + "Aktualizácie", + "Obraz je aktualizovaný alebo aplikácia upravená s rovnakým tokom ako OCI menu" + ], + [ + "Pripojenia", + "Kontajnerové disky a hostiteľské adresáre s ich použitím" + ], + [ + "Logy", + "Len pre kontajnery OCI: výstup konzoly aplikácie" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "Aplikácia", + "intro": "Záložka App ponúka nainštalovanú aplikáciu ako detekciu s názvom, logom, portom a schémou záznamu. Zaregistrovanie otvára editor metódou Obraz OCI (nainštalovaný ProxMenux), ktorá nepotrebuje konfiguráciu.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Aplikácia", + "body": "Verzia aplikácie v obraze, načítaná zo samotného obrazu: z jeho prostredia alebo štítku org.opencontainers.image.version. Je to informatívny údaj." + }, + { + "icon": "archive", + "title": "Obraz", + "body": "Dátum zostavenia a digest nainštalovaného obrazu. O aktualizácii sa rozhoduje tu: nainštalovaný digest sa porovná s tým, ktorý register dnes publikuje pre rovnakú značku." + } + ] + } + }, + { + "list": { + "items": [ + "Keď register zverejní nový digest, na karte sa zobrazí Nový obraz s jeho dátumom a digest, aj keď sa verzia aplikácie vo vnútri nezmenila: prestavba na aktualizovanej báze je aktualizáciou.", + "Keď sa hodnoty digest zhodujú, karta zobrazí Verzia.", + "Karta odkazuje na úložisko obrazu: jeho projekt GitHub alebo jeho stránku Docker Hub pre oficiálny obraz.", + "Prístupové spojenie používa LAN adresu kontajnera, aj pre hlavného člena viackontajnerovej aplikácie, ktorá má druhú adresu vo svojej súkromnej sieti.", + "Tlačidlo Obnoviť údaje znovu načíta záznam aj register obrazov. Možnosti vyhľadávania alebo registrácie ďalších aplikácií sa neponúkajú: kontajner obsahuje presne aplikáciu zo svojho záznamu." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "App záložka kontajnera OCI s verziou aplikácie, obrazom a odkazom na úložisko", + "caption": "Karta Aplikácia kontajnera nainštalovaného cez OCI manager Apps." + } + }, + { + "p": "Kontrola prebieha raz denne s kontrolami aktualizácií monitora a Obnoviť údaje ju spustí okamžite. Nový obraz sa odošle ako upozornenie prostredníctvom kanálov nakonfigurovaných v Upozornenia. Aktualizácia sa aplikuje na karte Aktualizácie." + } + ] + }, + { + "id": "updates", + "title": "Aktualizácie", + "intro": "Pri kontajneri nainštalovanom cez OCI manager Apps zobrazuje karta Aktualizácie aplikáciu s nainštalovaným obrazom a prípadným novým obrazom z registra, rovnako ako pri ostatných aplikáciách. Nástroje na aktualizáciu balíkov a aplikácií bežného LXC sa nezobrazujú.", + "blocks": [ + { + "table": { + "headers": [ + "Tlačidlo", + "Čo to otvára?" + ], + "rows": [ + [ + "Aktualizácia", + "Aktualizácia z časti Spravovať nainštalované aplikácie OCI pre tento kontajner v termináli Monitor. Možno ju spustiť aj bez nového obrazu; vtedy sa nič nezmení. Vo viackontajnerovej aplikácii aktualizuje všetkých členov." + ], + [ + "Upraviť", + "Editor aplikácie (zdroje, sieť, cesty a GPU). Vo viackontajnerovej aplikácii pridáva alebo odstraňuje ďalšie cesty a zariadenia aplikačného kontajnera a v Immich mení, čo vykonáva rozpoznávanie." + ], + [ + "Obnoviť", + "Nahradí Aktualizáciu po prerušení operácie na kontajneri a otvorí jeho obnovu." + ], + [ + "Obnoviť (obnovený kontajner)", + "Zobrazí sa pri kontajneri obnovenom zo zálohy, ktorý na tomto hostiteľovi nemá kontrakt, v časti Obnovená aplikácia OCI. Otvorí obnovu v termináli Monitor; po registrácii sa zobrazia možnosti Aktualizovať a Upraviť." + ] + ] + } + }, + { + "p": "Externé zmeny, hostiteľské adresáre a multikontajnerové aplikácie sú spracované ako v ponuke OCI. Vo viackontajnerovej aplikácii sa tlačidlá a možnosti zobrazia len na hlavnom kontajneri; ostatní členovia zobrazujú svoj obraz, poznámku, že sú aktualizované z hlavného kontajnera a tlačidlo, ktoré ho otvára. Keď sa terminál uzavrie, obraz, záznam a pripojenia sa znovu prečítajú." + }, + { + "table": { + "headers": [ + "Možnosť", + "Správanie" + ], + "rows": [ + [ + "Ponechať zálohu vytvorenú pred aktualizáciou", + "Každá aktualizácia zálohuje kontajner, aby ho bolo možné obnoviť pri zlyhaní aktualizácie. Pri tejto voľbe sa tá istá záloha ponechá vo vybranom úložisku a zobrazí medzi zálohami CT; pri Proxmox Backup Server sa záloha zapíše pred aktualizáciou." + ], + [ + "Plánované aktualizácie", + "Obraz sa aktualizuje v zvolenom čase iba vtedy, keď register publikuje nový obraz; voliteľne až po jeho dosiahnutí veku 1, 3, 7 alebo 14 dní. Kontajner so zmenami mimo ProxMenux sa preskočí a nahlási." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Logy", + "intro": "Karta Záznamy sa zobrazuje iba pri kontajneroch nainštalovaných cez OCI manager Apps, medzi kartami Pripojenia a Zálohy. Zobrazuje štandardný a chybový výstup hlavného procesu obrazu, teda rovnaký výstup ako docker logs pri kontajneri Docker.", + "blocks": [ + { + "list": { + "items": [ + "Výstup sa uchováva na hostiteľovi v /var/log/proxmenux/oci/VMID.console.log (režim 0600) od prvého spustenia aj cez reštarty, takže ho možno čítať pri zastavenom kontajneri.", + "Súbor sa otáča na 10 MB, pričom sa uchovávajú tri komprimované kópie (/etc/logrotate.d/proxmenux-oci).", + "Každý začiatok kontajnera je označený vo výstupe. Karta štandardne zobrazuje výstup od posledného spustenia; Úplná história zobrazuje aj predchádzajúce.", + "Po odstránení aplikácie sa odstráni jej výstup.", + "Zobrazuje sa posledných 100, 500 alebo 1000 riadkov. Počas behu kontajnera sa nové riadky sledujú naživo; posunutie nahor sledovanie pozastaví a tlačidlo Sledovať ho obnoví.", + "Filter zobrazí iba riadky, ktoré obsahujú text a Stiahnuť uloží načítané riadky.", + "Farebné kódy sa odstránia a riadok opakovane prekresľovaný ukazovateľom priebehu sa zobrazí v konečnom stave.", + "Karta načíta súbor pri každom otvorení; nepoužíva vyrovnávaciu pamäť." + ] + } + }, + { + "calloutInfo": { + "title": "Prihlasovacie údaje pri prvom spustení", + "body": "Obrazy, ktoré vytlačia vygenerované heslo na ich prvom štarte, to nechajú v tomto výstupe, ako to robí docker logs. Inštalátor ho odtiaľ prečíta, aby ju ukázal v súhrne." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Karta záznamov s výstupom konzoly kontajnera OCI, výberom počtu riadkov, filtrom a tlačidlom sledovania", + "caption": "Konzolový výstup kontajnera OCI." + } + } + ] + }, + { + "id": "terminal", + "title": "konzola Proxmox VE", + "blocks": [ + { + "p": "Obraz OCI spúšťa vlastný proces ako PID 1 a neobsahuje prihlasovaciu službu. Predvolená konzola LXC by preto otvorila terminál bez procesu, ktorý by odpovedal na prihlásenie. Kontajnery nainštalované cez OCI manager Apps sa vytvárajú s cmode: shell: konzola Proxmox VE otvorí shell pomocou lxc-attach, čo zodpovedá docker exec. Spustená aplikácia zostáva nedotknutá." + }, + { + "list": { + "items": [ + "Shell je ten, ktorý súbor /etc/passwd v obraze priraďuje používateľovi root. Ak root nemá shell alebo obraz žiadny neobsahuje, zostane predvolená konzola.", + "Shell beží ako root vnútri kontajnera, bez hesla. O tom, kto ho môže otvoriť, rozhoduje VM.Console povolenie Proxmox VE.", + "Terminál monitora vstupuje do kontajnera s pct enter, ktorý funguje rovnako." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Pripojenia", + "blocks": [ + { + "p": "Karta Pripojenia uvádza disky kontajnera a jeho adresáre hostiteľa. Využitie disku kontajnera na blokovom úložisku, napríklad LVM-thin, sa číta zo súborového systému disku, ktorý je pripojený iba v kontajneri." + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/stacks.json b/web/messages/sk/docs/oci-manager/stacks.json new file mode 100644 index 00000000..5a0c1640 --- /dev/null +++ b/web/messages/sk/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Aplikácie s viacerými kontajnermi | ProxMenux", + "description": "Ako OCI manager Apps mení aplikáciu s databázou a vyrovnávacou pamäťou na koordinované natívne LXC: plán, súkromná sieť, hook závislostí, overenie a transakčné aktualizácie." + }, + "header": { + "title": "Aplikácie s viacerými kontajnermi", + "description": "Aplikácia so svojou databázou, vyrovnávacou pamäťou a ďalšími službami sa stáva niekoľkými koordinovanými LXC, nainštalovanými a aktualizovanými ako jedna.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Jedna aplikácia pre užívateľa, niekoľko LXC pre Proxmox VE", + "body": "Definícia viacerých kontajnerov je jedným záznamom v katalógu. Inštalátor vytvorí jeden natívny LXC pre každú službu a explicitne spravuje ich závislosti. Immich, Nextcloud, Paperless-ngx a Tandoor sú nainštalované týmto spôsobom." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Stack plán", + "app": "Aplikácia LXC", + "appNet": "LAN + súkromné", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "súkromné" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. Plán ešte pred vytvorením kontajnerov", + "blocks": [ + { + "p": "Počas spracovania definície Compose sa nevytvára žiadny LXC. Najprv vznikne úplný plán so všetkými členmi, obrazmi, VMID, sieťami, cestami, tajnými údajmi, poradím a kontrolami stavu. Ak plán nie je konzistentný, inštalácia sa nezačne." + }, + { + "table": { + "headers": [ + "Prvok plánu", + "Obsah", + "Overenie pred vytvorením čohokoľvek" + ], + "rows": [ + [ + "Členovia", + "Hlavná aplikácia, databáza, vyrovnávacia pamäť, strojové učenie a iné závislosti", + "Jedinečné VMID, známe úlohy a presne jeden hlavný člen" + ], + [ + "Obrazy", + "Priebežne aktualizovaný odkaz, architektúra a určený digest každej služby", + "Všetky existujú, podporujú architektúru a prejdú kontrolou integrity OCI" + ], + [ + "Sieť", + "Súkromný most, podsieť, pevná adresa pre každú službu a prístup LAN pre hlavného člena", + "Žiadna zrážka s existujúcimi mostmi alebo podsietami a žiadna opakovaná adresa" + ], + [ + "Perzistencia", + "Kontajnerové disky, hostiteľské adresáre, majitelia a zálohovanie", + "Žiadne prekrývanie ciest, dostupné úložisko a deklarované oprávnenia" + ], + [ + "Tajomstvá", + "Heslo databázy, aplikačné kľúče a počiatočné prihlasovacie údaje", + "Generované raz a podané iba členom, ktorí ich používajú" + ], + [ + "Životný cyklus", + "Poradie spúšťania, poradie zastavovania a kontrola stavu každého člena", + "Hlavná aplikácia sa spúšťa posledná a zastavuje prvá" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Vytváranie jednotlivých členov", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Rezervujte každý VMID", + "body": "Súpis Proxmox VE a inštančný register sú kontrolované. Existujúce CT nie je prijaté a zmluva, ktorá stále patrí do inej inštancie, sa opätovne nepoužíva." + }, + { + "title": "Pripravte každý obraz", + "body": "Všetky obrazy sú vyriešené, stiahnuté a overené pred vytvorením prvého kontajnera." + }, + { + "title": "Vytvoriť každý rootfs", + "body": "Importujú sa oficiálne metadáta OCI a pridá sa identita inštancie ProxMenux a rola člena." + }, + { + "title": "Súkromná sieť", + "body": "Vytvorí sa most a podsieť a každý člen dostane pevnú adresu; iba hlavný člen navyše dostane rozhranie LAN." + }, + { + "title": "Perzistencia", + "body": "Každá databáza a konfigurácia dostane svoj vlastný disk kontajnera; iba údaje určené na zdieľanie používajú adresáre hostiteľov." + }, + { + "title": "Prostredie", + "body": "Zapíšu sa interné koncové body, zdieľané tajné údaje a premenné služieb. Aplikácia pristupuje k svojim závislostiam cez ich vyhradené súkromné adresy." + }, + { + "title": "Záznam", + "body": "Každý člen zaznamenáva svoju natívnu konfiguráciu, rootfs zmeny, ktoré aktualizácie musia reprodukovať a jeho vzťah k stohu." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Spustiť a skontrolovať každý kontajner", + "intro": "Vytvorený LXC ešte neznamená pripravenú službu. Závislosti sa spúšťajú v určenom poradí a každá prejde kontrolou špecifickou pre danú službu.", + "blocks": [ + { + "table": { + "headers": [ + "Služba", + "Kontrola", + "Čo ukazuje" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready v CT s očakávaným hostiteľom, používateľom a databázou", + "Server akceptuje spojenia pre nakonfigurovanú databázu" + ], + [ + "Redis / Valkey", + "redis-cli alebo valkey-cli s príkazom PING na jeho súkromnej adrese", + "Broker počúva a odpovedá" + ], + [ + "Immich strojové učenie", + "HTTP GET /ping, plus kontrola zvoleného runtime GPU", + "Služba odpovedá a požadovaná akcelerácia neprešla na CPU" + ], + [ + "Nextcloud", + "GET /status.php s installed=true, maintenance=false a needsDbUpgrade=false", + "Inicializácia ukončená bez ďalšej migrácie" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP na adrese LAN a skutočnom porte služby", + "Frontend a jeho závislosti poskytujú aplikáciu" + ], + [ + "Hlavná aplikácia", + "Koncový bod jeho šablóny, napríklad /api/server/ping v Immich", + "Celý zásobník pracuje prostredníctvom aplikácie, ktorá používa závislosti" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Spustený neznamená funkčný", + "body": "Stav spustené len hovorí, že proces LXC existuje. Tam, kde služba ponúka lepšiu kontrolu, sa používa exec alebo HTTP kontrola s časovým limitom. Ak člen zastaví alebo zlyhá jeho kontrola, zásobník nie je deklarovaný ako nainštalovaný." + } + } + ] + }, + { + "id": "hook", + "title": "4. Hook závislostí", + "blocks": [ + { + "p": "Hook sa nastaví iba na hlavnom kontajneri zostavy so závislosťami. Proxmox VE uchováva skript ako snippet a spúšťa ho ako hookscript daného CT. Recept zostavy nie je zapísaný v skripte: nachádza sa v samostatnom súkromnom kontrakte." + }, + { + "codeGrid": { + "items": [ + { + "title": "Hlavné nastavenie CT", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Súkromná zmluva (príklad)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Kompletný zdroj proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "Skript je rovnaký pre každý zásobník. VMID, názvy, kontrolné typy a timeouty pochádzajú zo súkromného kontraktu /etc/pve/priv/proxmenux-stack-VMID.json každého zásobníka." + }, + { + "steps": { + "items": [ + { + "title": "Proxmox VE zavolá pre-start", + "body": "Pred spustením hlavného CT sa hook spustí s jeho VMID a fázou životného cyklu." + }, + { + "title": "Zámok na zásobník", + "body": "flock na /run/lock/proxmenux-stack-VMID.lock zabraňuje dvom štartovým sekvenciám súčasne." + }, + { + "title": "Zmluva je prečítaná a overená", + "body": "Požaduje sa známa schéma, numerické závislosti, etikety, exec, http alebo kontrola chodu a pozitívny timeout." + }, + { + "title": "Závislosti v poradí", + "body": "Každý CT musí existovať; zastavený sa spustí, už spustený sa nereštartuje." + }, + { + "title": "Čakanie na pripravenosť služby", + "body": "Kontrola beží každé dve sekundy, a CT je tiež potvrdené, že stále beží." + }, + { + "title": "Hlavné CT začína", + "body": "Keď je každá závislosť pripravená, pre-start končí a Proxmox VE spustí aplikáciu." + } + ] + } + }, + { + "calloutInfo": { + "title": "Háčik nezastaví závislosť", + "body": "Fázy post-start, pre-stop a post-stop nič nevykonávajú. Zastavenie hlavného CT ponechá PostgreSQL, Redis, Valkey alebo strojové učenie spustené. Hook určuje poradie spúšťania; nemení viaceré LXC na jeden proces." + } + }, + { + "table": { + "headers": [ + "Typ", + "Príklad", + "Neskoršie spustenia" + ], + "rows": [ + [ + "Závislý zásobník", + "Immich, Nextcloud, aplikácia s PostgreSQL", + "Hook hlavného CT spustí závislosti a čaká na ich pripravenosť." + ], + [ + "Súprava aplikácií", + "Súprava Arr", + "Žiadny hlavný člen: každý LXC sleduje svoje vlastné onboot" + ], + [ + "Samostatná aplikácia", + "Jellyfin", + "Proxmox VE spustí daný LXC priamo" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Kontrola stohu ako celku", + "blocks": [ + { + "list": { + "items": [ + "Každý VMID existuje, je jedinečný a udržiava očakávanú inštančnú identitu.", + "Každý kontrakt patrí do rovnakej zostavy, zachováva svoju rolu a zaznamenanú natívnu konfiguráciu.", + "Hlavný člen je posledný v start_order a prvý v stop_order.", + "Nechýba žiadny člen, zväzok ani úprava rootfs.", + "Hook odkazuje na oficiálny snippet, jeho obsah sa nezmenil a jeho kontrakt zodpovedá receptu zostavy.", + "Zistené obrazy a hodnoty digest zodpovedajú pripraveným archívom OCI.", + "Zariadenia, profily GPU, pripojenia, tajomstvá a koncové body stále zodpovedajú zmluvám.", + "Keď všetky závislosti prejdú kontrolou, koncový bod hlavnej aplikácie overí integráciu medzi členmi." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Správa nainštalovaného zásobníka", + "intro": "V časti Spravovať nainštalované aplikácie OCI vedie každý člen k celej zostave. Ponuka obsahuje Aktualizovať všetky kontajnery aplikácie, Upraviť dodatočné cesty a zariadenia, Znova vytvoriť všetky kontajnery s uloženou konfiguráciou a Odstrániť: vymazať aplikáciu a jej kontajnery. Opätovné vytvorenie zostaví každý kontajner z pôvodného obrazu podľa uloženého digestu bez hľadania novšieho obrazu. Používa rovnaké kroky ako aktualizácia a ponúka sa pre aktualizovateľné zostavy.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Každý člen", + "body": "Zmluva člena menuje hlavné VMID a kompletný zoznam členov." + }, + { + "title": "Stack je reprodukovateľný", + "body": "Overia sa identity, kontrakty, hook, úpravy a koordinované opätovné vykonanie daného receptu." + }, + { + "title": "Prvé obrazy", + "body": "Aplikácia sa nezastaví, kým sa každý digest neurčí, nestiahne a neoverí." + }, + { + "title": "Zastaviť súčasti a zálohovať celú zostavu", + "body": "Overené natívne zálohy sa vytvoria pri zastavenej zostave, aby aplikácia a databázy zodpovedali rovnakému okamihu." + }, + { + "title": "Aktualizovať a skontrolovať každého člena", + "body": "Adaptácia, sieť, pripojenia, tajomstvá a zariadenia sa používajú opäť pred kontrolou stavu každého člena." + }, + { + "title": "Publikovať alebo obnoviť všetko", + "body": "Nové zmluvy sa uverejňujú iba vtedy, keď každý člen prejde. Ak jeden zlyhá, každý člen je obnovený." + } + ] + } + }, + { + "table": { + "headers": [ + "Upraviť", + "Čo sa zmení?", + "Čo sa uchováva" + ], + "rows": [ + [ + "Extra cesty a zariadenia", + "Pridajú sa do aplikačného kontajnera alebo sa z neho odstránia a kontajner sa reštartuje. Nič sa znova nevytvára.", + "Údaje aplikácie, jej databázy a ostatných kontajnerov" + ], + [ + "Čo spúšťa rozpoznanie (Immich)", + "Kontajner strojového učenia dostane obraz a zariadenia pre CPU alebo GPU hostiteľa. Celá aplikácia sa zastaví a aktualizuje rovnakým postupom ako pri aktualizácii.", + "Modelová vyrovnávacia pamäť, knižnica a databáza. Ak niečo zlyhá, predchádzajúce kontajnery a predchádzajúca voľba sú obnovené" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Zostava bez koordinovaného opätovného vykonania sa neaktualizuje", + "body": "Ak sa príprava zásobníka nedá reprodukovať, aktualizácia sa zamietne pred zastavením zásobníka. Aplikácia funguje tak, ako je." + } + } + ] + }, + { + "id": "failure", + "title": "6. Keď niečo zlyhá", + "blocks": [ + { + "p": "Ak počas inštalácie nastane chyba, zastavia a odstránia sa neúplné kontajnery vytvorené touto operáciou aj súkromný most, ak ho operácia vytvorila. Zostava sa nezverejní ako platná, kým neskončí celý postup." + }, + { + "p": "Aktualizácia je transakčná: všetky obrazy sú pripravené ako prvé, potom stack zastaví, každý člen je zálohovaný a záloha overená, a len potom je každý rootfs nahradený. Členovia začínajú jeden po druhom so zdravotnou kontrolou; ak jeden zlyhá, každý člen je obnovený z rovnakého súboru záloh, takže databáza a aplikácia nikdy nepatria do rôznych momentov." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Pripravte každý obraz", + "stop": "Stop, hlavný člen prvý", + "backup": "Overená záloha každého CT", + "replace": "Nahradiť rootfs", + "healthy": "Sú všetky funkčné?", + "yes": "Áno", + "no": "Nie", + "commit": "Zverejniť kontrakty", + "rollback": "Obnoviť každého člena" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "Čo zásobník nezahŕňa", + "blocks": [ + { + "list": { + "items": [ + "Súprava nainštalovaná v jednom postupe, napríklad súprava Arr, nie je zostavou so závislosťami: jej kontajnery nemajú určené vzájomné poradie spúšťania.", + "Súkromná sieť nenahrádza autentifikáciu, TLS ani konfiguráciu každej aplikácie.", + "Zálohovanie jedného LXC neobsahuje most, hák ani zmluvy zvyšku zásobníka.", + "Prowlarr, Sonarr alebo Radarr neprijímajú žiadne indexátory, profily alebo poskytovateľov z ProxMenux." + ] + } + } + ] + } + ] +} diff --git a/web/messages/sk/docs/oci-manager/storage-network.json b/web/messages/sk/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..f0ff007e --- /dev/null +++ b/web/messages/sk/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Údaje, cesty a sieť | ProxMenux", + "description": "Ako OCI manager Apps uchováva údaje kontajnera OCI: disky kontajnera, adresáre hostiteľa, pripojenia cez rclone, adresy a súkromné siete." + }, + "header": { + "title": "Údaje, cesty a sieť", + "description": "Čo sa nachádza v rootfs, čo sa zachová pri jeho výmene, ako sa zdieľajú údaje medzi kontajnermi a ako každý kontajner získa svoju adresu.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Trvalé uloženie údajov sa určí pred vytvorením LXC", + "body": "Pred vytvorením kontajnera sa načítajú zväzky deklarované obrazom a jeho súborom Compose. Každá cesta, ktorá sa má zachovať po aktualizácii, sa stane prípojným bodom nezávislým od rootfs. Rootfs tak obsahuje iba súbory patriace obrazu a možno ho vymeniť." + } + } + ] + }, + { + "id": "questions", + "title": "Na čo sa pýta inštalátor", + "intro": "Šablóna poskytuje cesty potrebné pre aplikáciu. Každá sa umiestni na disk kontajnera alebo do adresára hostiteľa. Pred súhrnom možno pridať ďalšie cesty.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Potrebné cesty", + "body": "Zobrazia sa /config, /data, knižnice, stiahnuté súbory a všetky zväzky deklarované aplikáciou." + }, + { + "title": "Umiestnenie", + "body": "Každá cesta sa umiestni na disk kontajnera alebo do existujúceho adresára hostiteľa." + }, + { + "title": "Úložisko a veľkosť", + "body": "Na úložisku Proxmox VE sa vytvorí disk kontajnera so zadanou veľkosťou. Predvolená inštalácia používa local-lvm. Rozšírená inštalácia sa pri každej ceste pýta najprv na úložisko a potom na veľkosť; navrhuje úložisko rootfs. Disk sa zobrazuje ako vm-VMID-disk-N a pripája sa ako mpN." + }, + { + "title": "Adresár hostiteľa", + "body": "Adresár hostiteľa sa zadáva jeho cestou. Ak neexistuje, vytvorí sa a jeho vlastníkom bude používateľ hostiteľa, na ktorého sa mapuje používateľ kontajnera." + }, + { + "title": "Ďalšie cesty", + "body": "Pred inštaláciou možno pridať ďalšie dvojice cesty alebo zväzku hostiteľa a cesty v kontajneri." + }, + { + "title": "Súhrn", + "body": "Pred vytvorením CT sa zobrazí úplné mapovanie ciest a uloží sa do kontraktu inštancie." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "Dve možnosti trvalého uloženia", + "blocks": [ + { + "table": { + "headers": [ + "Vlastnosť", + "Disk kontajnera", + "Adresár hostiteľa" + ], + "rows": [ + [ + "V konfigurácii", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Záloha kontajnera (vzdump)", + "Zahrnutý pri backup=1", + "Nezahrnutý" + ], + [ + "Veľkosť", + "Pevná; zväčšuje sa zmenou veľkosti prípojného bodu", + "Voľné miesto v súborovom systéme alebo datasete hostiteľa" + ], + [ + "Ostatné kontajnery", + "Pripája ho iba kontajner, ktorému patrí", + "Ten istý adresár možno pripojiť do viacerých kontajnerov" + ], + [ + "Snapshoty a obnova", + "Spravuje ich Proxmox VE spolu s CT", + "Spravujú sa na úložisku hostiteľa" + ], + [ + "Pri odstránení aplikácie", + "Odstráni sa spolu s kontajnerom", + "Zachová sa aj s obsahom" + ], + [ + "Pri presune CT na iný uzol", + "Presunie sa spolu s CT", + "Na druhom uzle musí existovať rovnaká cesta" + ] + ] + } + }, + { + "p": "Rootfs je vyhradený pre binárne súbory a obsah obrazu. Aktualizácia alebo opätovné vytvorenie ho nahradí bez zásahu do ktoréhokoľvek z dvoch typov prípojných bodov." + } + ] + }, + { + "id": "example", + "title": "Príklad: kontajner s oboma umiestneniami", + "blocks": [ + { + "code": { + "title": "Jellyfin nainštalovaný ako CT 151 na local-lvm (úryvok)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "Aktualizácia alebo opätovné vytvorenie nahradí iba rootfs: mp0 zachová používateľov, knižnice a nastavenia a mp1 naďalej sprístupňuje rovnaké médiá. Obnova zálohy CT obnoví /config. Adresár s médiami sa v prípade potreby obnovuje zo zálohy úložiska hostiteľa." + }, + { + "flow": { + "nodes": [ + { + "label": "Kontrakt", + "detail": "/config" + }, + { + "label": "Umiestnenie", + "detail": "Disk kontajnera\nalebo adresár hostiteľa" + }, + { + "label": "LXC", + "detail": "Pre aplikáciu\nvždy /config" + } + ], + "caption": "Aplikácia vidí cestu deklarovanú obrazom; mení sa iba miesto jej uloženia." + } + } + ] + }, + { + "id": "shared", + "title": "Jeden adresár hostiteľa, viacero kontajnerov", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Adresár hostiteľa", + "config": "Vlastná konfigurácia" + } + } + }, + { + "p": "Každý kontajner uchováva svoju konfiguráciu na vlastnom disku. Knižnica alebo stiahnuté súbory sú v jednom adresári hostiteľa, pripojenom na rovnakej vnútornej ceste v každom kontajneri. Cesta, do ktorej jedna aplikácia zapisuje, je preto rovnaká ako cesta, z ktorej druhá číta." + } + ] + }, + { + "id": "rclone", + "title": "Cloudové úložisko cez aplikáciu rclone", + "intro": "Položka rclone v katalógu okrem inštalácie ponúka aj možnosť Aktivovať pripojenie v existujúcom kontajneri OCI s rclone. Pripojí sa vzdialené úložisko, ktoré už bolo vytvorené a autorizované vo webovom rozhraní rclone, a sprístupní sa na hostiteľovi. Ostatné kontajnery ho môžu používať ako adresár hostiteľa.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Kontajner a vzdialené úložisko", + "body": "VMID kontajnera s rclone, presný názov vzdialeného úložiska a voliteľne cesta v ňom." + }, + { + "title": "Názov pripojenia a vyrovnávacia pamäť", + "body": "Názov pripojenia a režim vyrovnávacej pamäte VFS: off, minimal, writes alebo full (predvolený)." + }, + { + "title": "Sprístupnené zobrazenia", + "body": "Spoločný koreňový adresár, predvolene /mnt/oci-shared, obsahuje zobrazenie na čítanie aj zápis v /mnt/oci-shared/remotes/NAME a zobrazenie iba na čítanie v /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Aktivácia", + "body": "Po potvrdení sa CT zastaví, nastaví sa jeho spúšťací príkaz a hookscript Proxmox VE a znova sa spustí. Operácia čaká, kým budú obe zobrazenia pripojené na hostiteľovi." + } + ] + } + }, + { + "calloutInfo": { + "title": "Ak sa pripojenie neaktivuje", + "body": "Obnoví sa predchádzajúca konfigurácia kontajnera a kontajner sa znova spustí. Ak aktivácia zlyhá, rclone tak zostane v pôvodnom stave." + } + } + ] + }, + { + "id": "network", + "title": "Adresy a siete", + "intro": "Aplikácia s jedným kontajnerom potrebuje adresu. Aplikácia s viacerými kontajnermi potrebuje aj stabilnú sieť medzi svojimi členmi.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Aplikácia s jedným kontajnerom", + "body": "Vyberie sa sieťový most a DHCP alebo pevná adresa vo formáte CIDR. LXC má vlastnú adresu a súhrn zobrazuje úplné URL služieb." + }, + { + "icon": "waypoints", + "title": "Aplikácia s viacerými kontajnermi", + "body": "Vyhľadá sa voľná podsieť a vytvorí sa trvalý súkromný sieťový most. Každý člen — aplikácia, databáza a vyrovnávacia pamäť — v ňom dostane pevnú adresu." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Dostupná adresa\niba pre hlavnú službu" + }, + { + "label": "Hlavný LXC", + "detail": "Web / API\nLAN + súkromná sieť" + }, + { + "label": "Súkromná sieť", + "detail": "PostgreSQL · Valkey · ML\npevné adresy" + } + ], + "caption": "Závislé služby komunikujú cez súkromnú sieť a nemajú adresu v LAN." + } + }, + { + "p": "Kontrakt zostavy uchováva sieťový most, podsieť, adresy a vzťahy medzi službami. Pri aktualizácii aplikácie sa pre všetkých členov znovu použije rovnaká topológia. ProxMenux Monitor otvára hlavný kontajner na jeho adrese v LAN, nie na adrese v súkromnej sieti." + } + ] + }, + { + "id": "ownership", + "title": "Vlastníctvo súborov a adresárov", + "blocks": [ + { + "list": { + "items": [ + "Nové adresáre sa vytvárajú s UID a GID, na ktoré sa mapuje neprivilegovaný LXC.", + "Vlastníctvo existujúcich adresárov sa nemení rekurzívne.", + "Sockety, systémové súbory a citlivé cesty sa neponúkajú ako bežné adresáre hostiteľa." + ] + } + } + ] + }, + { + "id": "backup", + "title": "Čo obsahuje záloha kontajnera", + "blocks": [ + { + "table": { + "headers": [ + "Položka", + "V zálohe vzdump kontajnera CT", + "Kde sa uchováva" + ], + "rows": [ + [ + "Rootfs OCI", + "Áno", + "V zálohe CT; možno ho tiež znovu vytvoriť z obrazu a kontraktu" + ], + [ + "Disk kontajnera s backup=1", + "Áno", + "Záloha CT" + ], + [ + "Adresár hostiteľa", + "Nie", + "Záloha úložiska hostiteľa" + ], + [ + "Kontrakt inštancie", + "Áno, kópia uložená v CT", + "/.proxmenux/oci-record.json v CT; ďalšie kópie hostiteľa a klastra sa uchovávajú samostatne" + ], + [ + "Aplikácia s viacerými kontajnermi", + "Každý člen vo vlastnej zálohe", + "Zálohy všetkých členov spolu s kontraktom zostavy a jej sieťovým mostom na hostiteľovi" + ] + ] + } + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/architecture.json b/web/messages/sv/docs/oci-manager/architecture.json new file mode 100644 index 00000000..fa8bb5ab --- /dev/null +++ b/web/messages/sv/docs/oci-manager/architecture.json @@ -0,0 +1,171 @@ +{ + "meta": { + "title": "Så anpassas en OCI-avbildning | ProxMenux", + "description": "Från avbildningens arkiv och Compose-fil till en granskningsbar mall, en distributionsplan och en LXC direkt i Proxmox VE, utan Docker i containern." + }, + "header": { + "title": "Så anpassas en OCI-avbildning", + "description": "Från avbildningens arkiv och Compose-fil till en granskningsbar mall, en distributionsplan och en LXC direkt i Proxmox VE, utan att installera Docker i containern.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "pipeline", + "title": "Anpassningsflödet", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]", + "labels": { + "repo": "Avbildningens arkiv", + "converter": "Konverterare", + "template": "JSON-mall", + "blockers": "Inga hinder?", + "no": "Nej", + "yes": "Ja", + "review": "Granskning / overlay", + "plan": "Distributionsplan", + "lxc": "LXC direkt i Proxmox VE" + } + } + }, + { + "p": "Konverteraren läser avbildningen och Compose-filen som projektet publicerar och skapar en JSON-mall. Om krav ännu inte har kunnat anpassas och hindrar installationen granskas mallen innan den publiceras i katalogen. En fil med granskade anpassningar, en så kallad overlay, löser dessa hinder. Bara mallar utan kvarstående hinder erbjuds för installation." + } + ] + }, + { + "id": "template", + "title": "Vad mallen bevarar", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Avbildningens identitet", + "body": "Arkiv, löpande uppdaterad tagg, arkitektur, fastställd digest och källrevision." + }, + { + "icon": "braces", + "title": "Containerkontrakt", + "body": "Entrypoint, Cmd, miljö, användare, arbetskatalog, stoppsignal, portar och volymer." + }, + { + "icon": "layers", + "title": "Anpassning till Proxmox VE", + "body": "Resurser, säkerhet, monteringspunkter, enheter, sysctls, hälsokontroller och de anpassningar som behövs för var och en, med motivering." + }, + { + "icon": "shield", + "title": "Kompatibilitet", + "body": "Nycklar som stöds, krav som ännu inte anpassats och hindrar installationen samt status för varje validering." + } + ] + } + } + ] + }, + { + "id": "sources", + "title": "OCI anger processen, Compose anger miljön", + "blocks": [ + { + "table": { + "headers": [ + "Källa", + "Exempel", + "Resultat i Proxmox VE" + ], + "rows": [ + [ + "OCI-metadata", + "Entrypoint, Cmd, User", + "Proxmox VE importerar dem när CT:n skapas" + ], + [ + "Docker Compose", + "environment, volumes, devices", + "Miljöposter för LXC, mpN och devN" + ], + [ + "ProxMenux-profil", + "GPU, hälsokontroll, inloggningsuppgifter", + "Frågor och granskade anpassningar" + ], + [ + "Användare", + "VMID, lagring, nätverk", + "Instanskontraktet" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Vad som händer under installationen", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Fastställ avbildningen", + "body": "Registret frågas, värdens arkitektur väljs och den exakta digest som den löpande uppdaterade taggen pekar på fastställs." + }, + { + "title": "Hämta och verifiera", + "body": "Skopeo hämtar avbildningen som ett OCI-arkiv. Innan något skapas kontrolleras varje lager mot sin digest och packas upp. Om hämtningen är skadad görs ett nytt försök innan installationen avbryts." + }, + { + "title": "Skapa filsystemet", + "body": "pct create skapar rootfs från arkivet och bevarar avbildningens officiella processmetadata." + }, + { + "title": "Anslut resurserna", + "body": "Deklarerade volymer, nätverk, miljö, enheter och säkerhetsprofiler kopplas in." + }, + { + "title": "Konsol", + "body": "Containerns konsolutdata sparas på värden. Konsolen i Proxmox VE öppnar ett skal om avbildningen innehåller ett." + }, + { + "title": "Kontrollera", + "body": "Vid första starten väntar installationen på en adress och på att tjänsten ska svara. Ett misslyckande rapporteras inte som en lyckad installation." + }, + { + "title": "Registrera konfigurationen", + "body": "Den faktiska konfigurationen sparas i instanskontraktet som används vid uppdateringar och återskapande." + } + ] + } + } + ] + }, + { + "id": "example", + "title": "Exempel: en avbildning med /config och /downloads", + "intro": "En vanlig Compose-definition och den Proxmox VE-konfiguration som den omvandlas till. Sökvägarna som programmet förväntar sig ändras inte.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Docker Compose", + "code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080" + }, + { + "title": "/etc/pve/lxc/VMID.conf (utdrag)", + "code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000" + } + ] + } + }, + { + "p": "mp0 är en andra disk som tillhör containern och heter vm-VMID-disk-N på den valda lagringen. Den monteras på /config och ingår i containerns säkerhetskopia med backup=1. mp1 skapar ingen disk: värdens katalog /srv/downloads monteras på /downloads i LXC-containern. Port 8080 mappas inte: LXC-containern har en egen adress där tjänsten svarar." + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/custom-image.json b/web/messages/sv/docs/oci-manager/custom-image.json new file mode 100644 index 00000000..c8c04c7e --- /dev/null +++ b/web/messages/sv/docs/oci-manager/custom-image.json @@ -0,0 +1,278 @@ +{ + "meta": { + "title": "Installera en avbildning utanför katalogen | ProxMenux", + "description": "Anpassa en OCI-avbildning från en Compose-fil, en URL, ett docker run-kommando eller en avbildningsreferens och granska vad ProxMenux kan återskapa före installationen." + }, + "header": { + "title": "Installera en avbildning utanför katalogen", + "description": "Din avbildning anpassas från sin Compose-fil, ett docker run-kommando eller enbart referensen. Resultatet visas för granskning innan någon LXC-container skapas.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Samma konverterare och installationsflöde", + "body": "Alternativet Installera en avbildning utanför katalogen använder samma konverterare och installationsflöde som katalogmallarna. Skillnaden är att kontraktet skapas vid det tillfället från den angivna definitionen och valideras innan någon LXC-container skapas." + } + } + ] + }, + { + "id": "sources", + "title": "Så beskrivs avbildningen", + "intro": "Första vyn frågar hur avbildningen beskrivs. Fyra alternativ läser en definition som anpassas, ett femte använder bara avbildningens namn och tre andra läser en färdig oci-compose-fil.", + "blocks": [ + { + "table": { + "headers": [ + "Alternativ", + "Vad som läses" + ], + "rows": [ + [ + "Klistra in Compose-filen i terminalen", + "Klistra in YAML-innehållet i terminalen och avsluta med Ctrl+D." + ], + [ + "Läs Compose-filen från värden", + "En sökväg på noden, som standard /root/docker-compose.yml. Filen måste finnas och får vara högst 256 KiB." + ], + [ + "Hämta Compose-filen från en adress", + "En http://- eller https://-adress som tillhandahåller rå YAML. Högst 256 KiB hämtas." + ], + [ + "Klistra in docker run-kommandot i terminalen", + "Kommandot som projektet publicerar. Portar, miljö, volymer, enheter, capabilities, användare, delat minne och andra alternativ som stöds anpassas." + ], + [ + "Bara avbildningsreferensen, utan Compose-fil", + "En referens som ghcr.io/user/application:latest. Registret frågas och avbildningens OCI-metadata bevaras." + ], + [ + "Klistra in en oci-compose i terminalen", + "Klistra in JSON-innehållet i terminalen och avsluta med Ctrl+D. Det konverteras inte utan kontrolleras och installeras som det är skrivet." + ], + [ + "Läs en oci-compose-fil från värden", + "En JSON-fil på noden, som standard /root/oci-compose.json, på högst 2 MiB." + ], + [ + "Hämta en oci-compose från en adress", + "En http://- eller https://-adress som tillhandahåller JSON-filen. Högst 2 MiB hämtas." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-source-menu.png", + "alt": "Meny för att välja hur en avbildning utanför katalogen ska beskrivas", + "caption": "Olika sätt att beskriva en avbildning utanför katalogen." + } + } + ] + }, + { + "id": "reference-only", + "title": "Vad enbart avbildningsreferensen ger", + "intro": "Avbildningen innehåller processens metadata, men inte allt som en Compose-fil brukar lägga till runt den.", + "blocks": [ + { + "table": { + "headers": [ + "Läses från avbildningen", + "Finns inte i avbildningen", + "Konsekvens" + ], + "rows": [ + [ + "Entrypoint, Cmd, User, WorkingDir och den inbyggda miljön", + "Variabler som bara finns i dokumentationen", + "Läggs till under installationen eller via Ändra" + ], + [ + "Volymer som avbildningen deklarerar", + "Kataloger på värden som bara anges i Compose-filen", + "Ytterligare sökvägar läggs till före installationen" + ], + [ + "EXPOSE-portar", + "URL, protokoll eller funktionell hälsokontroll", + "Porten och sättet att kontrollera tjänsten bekräftas" + ], + [ + "Arkitekturer och digest i registret", + "Enheter, privilegier eller externa beroenden", + "Aktiveras inte utan en definition som begär dem" + ] + ] + } + } + ] + }, + { + "id": "review", + "title": "Granska före installationen", + "intro": "Efter att definitionen anpassats visar ProxMenux hur den har tolkats, innan VMID, resurser eller lagring efterfrågas.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Analysera", + "body": "Tjänster, avbildning, portar, sökvägar, miljö, enheter, säkerhet och hälsokontroll läses in." + }, + { + "title": "Fråga registret", + "body": "Avbildningen måste finnas i registret och de publicerade arkitekturerna listas. Ett privat register läses med kontot som sparats för det." + }, + { + "title": "Lista det som inte tillämpas", + "body": "Etiketter, Docker-nätverk, Swarm-inställningar och andra nycklar som inte påverkar en LXC-container listas." + }, + { + "title": "Blockera alternativ utan säker motsvarighet", + "body": "Nycklar utan säker motsvarighet visas under Alternativ utan motsvarighet och installationen fortsätter inte. Inget ignoreras utan besked." + }, + { + "title": "Fråga efter hemliga uppgifter", + "body": "Variabler vars namn tyder på lösenord, token eller nyckel efterfrågas som känsliga uppgifter under installationen." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/custom-review.png", + "alt": "Sammanfattning av hur ProxMenux har tolkat Compose-filen", + "caption": "Sammanfattningen som visas innan installationen frågar efter VMID och resurser." + } + }, + { + "p": "Efter att sammanfattningen godkänts följer samma flöde som i katalogen: namn, standardläge eller avancerat läge, VMID, CPU, minne, nätverk, start med noden, beständiga och extra sökvägar, kompatibla enheter och en slutlig sammanfattning." + } + ] + }, + { + "id": "examples", + "title": "Exempel på indata", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Compose", + "code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid" + }, + { + "title": "docker run", + "code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest" + } + ] + } + } + ] + }, + { + "id": "oci-compose", + "title": "En oci-compose skriven för hand", + "intro": "En oci-compose är JSON-filen som ett program installeras från. Katalogen innehåller en sådan fil per program. Samma format kan skrivas för hand och installeras med det här alternativet.", + "blocks": [ + { + "p": "I en handskriven fil bestämmer författaren avbildning, portar, variabler, volymer och installationsfrågor. ProxMenux kompletterar resten när filen läses. Formatet kan ange sådant som Compose inte kan: frågetexter, ett lösenord som skapas under installationen, en GPU-profil, ägaren till varje volym, kontrollen vid första starten eller inloggningsuppgifterna som visas till sist." + }, + { + "steps": { + "items": [ + { + "title": "Kontrollera", + "body": "En fil med fel installeras inte. Varje fel anger vilket fält det gäller; vid ogiltig JSON visas rad och kolumn." + }, + { + "title": "Granska", + "body": "En giltig fil visar vad den installerar: avbildning, webbadress, portar, beständiga sökvägar, variabler som efterfrågas och filer som skrivs." + }, + { + "title": "Installera", + "body": "Installationen är densamma som för katalogens program, med standardinställningar eller avancerade inställningar." + } + ] + } + }, + { + "calloutWarning": { + "title": "En fil som bestämmer privilegierna", + "body": "En oci-compose bestämmer vad som monteras i containern och med vilka privilegier. Granskningen visar detta före installationen: installera bara filer från betrodda källor." + } + }, + { + "p": "Formatet beskrivs block för block med två fullständiga exempel i OCI-motorns README. Även en installationspost kan läsas på samma sätt: bara den ursprungliga mallen används, inga av svaren från den installationen." + } + ] + }, + { + "id": "private-registries", + "title": "Avbildningar i privata register", + "intro": "En avbildning i ett privat register, till exempel ett privat GitHub-paket eller ett privat Docker Hub-arkiv, läses med ett konto som sparats i ProxMenux.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Registret nekar åtkomst till avbildningen", + "body": "Om registret svarar att avbildningen kräver ett konto erbjuder granskningen inloggning och läser sedan avbildningen igen." + }, + { + "title": "Logga in", + "body": "Ett användarnamn och ett lösenord eller en token som får hämta avbildningen. Registret kontrollerar uppgifterna och de sparas bara om de godkänns." + }, + { + "title": "Installera och håll uppdaterat", + "body": "Kontot används för att hämta avbildningen under installationen och även vid uppdatering, Ändra, Återskapa, watchdog och Monitors uppdateringskontroll, på alla noder i klustret." + } + ] + } + }, + { + "p": "Registerkonton i huvudmenyn för OCI manager Apps visar sparade konton per register och användare, aldrig deras hemliga uppgifter. Från varje konto kan du kontrollera, hämta eller installera en avbildning samt uppdatera eller ta bort inloggningsuppgifterna." + }, + { + "list": { + "items": [ + "GitHub (ghcr.io): användaren är GitHub-kontot och lösenordet en personlig åtkomsttoken med behörigheten read:packages.", + "Docker Hub (docker.io): användaren är Docker Hub-kontot och lösenordet en åtkomsttoken med läsbehörighet.", + "Andra register, även med egen port som registry.example.com:5000: användarnamn och lösenord eller token som registret tillhandahåller." + ] + } + }, + { + "calloutInfo": { + "title": "Var kontona sparas", + "body": "I /etc/pve/priv/proxmenux/registry-auth.json, läsbar bara av root, i samma mapp där Proxmox sparar lagringslösenord. /etc/pve delas mellan alla noder i klustret, så ett konto som läggs till på en nod används på alla. Värdens säkerhetskopia innehåller filen och avinstallation av ProxMenux tar bort den från alla noder." + } + } + ] + }, + { + "id": "limits", + "title": "Definitioner som inte installeras", + "blocks": [ + { + "list": { + "items": [ + "En Dockerfile utan publicerad avbildning: avbildningen måste först byggas och publiceras i ett OCI-register.", + "En Compose-fil med flera tjänster blockeras eftersom det här alternativet installerar en tjänst i en container, även om flera tjänster använder samma avbildning.", + "configs, externa hemligheter eller enhetsformat som inte kan anpassas entydigt.", + "Skalsubstitutioner som $(command): ange det resulterande värdet direkt i stället.", + "Okända docker run-alternativ och --env-file: använd en Compose-fil med uttryckliga värden i stället." + ] + } + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/hardware.json b/web/messages/sv/docs/oci-manager/hardware.json new file mode 100644 index 00000000..e828a5d7 --- /dev/null +++ b/web/messages/sv/docs/oci-manager/hardware.json @@ -0,0 +1,392 @@ +{ + "meta": { + "title": "Enheter och acceleration | ProxMenux", + "description": "Hur OCI manager Apps ger en OCI-container tillgång till GPU, NVIDIA, Coral, USB, FUSE och andra enheter som validerade inbyggda Proxmox VE-resurser." + }, + "header": { + "title": "Enheter och acceleration", + "description": "GPU, NVIDIA, Coral, USB, FUSE och blockenheter blir validerade inbyggda Proxmox VE-resurser för containern.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "principle", + "title": "Enheten som programmet behöver, inte hela värden", + "blocks": [ + { + "p": "En enhet som begärs av Compose-filen eller av applikationsprofilen blir en konkret devN-post eller LXC-montering. Be om en GPU, en USB-enhet eller en Coral gör inte containern privilegierad." + }, + { + "flow": { + "nodes": [ + { + "label": "Värdinventering", + "detail": "/dev/dri/renderD128\nGID 993 · Intel" + }, + { + "label": "ProxMenux", + "detail": "tillverkare och\nbehörigheter kontrollerade" + }, + { + "label": "LXC", + "detail": "samma enhet\neffektiv GID" + } + ] + } + } + ] + }, + { + "id": "origin", + "title": "Varifrån enhetsbegäran kommer", + "blocks": [ + { + "table": { + "headers": [ + "Källa", + "Vad läses", + "Vad installationsprogrammet gör" + ], + "rows": [ + [ + "Docker Compose", + "devices, group_add, deploy.resources och NVIDIA förfrågningar", + "Varje krav blir en begäran om enhet som visas för granskning" + ], + [ + "Katalogprofil", + "Programmets faktiska stöd för GPU, Coral, OpenCL, USB eller FUSE", + "Endast de alternativ som valideras för den avbildningen erbjuds" + ], + [ + "Avbildningens metadata och dokumentation", + "VA-API, Selkies, LinuxServer mods eller NVIDIA runtime", + "Dokumenterade variabler och förberedelser läggs till" + ], + [ + "Användarval", + "CPU endast, Intel/AMD, OpenCL, NVIDIA eller en valfri enhet", + "Urvalet lagras i instansavtalet" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Upptäckta enheter ansluts inte automatiskt", + "body": "Värden är inventerad, men endast enheter som deklareras av Compose-filen eller av en kompatibel profil erbjuds och bifogas. En GPU, USB-dongel eller Coral närvarande på värden är inte utsatt för varje LXC." + } + } + ] + }, + { + "id": "identify", + "title": "Hur värdenheten identifieras", + "intro": "Innan LXC modifieras läses enheten på värden och matchas mot den valda profilen.", + "blocks": [ + { + "table": { + "headers": [ + "Typ", + "Identitet", + "Validering" + ], + "rows": [ + [ + "Intel/AMD DRM", + "/dev/dri/renderD* och /sys/class/drm/NODE/device/vendor", + "En teckenenhet med leverantör 0x8086 (Intel) eller 0x1002 (AMD)" + ], + [ + "AMD OpenCL", + "Rendernoden plus /dev/kfd när profilen behöver den", + "Existens, typ, leverantör, behörigheter och förklarad kompatibilitet" + ], + [ + "AMD ROCm", + "Rendernoden, /dev/kfd och generationen som beräkningsdrivrutinen rapporterar i /sys/class/kfd/kfd/topology/nodes/*/properties", + "En generation som programmets ROCm-avbildning innehåller beräkningskärnor för — de namngivna RDNA2-GPU:erna och nyare — erbjuds utan anpassning. Andra GPU:er i dessa familjer, till exempel Radeon 680M och 780M, erbjuds som experimentella och väljs aldrig som föreslaget alternativ: ROCm stöder dem inte officiellt, programmet presenterar dem som familjens generation (HSA_OVERRIDE_GFX_VERSION) och installeraren begär bekräftelse innan den större avbildningen hämtas. Äldre GPU:er, till exempel den integrerade grafiken i Ryzen 5000U, erbjuds inte och Immich fortsätter köra igenkänningen på CPU:n." + ], + [ + "NVIDIA", + "nvidia-smi och nvidia-container-cli", + "GPU, UUID, PCI buss, drivrutinsversion, Toolkit, /dev/nvidia* noder, binärer och bibliotek" + ], + [ + "Coral PCIe/M.2", + "/dev/apex_N och dess länk i /sys/dev/char/MAJOR:MINOR", + "Teckenenhetsnod, major/minor-nummer, ägare, GID och behörigheter" + ], + [ + "USB och seriell", + "/dev/ttyUSB*, /dev/ttyACM* eller /dev/bus/usb/BBB/DDD", + "Teckenenhetsnod; för USB även tillverkare, produkt och serienummer när sysfs publicerar dem" + ], + [ + "KVM, TUN, FUSE, video och generisk SCSI", + "/dev/kvm, /dev/net/tun, /dev/fuse, /dev/videoN eller /dev/sgN", + "Sökväg som stöds, nodtyp och effektiva behörigheter" + ], + [ + "Optisk enhet", + "/dev/srN", + "En blockenhet" + ] + ] + } + } + ] + }, + { + "id": "install", + "title": "Vad händer under installationen", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Mallen erbjuder sina profiler", + "body": "Till exempel CPU endast, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL eller NVIDIA. Alternativen hör till avbildningen, inte till en vanlig meny." + }, + { + "title": "En profil väljs", + "body": "Den anger enhetsnoder, miljö, moddar eller den körmiljö som programmet behöver." + }, + { + "title": "En väg föreslås", + "body": "För DRM, /dev/dri/renderD128, som kan ändras på en värd med flera rendernoder. För USB eller seriell, är den specifika noden vald." + }, + { + "title": "Validering", + "body": "Existens, typ, tillåten leverantör, behörigheter och GID kontrolleras. En missmatchning stoppar operationen." + }, + { + "title": "Kontraktet är skrivet", + "body": "Sökväg, läge, GID, skrivåtkomst och profil sparas för uppdateringar och när containern skapas om." + }, + { + "title": "Anslut och testa", + "body": "pct set lägger till devN inträde och åtkomst kontrolleras sedan inuti LXC. LinuxServer avbildningar kontrolleras också som användare abc." + } + ] + } + } + ] + }, + { + "id": "config", + "title": "Hur det visas i LXC-konfigurationen", + "intro": "Exempelvärden: dev0 och dev1 är lediga platser som Proxmox VE tilldelar, och renderD128, apex_0 samt GID beror på nodens maskinvara.", + "blocks": [ + { + "codeGrid": { + "items": [ + { + "title": "Intel/AMD VA-API", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" + }, + { + "title": "Coral PCIe/M.2", + "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "En specifik USB-enhet", + "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" + }, + { + "title": "AMD OpenCL", + "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" + } + ] + } + }, + { + "p": "GID läses med stat på värden och skrivs till devN-posten; render och video-grupper antas inte ha ett fast nummer. Enheten håller samma /dev-väg inuti LXC, där programmets egna mekanismer letar efter det." + } + ] + }, + { + "id": "profiles", + "title": "Profiler en avbildning kan erbjuda", + "blocks": [ + { + "table": { + "headers": [ + "Profil", + "Resulterande konfiguration", + "Erbjuds när" + ], + "rows": [ + [ + "Intel/AMD VA-API", + "Rendernod i /dev/dri", + "Applikationen stöder videoacceleration" + ], + [ + "OpenCL", + "Rendernod, /dev/kfd vid behov och den officiella modden", + "Avbildningen eller profilen dokumenterar den" + ], + [ + "NVIDIA", + "Värdens drivrutinsenheter och bibliotek", + "Värden har en fungerande drivrutin och NVIDIA Container Toolkit" + ], + [ + "Coral", + "/dev/apex_0 eller USB buss", + "Profilen förklarar Coral Support (Frigate)" + ], + [ + "USB, seriell, FUSE", + "En enhet, ett validerat träd eller en LXC-funktion", + "Kontraktet begär det" + ] + ] + } + } + ] + }, + { + "id": "nvidia", + "title": "NVIDIA", + "blocks": [ + { + "calloutWarning": { + "title": "Nodkrav: NVIDIA Container Toolkit", + "body": "En fungerande drivrutin på Proxmox VE är inte tillräckligt för att ge en NVIDIA GPU till en OCI-avbildning. OCI Manager Apps använder nvidia-container-cli, från NVIDIA Container Toolkit, för att identifiera enheterna och för att få binärer och bibliotek som matchar den laddade drivrutinen." + } + }, + { + "p": "ProxMenux NVIDIA-installerare installerar NVIDIA Container Toolkit från NVIDIAs officiella arkiv tillsammans med drivrutinen. Den kontrollerar dess fyra paket, validerar nvidia-container-cli och registrerar resultatet i ändringsloggen för Audit & Report." + }, + { + "p": "Dessa två kommandon på värden visar om drivrutinen och Toolkit är tillgängliga:" + }, + { + "shell": { + "code": "nvidia-smi -L\nnvidia-container-cli --version" + } + }, + { + "p": "På en värd där drivrutinen installerades på annat sätt installeras Toolkit från det officiella stabila förvaret:" + }, + { + "shell": { + "code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools" + } + }, + { + "p": "Inventeringen OCI Manager Apps använder är utdata av:" + }, + { + "shell": { + "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" + } + }, + { + "calloutInfo": { + "title": "Docker runtime konfiguration är inte involverad", + "body": "Containrarna är inbyggda LXC:er utan Docker-demon, så nvidia-ctk runtime configure --runtime=docker ingår inte: ProxMenux frågar nvidia-container-cli direkt och skriver LXC-enheter och monteringar. Kommandon och plattformar som stöds underhålls i installationsguiden för NVIDIA Container Toolkit." + } + }, + { + "cards": { + "items": [ + { + "icon": "cpu", + "title": "Inventering från drivrutinen", + "body": "nvidia-container-cli listar enhetsnoder, binärer, firmware och bibliotek av den installerade drivrutinen." + }, + { + "icon": "refresh", + "title": "Ingen fast version", + "body": "Mallen anger inga biblioteksfilnamn. Profilen genereras från den aktuella värden." + }, + { + "icon": "shield", + "title": "Skrivskyddade monteringar", + "body": "Värdens bibliotek monteras skrivskyddat i stället för att kopieras till containern." + }, + { + "icon": "hardDrive", + "title": "Ändringar av drivrutin", + "body": "Efter en drivrutinsändring genereras inventeringen på nytt innan berörda LXC:er startar." + } + ] + } + }, + { + "code": { + "title": "NVIDIA resultat (förenklad)", + "code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0" + } + }, + { + "p": "Att bara ge tillgång till /dev/nvidia0 räcker inte. Den laddade drivrutinens användarkomponenter monteras skrivskyddat, och nvidia-smi körs sedan i LXC:n för att jämföra GPU, UUID, PCI-buss och version med värdens inventering." + } + ] + }, + { + "id": "usb", + "title": "USB, seriell och USB Coral", + "blocks": [ + { + "calloutWarning": { + "title": "USB-nummer kan ändra", + "body": "En väg som /dev/bus/usb/003/004 kan ändras när enheten är återansluten eller värden startar om. Profilen registrerar tillverkaren, produkten och serienumret när de är tillgängliga, men en ny bussadress mappas om inte automatiskt." + } + }, + { + "p": "En kringutrustning anges med sin specifika nod: /dev/ttyUSB0, /dev/ttyACM0, /dev/apex_0 eller /dev/bus/usb/BBB/DDD. Att passera hela /dev accepteras inte. Coral erbjuds endast program vars profil anger det." + } + ] + }, + { + "id": "trees", + "title": "Enhetsträd och LXC-funktioner", + "blocks": [ + { + "table": { + "headers": [ + "Begäran", + "Resulterande konfiguration", + "Omfattning" + ], + "rows": [ + [ + "/dev/dvb, /dev/snd eller /dev/bus/usb", + "Varje teckenenhetsnod i trädet får en egen devN-post med värdens läge och GID", + "Endast det begärda trädet, inte resten av /dev" + ], + [ + "/dev/fuse", + "Noden och när profilen behöver den, funktionen fuse=1", + "FUSE offentliggör inte fästen till andra LXCs" + ], + [ + "/dev/net/tun", + "En devN-post med samma sökväg inuti LXC:n", + "VPN- eller nätverkskonfigurationen stannar i programmet" + ], + [ + "/dev/kvm", + "En validerad devN-post", + "Erbjuds endast när kontraktet begär det" + ] + ] + } + } + ] + }, + { + "id": "security", + "title": "Bekräftelser av risknivå", + "blocks": [ + { + "p": "Konkreta enheter, valfri privilegiering, krävs privilegier, AppArmor eller seccomp avslappning och tillgång till värd PID-namnrymden behandlas som separata fall, inte under en generisk privilegierad etikett. Varje alternativ med en risk förklaras och bekräftas under installationen." + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/index.json b/web/messages/sv/docs/oci-manager/index.json new file mode 100644 index 00000000..390aa4d6 --- /dev/null +++ b/web/messages/sv/docs/oci-manager/index.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI manager Apps | ProxMenux", + "description": "Kör officiella OCI-avbildningar som LXC-containrar direkt i Proxmox VE, med beständiga data, enhetsåtkomst, program med flera containrar och transaktionsbaserade uppdateringar." + }, + "header": { + "title": "OCI manager Apps", + "description": "Officiella OCI-avbildningar körs som inbyggda LXC-containrar i Proxmox VE. Avbildningen är fortfarande den som underhållaren publicerar; ProxMenux återskapar miljön som Docker Compose annars skulle ha skapat runt den.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "model", + "blocks": [ + { + "calloutInfo": { + "title": "En OCI-avbildning, en LXC direkt i Proxmox VE", + "body": "Ingen Docker Engine körs i containrarna. Proxmox VE importerar avbildningens filsystem och OCI-metadata. Programmet blir huvudprocessen i en inbyggd LXC-container som hanteras med samma verktyg som övriga containrar på noden." + } + }, + { + "flow": { + "nodes": [ + { + "label": "Källa", + "detail": "OCI-avbildning\nCompose och dokumentation" + }, + { + "label": "ProxMenux", + "detail": "JSON-kontrakt\nvalidering och planering" + }, + { + "label": "Proxmox VE", + "detail": "LXC direkt i Proxmox VE\nvolymer och enheter" + } + ], + "caption": "Programmet byggs inte om. Dess miljö beskrivs deklarativt och återskapas." + } + } + ] + }, + { + "id": "where", + "title": "Var du hittar det", + "intro": "Öppna OCI manager Apps (Technology Preview) i ProxMenux huvudmeny på Proxmox-noden som root. Den första vyn erbjuder:", + "blocks": [ + { + "list": { + "items": [ + "Sök program – sök efter namn i hela katalogen.", + "Alla program – visa hela katalogen och antalet program.", + "Hantera installerade OCI-program – uppdatera, ändra, ta bort eller återställ befintliga installationer.", + "Registerkonton – hantera konton för att installera och uppdatera avbildningar från privata register.", + "Installera en avbildning utanför katalogen – anpassa en Compose-fil, ett docker run-kommando eller en avbildningsreferens.", + "Katalogens kategorier, var och en med sitt antal program." + ] + } + }, + { + "figure": { + "src": "/oci-manager/main-menu.png", + "alt": "Huvudvyn i OCI manager Apps med sökning, hela katalogen, programhantering, avbildningar utanför katalogen och kategorier.", + "caption": "Huvudvyn i OCI manager Apps." + } + }, + { + "p": "När du väljer ett program visas dess beskrivning, avbildningen som används och två installationslägen. Installera med standardinställningar frågar bara efter grundvalen: lagring, dataplacering, adress och start. Installera med avancerade inställningar går igenom alla val i ordning: VMID och resurser, lagring för varje sökväg, nätverksbrygga och enheter." + }, + { + "p": "Program som faktiskt har testats visas som Verifierad i den här vyn och markeras med ✓ i listorna, oavsett om testet gjordes i ProxMenux labb eller av användargemenskapen." + } + ] + }, + { + "id": "beta", + "title": "Varför beteckningen Technology Preview används", + "blocks": [ + { + "p": "OCI manager Apps behåller beteckningen Technology Preview som Proxmox VE använder för programcontainrar skapade från OCI-avbildningar. Stödet infördes i Proxmox VE 9.1. Beteckningen gäller tekniken som OCI manager Apps bygger på, inte ProxMenux-motorn: containrarnas beteende kan fortfarande ändras mellan Proxmox VE-versioner. Beteckningen behålls tills Proxmox VE bedömer att stödet är stabilt." + }, + { + "p": "Funktionerna i OCI manager Apps testas i ProxMenux labb: installation, uppdatering, ändringar, återskapande, säkerhetskopiering, återställning, migrering och watchdog. Varje program testas separat. De som faktiskt har testats visas som Verifierad och markeras med ✓ i listorna." + } + ] + }, + { + "id": "translation", + "title": "Så beskriver Docker kraven och så omsätter OCI manager Apps dem", + "blocks": [ + { + "table": { + "headers": [ + "Krav", + "Så uttrycks det i Docker", + "Så omsätts det i OCI manager Apps" + ], + "rows": [ + [ + "Kör programmet", + "image, entrypoint, command", + "OCI-avbildningens rootfs och LXC-containerns huvudprocess" + ], + [ + "Bevara konfigurationen", + "volumes: /config", + "En beständig mpN-volym som tas med i säkerhetskopian, eller en katalog på värden." + ], + [ + "Gör tjänsten tillgänglig", + "ports", + "En egen adress för LXC-containern och tjänstens åtkomst-URL." + ], + [ + "Använd maskinvara", + "devices, group_add", + "devN-poster med värdens effektiva grupp-ID (GID) och en validerad profil." + ], + [ + "Koppla ihop beroenden", + "networks, depends_on", + "En privat nätverksbrygga, fasta adresser, startordning och hälsokontroller." + ], + [ + "Uppdatera", + "Hämta med pull och återskapa", + "Ett nytt rootfs med samma kontrakt för beständiga data." + ] + ] + } + } + ] + }, + { + "id": "kept", + "title": "Vad som bevaras och vad som ändras", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "archive", + "title": "Bevaras", + "body": "Den officiella avbildningen, dess interna sökvägar, miljö, startprocess och användardokumentation." + }, + { + "icon": "boxes", + "title": "Anpassas", + "body": "Körmiljön: nätverk, beständiga data, enheter, behörigheter och beroenden använder de inbyggda LXC-funktionerna i Proxmox VE." + }, + { + "icon": "shield", + "title": "Utan motsvarighet", + "body": "Ett Compose-alternativ utan en säker motsvarighet markeras som ett hinder för installationen. Mallar med sådana hinder erbjuds inte för installation, och känsliga alternativ måste bekräftas i installationsguiden." + }, + { + "icon": "refresh", + "title": "Sparas", + "body": "Avbildningens digest, resurser, sökvägar, nätverk, enheter och tillhörighet till en stack sparas i instanskontraktet. Uppdateringar och återskapande följer samma kontrakt." + } + ] + } + } + ] + }, + { + "id": "pages", + "title": "Sidor i det här avsnittet", + "blocks": [ + { + "next": { + "items": [ + { + "label": "Så anpassas en OCI-avbildning", + "href": "/docs/oci-manager/architecture", + "tail": "Från avbildningen och dess Compose-fil till en LXC direkt i Proxmox VE." + }, + { + "label": "Installera en avbildning utanför katalogen", + "href": "/docs/oci-manager/custom-image", + "tail": "En Compose-fil, ett docker run-kommando eller en avbildningsreferens." + }, + { + "label": "Data, sökvägar och nätverk", + "href": "/docs/oci-manager/storage-network", + "tail": "Containervolymer, kataloger på värden, rclone-monteringar och adresser." + }, + { + "label": "Enheter och acceleration", + "href": "/docs/oci-manager/hardware", + "tail": "GPU, NVIDIA, Coral, USB och andra enheter." + }, + { + "label": "Program med flera containrar", + "href": "/docs/oci-manager/stacks", + "tail": "Program, databas och cache i samordnade LXC-containrar." + }, + { + "label": "Installera, uppdatera och ändra", + "href": "/docs/oci-manager/lifecycle", + "tail": "Instanskontraktet och dess åtgärder." + }, + { + "label": "I ProxMenux Monitor", + "href": "/docs/oci-manager/monitor", + "tail": "Versioner, uppdateringar, konsolutdata och terminal." + } + ] + } + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/lifecycle.json b/web/messages/sv/docs/oci-manager/lifecycle.json new file mode 100644 index 00000000..b4a5ad6e --- /dev/null +++ b/web/messages/sv/docs/oci-manager/lifecycle.json @@ -0,0 +1,499 @@ +{ + "meta": { + "title": "Installera, uppdatera och ändra | ProxMenux", + "description": "Instanskontraktet för en OCI-container och de åtgärder som använder det: uppdatering, ändring, borttagning, återupptagning av en avbruten åtgärd och återställning av driften efter en återläsning på denna eller en annan värd." + }, + "header": { + "title": "Installera, uppdatera och modifiera", + "description": "Varje instans behåller ett reproducerbart kontrakt, så att dess rootfs kan ersättas utan att konfiguration eller beständiga data går förlorade.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "contract", + "title": "Instanskontraktet", + "blocks": [ + { + "p": "Efter installationen sparas den faktiska konfigurationen i /usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json. Det är inte en kopia av den ursprungliga Compose-filen, utan ett reproducerbart kontrakt för den LXC som finns på värden." + }, + { + "code": { + "code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle" + } + }, + { + "p": "En kopia av kontraktet finns i containern under /.proxmenux/oci-record.json och kan bara läsas av containerns root-användare. En säkerhetskopia av containern innehåller därför alltid det kontrakt som gällde då. En andra kopia sparas i /etc/pve/priv/proxmenux/oci, som delas av alla noder i klustret och bara kan läsas av värdens root-användare. Båda kopiorna skrivs efter varje installation, uppdatering och ändring." + } + ] + }, + { + "id": "manage", + "title": "Hantera installerade OCI-program", + "intro": "Det här alternativet på huvudskärmen listar registrerade instanser med tillhörande program och avbildning. Varje instans kontrolleras mot sitt kontrakt före varje åtgärd. En CT som inte längre stämmer med sin post varken ändras eller tas bort. Samma uppdatering och editor finns på fliken Uppdateringar i ProxMenux Monitor.", + "blocks": [ + { + "table": { + "headers": [ + "Alternativ", + "Vad ändras", + "Vad bevaras" + ], + "rows": [ + [ + "Uppdatera avbildningen med sparad konfiguration", + "Rootfs ersätts med den avbildning som den sparade kanalen publicerar för närvarande", + "Kontrakt, containerdiskar, värdkataloger, nätverk och enheter" + ], + [ + "Ändra: redigera resurser, nätverk, sökvägar och GPU", + "Editorn öppnas med det aktuella kontraktet; CT:n byggs om med ändringarna", + "Data på containerdiskar och i värdkataloger" + ], + [ + "Watchdog: starta om programmet när det kraschar", + "Om programmet ska startas igen när det stannar av sig självt. Ingenting byggs om", + "Hela installationen; endast valet sparas i kontraktet" + ], + [ + "Ta bort: ta bort programmet och dess containrar", + "LXC, eller varje medlem i en stack, och deras kontrakt raderas", + "Värdkataloger, med deras innehåll" + ] + ] + } + }, + { + "p": "För ett program med flera containrar erbjuder menyn Uppdatera alla programmets containrar, Ändra extra sökvägar och enheter, Återskapa alla containrar med sparad konfiguration, watchdog och borttagning. Ändra lägger till eller tar bort extra sökvägar och enheter i programcontainern utan att bygga om något och ändrar i Immich vad som kör igenkänningen. Återskapa bygger om varje container från den avbildning som användes vid installationen utan att söka efter en nyare." + }, + { + "figure": { + "src": "/oci-manager/manage-menu.png", + "alt": "Lista över installerade OCI-program med alternativ för att uppdatera, ändra och ta bort", + "caption": "Hantera installerade OCI-program." + } + } + ] + }, + { + "id": "update", + "title": "En transaktionell uppdatering", + "blocks": [ + { + "mermaid": { + "chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end", + "labels": { + "user": "Användare", + "registry": "OCI-registret", + "update": "Uppdatera instans", + "resolve": "Slå upp taggen", + "verify": "Verifiera arkivet och kör förkontroller", + "backup": "Stoppa och säkerhetskopiera CT:n", + "import": "Importera det nya rootfs", + "reapply": "Applicera kontraktet igen", + "healthy": "frisk", + "failure": "misslyckande", + "commit": "Kontraktet publiceras med den nya digesten", + "restored": "Tidigare instans återställd" + } + } + }, + { + "list": { + "items": [ + "Om registret fortfarande tillhandahåller den installerade digesten laddas ingenting ned och instansen lämnas orörd.", + "Den nya avbildningen verifieras lager för lager innan CT:n stoppas. En skadad nedladdning hämtas en andra gång; en redan cachad avbildning som inte klarar kontrollen laddas ned igen.", + "Inställningar som ändrats i Proxmox VE efter installationen (minne, swap, kärnor, CPU-gräns, CPU-prioritet och start med noden) bevaras och förs över till det nya kontraktet.", + "LAN-gränssnittet som Arr-sviten och multi-container-applikationerna lägger till (net1) och startordern (startup) ges tillbaka till den nya containern som de är.", + "Alla andra skillnader mellan CT och dess kontrakt stoppar uppdateringen innan CT stoppas." + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "Före", + "detail": "rootfs A\n/config mp0\n/media värdkatalog" + }, + { + "label": "Uppdatering", + "detail": "ersätter endast\nrootfs" + }, + { + "label": "Efter", + "detail": "rootfs B\n/config mp0\n/media värdkatalog" + } + ] + } + }, + { + "p": "Innan något stoppas kontrolleras att det finns plats för säkerhetskopian: använda data på de volymer som ingår, plus en marginal. Den skrivs intill programmets post på värdens systemdisk. Om utrymmet inte räcker ber menyn om en annan lagring på värden som stöder säkerhetskopior. Kopian skapas där och tas bort när åtgärden är klar. Valet sparas för programmet, även för schemalagda uppdateringar. Om ingen lagring har tillräckligt med plats avvisas åtgärden och programmet förblir oförändrat. Utrymmet för den nya avbildningen kontrolleras på samma sätt före nedladdningen." + } + ] + }, + { + "id": "recovery", + "title": "Återställa en avbruten operation", + "intro": "En uppdatering och en förändring använder en ihållande transaktion. Om processen, terminalen eller noden avbryts efter CT-stoppen, är operationen inte färdig.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Markör för väntande åtgärd", + "body": "När Hantera installerade OCI-program öppnas visar det sparade tillståndet att ersättningen aldrig publicerades." + }, + { + "title": "Visa status", + "body": "Visar den fas som uppnåddes utan att ändra containrar eller data." + }, + { + "title": "Återställ den tidigare installationen", + "body": "Återläser den verifierade säkerhetskopia i plattformens eget format som togs före ersättningen samt det tidigare kontraktet." + }, + { + "title": "Stackar som en enhet", + "body": "För en multicontainer-applikation återhämtas varje medlem från samma transaktionspunkt, inte bara den valda." + } + ] + } + }, + { + "calloutWarning": { + "title": "Värdkataloger ingår inte i återställningen", + "body": "Säkerhetskopian omfattar rootfs och de containerdiskar som ingår i den. En värdkatalog återställs inte till sitt tidigare tillstånd, eftersom andra LXC-containrar kan använda dess data." + } + } + ] + }, + { + "id": "watchdog", + "title": "Watchdog: starta om ett program som stannar", + "intro": "Proxmox har ingen omstartspolicy: när processen i en programcontainer avslutas förblir containern stoppad. Med watchdog aktiverad startas ett program som stannar av sig självt igen. För ett OCI-program gör den vad restart: unless-stopped gör i en Compose-fil.", + "blocks": [ + { + "table": { + "headers": [ + "Vad händer", + "Med Watchdog på" + ], + "rows": [ + [ + "Programmet kraschar eller dess process avslutas", + "Containern startas igen inom några sekunder" + ], + [ + "Det stoppas eller stängs av från Proxmox, från Monitor eller med pct", + "Den förblir stoppad tills någon startar den" + ], + [ + "En backup, en migration eller en ProxMenux-operation arbetar med den", + "Ingenting görs förrän det slutar" + ], + [ + "Den var redan stoppad när värden startade", + "Den förblir stoppad; detta styrs av Starta med Proxmox" + ], + [ + "Den stannar upprepade gånger direkt efter start", + "För ett program som stannar inom några sekunder efter start, eller inte startar alls, görs fem startförsök med 30 sekunders, 1, 2 och 5 minuters väntan mellan försöken. Därefter lämnas det stoppat och en avisering skickas" + ] + ] + } + }, + { + "p": "Installationsprogrammet frågar om detta i båda installationslägena och föreslår värdet från programmets recept. Inställningen kan senare ändras under Hantera installerade OCI-program eller med containerns Watchdog-reglage i ProxMenux Monitor och gäller alla containrar i ett program med flera containrar. En enda tjänst på värden, proxmenux-oci-watchdog.service, övervakar alla program som begärt det. Installationen visas på fliken Ändringar i Audit & Report." + }, + { + "calloutInfo": { + "title": "Vad värden inte kan avgöra", + "body": "Programmets slutkod når inte värden, så ett normalt avslut och en krasch ser likadana ut och leder båda till omstart, som always och unless-stopped i Docker. Ett stopp som begärs utanför Proxmox med lxc-stop eller systemctl lämnar ingen stoppuppgift och tolkas som att programmet stannade av sig självt." + } + } + ] + }, + { + "id": "restore", + "title": "Backup, restaurering och en annan värd", + "intro": "En säkerhetskopia som görs med vzdump eller Proxmox Backup Server innehåller containern, de containerdiskar som valts för säkerhetskopiering och kopian av dess kontrakt. Det som installationen sparar på värden ingår inte. Hantera installerade OCI-program upptäcker containrar som återlästs på en värd utan deras kontrakt, efter en ny Proxmox-installation eller på en annan värd, och erbjuder att registrera dem igen.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Återställ containrarna i Proxmox", + "body": "Från säkerhetskopieringslagringen, med det ursprungliga ID:t eller ett annat ledigt ID. Ett program med flera containrar behöver samtliga sina containrar." + }, + { + "title": "Öppna Hantera installerade OCI-program", + "body": "De återlästa containrarna listas och återställning erbjuds. Knappen Återställ på fliken Uppdateringar i ProxMenux Monitor öppnar samma återställning." + }, + { + "title": "Kontrollera innan du ändrar", + "body": "Återställningen kontrollerar att värden har allt som varje program behöver. Ett program som inte kan återställas i sin helhet lämnas oförändrat och orsaken anges." + }, + { + "title": "Registrera och starta", + "body": "Kontraktet registreras på den här värden och det som installationen sparade där skrivs på nytt. Om programmen ska startas är en separat fråga. Svara Nej om de ursprungliga containrarna fortfarande körs på en annan värd." + } + ] + } + }, + { + "figure": { + "src": "/oci-manager/restore-offer.png", + "alt": "Dialog som listar de restaurerade containrarna och erbjuder att återställa dem", + "caption": "Återställningen som erbjuds när Hantera installerade OCI-program öppnas." + } + }, + { + "table": { + "headers": [ + "Vad värden sparade", + "Efter återhämtningen" + ], + "rows": [ + [ + "Instanskontrakt", + "Registreras från kopian i containern, som kontrolleras mot den konfiguration som Proxmox återläste" + ], + [ + "Privat nätverk av en multi-container-applikation", + "Skapad igen med samma bro och undernät; de fasta adresserna i containrarna ändras inte" + ], + [ + "Startorder för en multicontainer-applikation", + "Beroende hookscript och dess kontrakt installeras igen; snippets är aktiverade på local lagring när ingen lagring accepterar dem" + ], + [ + "Nätverks-sysctl och fil för värdövervakning", + "Skriven igen i /etc/pve/proxmenux" + ], + [ + "NVIDIA-körmiljö", + "Hooken för en oprivilegierad container installeras på nytt. En privilegierad container får den här värdens drivrutinsfiler i stället för filerna från ursprungsvärden" + ], + [ + "Rclone montering", + "Hookscriptet och de program som gör monteringen tillgänglig skrivs på nytt, med samma vyer på värden" + ], + [ + "Brandväggsregel på värden för en värdmonitor", + "Efterfrågas igen för dess webbport och bryggans undernät på den här värden" + ], + [ + "lost+found av varje återställd disk", + "Tas bort om den är tom; en återläsning skapar den och vissa program kan inte starta om den finns bland deras data" + ], + [ + "Diskar som återlästs till annan lagring", + "Kontraktet uppdateras till lagringen de är på nu" + ] + ] + } + }, + { + "p": "En container som återlästs med ett annat ID behåller sitt program. Kontraktet, konsolloggen, nätverksparametrarna för sysctl, hookscriptet för en Rclone-montering och startordningen för ett program med flera containrar registreras med de ID:n som containrarna har på den här värden." + }, + { + "table": { + "headers": [ + "Vad hindrar en återställning", + "Vad du gör" + ], + "rows": [ + [ + "En container av en multicontainer-applikation saknas", + "Återläs den också; programmet återställs i sin helhet" + ], + [ + "Det privata undernätet används redan på den här värden", + "Återhämtningen avbryts och ingenting ändras: adresserna till containrarna är fasta och flyttas inte till ett annat undernät." + ], + [ + "En värdkatalog finns inte", + "Montera eller skapa den med dess data; en säkerhetskopia av containern inkluderar inte värdkataloger" + ], + [ + "En enhet finns inte på denna värd", + "Anslut den eller ta bort den från containern i Proxmox" + ], + [ + "Ett program med NVIDIA på en värd utan drivrutinen", + "Installera NVIDIA-drivrutinen och Container Toolkit" + ], + [ + "Backupen gjordes innan kopian av kontraktet fanns", + "Containern stannar som en vanlig LXC och erbjuds inte igen" + ] + ] + } + }, + { + "calloutInfo": { + "title": "En container som kommer tillbaka", + "body": "En container som återlästs över sig själv från en äldre säkerhetskopia, återställts till en ögonblicksbild eller återvänt från en annan nod bär kontraktet för sitt nuvarande tillstånd. Om den väljs för en åtgärd och dess kontrakt skiljer sig från värdens startar åtgärden inte, och containern erbjuds för återställning." + } + }, + { + "figure": { + "src": "/oci-manager/restore-result.png", + "alt": "Resultatet av återhämtningen med det privata nätverket, startordern och de registrerade containrarna", + "caption": "Resultatet av en återhämtning steg för steg." + } + } + ] + }, + { + "id": "remove", + "title": "Ta bort en OCI-applikation", + "blocks": [ + { + "p": "Före bekräftelsen skapas en sammanfattning utifrån den faktiska konfigurationen: containrarna som tas bort, data som raderas med dem, det privata nätverket som frigörs och värdkatalogerna som bevaras. Bekräftelsen är förvald till Nej, eftersom data på raderade diskar inte kan återställas efteråt." + }, + { + "table": { + "headers": [ + "Resurs", + "Vid borttagning", + "Orsak" + ], + "rows": [ + [ + "rootfs och containerdiskar", + "Raderas", + "De tillhör endast containern" + ], + [ + "Värdkatalog", + "Bevaras med sitt innehåll", + "Andra applikationer kan använda den" + ], + [ + "Containerns filer på värden", + "Raderas", + "Dess konsollogg, nätverkssysctls, Rclone montering hookscript och dess registrering i App-fliken ProxMenux Monitor tjänar ingen annan container" + ], + [ + "Filer som delas av flera installationer", + "Tas bort när den sista installationen som använder dem tas bort", + "Värdmonitorfilen och hookscriptet för beroenden i program med flera containrar" + ], + [ + "Instanskontrakt", + "Tas bort efter en framgångsrik borttagning", + "Ingen CT är associerad med den mer" + ], + [ + "Privat brygga för en stack", + "Frigörs med stacken", + "Det har inga medlemmar kvar att ansluta" + ], + [ + "Nätverk som delas av Arr-paketet", + "Frigörs när det sista programmet i sviten tas bort", + "Varje program i sviten är oberoende och tas bort separat" + ], + [ + "En enda medlem av en stack", + "Tas inte bort separat", + "Hela applikationen tas bort, så ingen stack lämnas ofullständig" + ], + [ + "En container på en annan nod av klustret", + "Inte borttagen", + "Dess rekord och värdfiler är på noden där den installerades: den tas bort där, efter att ha migrerat den tillbaka." + ] + ] + } + }, + { + "figure": { + "src": "/oci-manager/remove-summary.png", + "alt": "Sammanfattning av borttagningen med berörda containrar, data och värdkataloger", + "caption": "Sammanfattningen av borttagningen, skapad utifrån den faktiska konfigurationen." + } + } + ] + }, + { + "id": "archives", + "title": "Nedladdade avbildningar och utrymme på värden", + "blocks": [ + { + "p": "OCI-arkivet används för att bygga rootfs; den körande CT:n läser det inte. Efter en installation eller uppdatering listas de arkiv som laddades ned under åtgärden med sina storlekar, och borttagning erbjuds. Att ta bort dem frigör utrymme utan att påverka containern eller dess data." + }, + { + "calloutInfo": { + "title": "Uppdateringar behöver inte arkivet", + "body": "Utan arkivet slår en uppdatering upp den sparade kanalen och laddar ned den nya digesten. Om arkivet finns återanvänds det bara när dess referens och integritet motsvarar det som begärs." + } + } + ] + }, + { + "id": "registry", + "title": "Register och rensning", + "blocks": [ + { + "p": "När Hantera installerade OCI-program öppnas tas först rester bort efter containrar som inte längre finns på någon nod i klustret: posten som sparats som historik efter borttagning via Proxmox-gränssnittet och värdfiler som lämnats kvar efter en borttagning. En post med en halvfärdig åtgärd behålls eftersom säkerhetskopian fortfarande kan behövas. Volymer och värdkataloger raderas aldrig utifrån antaganden." + } + ] + }, + { + "id": "cluster", + "title": "I ett kluster: migration och hög tillgänglighet", + "intro": "En OCI-container är en vanlig Proxmox-LXC: den kan migreras eller hanteras av HA som andra containrar, med samma begränsningar. Det som behövs för att starta den sparas där alla noder i klustret kan hitta det.", + "blocks": [ + { + "table": { + "headers": [ + "Delar", + "På en annan nod av klustret" + ], + "rows": [ + [ + "Konsollogg", + "Containern skapar /var/log/proxmenux/oci före start på den nod som kör den. Varje nod sparar loggen för de starter som utförts där." + ], + [ + "Nätverks-sysctl och värdmonitor", + "Sparas i /etc/pve/proxmenux, som delas av alla noder i klustret, så att en migrerad container hittar dem." + ], + [ + "Containerdiskar", + "Proxmox flyttar dem med containern. HA behöver ha deras data tillgängliga på målnoden via delad lagring eller en replikering som stöds." + ], + [ + "Värdkataloger och enheter", + "Samma regler som alla LXC: en värdkatalog måste finnas på målnoden och markeras som delad, och en GPU, Coral eller NPU måste finnas där." + ], + [ + "ProxMenux-post", + "Kontraktet stannar på noden där programmet installerades, och varje nod läser kopian i /etc/pve/priv/proxmenux/oci. Ett program med en enda container som migreras registreras på målnoden när Hantera installerade OCI-program öppnas eller en åtgärd startas för programmet. Ett program med flera containrar erbjuds för återställning där eftersom dess privata nätverk måste skapas på den noden." + ] + ] + } + }, + { + "calloutWarning": { + "title": "Inte för hög tillgänglighet", + "body": "En multi-container-applikation når sina medlemmar genom en privat bro av noden som den installerades på, så dess medlemmar stannar på den noden. En värdmonitor, såsom Glances i värdläge eller Netdata, övervakar den nod som den körs på; flytta den skulle övervaka en annan nod." + } + }, + { + "p": "När programmet återställs får en säkerhetskopia som återlästs utanför klustret de filer från /etc/pve/proxmenux som containern använder." + } + ] + }, + { + "id": "channel", + "title": "En rullande tag är inte en obevakad uppdatering", + "blocks": [ + { + "p": "Katalogen installerar den rullande tagg som underhållaren publicerar, men den faktiska digesten slås upp och sparas och ändras bara genom en uttrycklig uppdatering med förkontroller och återställning. ProxMenux Monitor jämför den installerade digesten med den som registret publicerar och visar och aviserar när en ny avbildning är tillgänglig." + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/monitor.json b/web/messages/sv/docs/oci-manager/monitor.json new file mode 100644 index 00000000..c1780c38 --- /dev/null +++ b/web/messages/sv/docs/oci-manager/monitor.json @@ -0,0 +1,213 @@ +{ + "meta": { + "title": "OCI-behållare i ProxMenux Monitor | ProxMenux", + "description": "Vad ProxMenux Monitor visar för en container installerad av OCI Manager Apps: applikations- och avbildningsversioner, nya avbildningar, konsolutdata och Proxmox VE-terminalen." + }, + "header": { + "title": "I ProxMenux Monitor", + "description": "En container installerad av OCI Manager Apps visas i VMs & LXCs som alla andra LXC. Dess modal läser installationsposten: applikations- och avbildningsversioner, nya avbildningar, konsolutdata och terminal.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Posten är källan", + "body": "OCI Manager Apps vet vad den installerade och var den kom ifrån. Monitorn läser det installationsrekordet istället för att prova containern, så data är detsamma om containern körs eller stoppas." + } + }, + { + "table": { + "headers": [ + "Flik", + "Vilka förändringar för en OCI container" + ], + "rows": [ + [ + "Status", + "En Watchdog-brytare under Start vid start: programmet startas igen när det stannar på egen hand." + ], + [ + "App", + "Programmet identifieras från posten och uppdateringar spåras av avbildning" + ], + [ + "Uppdateringar", + "Avbildningen uppdateras eller programmet ändras, med samma flöde som OCI-menyn" + ], + [ + "Monteringar", + "Container diskar och värd kataloger, med deras användning" + ], + [ + "Loggar", + "Endast för OCI-containrar: programmets konsolutdata" + ] + ] + } + } + ] + }, + { + "id": "app", + "title": "App", + "intro": "App-fliken erbjuder den installerade applikationen som en detektering, med namnet, logotypen, porten och schemat för posten. När den registreras öppnas redigeraren med metoden OCI-avbildning (installerad av ProxMenux) , som inte behöver någon konfiguration.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "boxes", + "title": "Program", + "body": "Versionen av applikationen i avbildningen, läs från själva avbildningen: dess miljö eller org.opencontainers.image.version-etiketten. Det är informativt." + }, + { + "icon": "archive", + "title": "Avbildning", + "body": "Byggdatum och digest för den installerade avbildningen. Uppdateringen avgörs här: installerad digest jämförs med den som registret publicerar för samma tagg i dag." + } + ] + } + }, + { + "list": { + "items": [ + "När registret publicerar en ny digest visar kortet Ny avbildning med datum och digest, även om programversionen inuti inte ändrats: en ombyggnad på en uppdaterad bas är en uppdatering.", + "När digestvärdena matchar visar kortet Version.", + "Kortet länkar till bildförvaret: dess GitHub-projekt, eller dess Docker Hub-sida för en officiell avbildning.", + "Tillgångslänken använder containerns LAN-adress, även för huvudmedlemmen i en multi-container-applikation, som har en andra adress på sitt privata nätverk.", + "Knappen Uppdatera data läser installationsposten och avbildningsregistret igen. Alternativen för att söka efter eller registrera andra program visas inte: containern innehåller exakt det program som anges i posten." + ] + } + }, + { + "figure": { + "src": "/oci-manager/monitor-app-tab.png", + "alt": "App-fliken för en OCI-behållare med applikationsversionen, avbildningen och förvaringslänken", + "caption": "App-fliken för en container installerad av OCI Manager Apps." + } + }, + { + "p": "Kontrollen körs en gång om dagen med uppdateringskontrollerna av Monitor, och Uppdatera data kör den omedelbart. En ny avbildning skickas som ett meddelande via de kanaler som konfigureras i Aviseringar. Uppdateringen tillämpas från fliken Uppdateringar." + } + ] + }, + { + "id": "updates", + "title": "Uppdateringar", + "intro": "För en container installerad av OCI Manager Apps visar fliken Uppdateringar programmet med sin installerade avbildning och när registret publicerar en, den nya avbildningen, i samma format som alla andra program. Paket- och programuppdaterarna för en vanlig LXC visas inte.", + "blocks": [ + { + "table": { + "headers": [ + "Knapp", + "Vad det öppnar" + ], + "rows": [ + [ + "Uppdatering", + "Uppdateringen från Hantera installerade OCI-program för denna container i Monitors terminal. Den kan köras med eller utan ny avbildning; utan en ny avbildning ändras ingenting. I ett program med flera containrar uppdateras alla medlemmar." + ], + [ + "Ändra", + "Redigeraren för programmet (resurser, nätverk, vägar och GPU). I en multi-container-applikation lägger den till eller tar bort de extra vägarna och enheterna i applikationsbehållaren och i Immich ändrar det som kör igenkänning." + ], + [ + "Återställ", + "Ersätter uppdatering när en operation på containern avbröts och öppnar sin återhämtning." + ], + [ + "Återställ (återställd container)", + "Visas för en container återställd från säkerhetskopia som saknar kontrakt på denna värd, under Återställt OCI-program. Öppnar återställningen i Monitors terminal; Uppdatera och Ändra visas när den har registrerats." + ] + ] + } + }, + { + "p": "Externa förändringar, värdkataloger och multi-container-applikationer hanteras som i OCI-menyn. I en multi-container-applikation visas knapparna och alternativen endast på huvudbehållaren; de andra medlemmarna visar sin avbildning, en anteckning om att de uppdateras från huvudbehållaren och en knapp som öppnar den. När terminalen stängs läses avbildningen, posten och monteringarna igen." + }, + { + "table": { + "headers": [ + "Alternativ", + "Beteende" + ], + "rows": [ + [ + "Behåll säkerhetskopian som tas före uppdateringen", + "Varje uppdatering backar upp containern för att återställa den om uppdateringen misslyckas. Med det här alternativet hålls samma säkerhetskopiering i den valda lagringen och visas bland säkerhetskopiorna i CT; på Proxmox Backup Server skrivs en säkerhetskopia före uppdateringen." + ], + [ + "Schemalagda uppdateringar", + "Avbildningen uppdateras vid vald tid bara om registret publicerar en ny, med möjlighet att vänta tills den är 1, 3, 7 eller 14 dagar gammal. En container med ändringar gjorda utanför ProxMenux hoppas över och rapporteras." + ] + ] + } + } + ] + }, + { + "id": "logs", + "title": "Loggar", + "intro": "Logs-fliken visas bara för containrar installerade av OCI manager Apps, mellan Monteringar och Säkerhetskopior. Den visar standardutdata och standardfel från avbildningens huvudprocess, samma utdata som docker logs visar för en Docker-container.", + "blocks": [ + { + "list": { + "items": [ + "Utdata sparas på värden i /var/log/proxmenux/oci/VMID.console.log (läge 0600), från första starten och över omstarter, så att de kan läsas när containern är stoppad.", + "Filen roteras på 10 MB och håller tre komprimerade kopior (/etc/logrotate.d/proxmenux-oci).", + "Varje start av containern är markerad i utgången. Fliken visar utgången sedan den sista starten som standard; Full historia visar också de tidigare.", + "När programmet tas bort tas även dess utdata bort.", + "De senaste 100, 500 eller 1000 raderna visas. När containern körs följs nya rader i realtid; om du rullar upp pausas följningen och Följ återupptar den.", + "Ett filter visar endast linjer som innehåller en text, och Ladda ner sparar linjerna laddade.", + "Färgkoder tas bort och en rad som ritas om av en förloppsindikator visas i sitt slutliga tillstånd.", + "Fliken läser filen varje gång den öppnas; den mellanlagras inte." + ] + } + }, + { + "calloutInfo": { + "title": "Inloggningsuppgifter vid första starten", + "body": "Avbildningar som skriver ut ett genererat lösenord på sin första start lämnar det i denna utgång, som docker logs gör. Installatören läser den därifrån för att visa den i sin sammanfattning." + } + }, + { + "figure": { + "src": "/oci-manager/monitor-logs-tab.png", + "alt": "Logs-fliken med konsolutgången av en OCI-behållare, linjeväljaren, filtret och följknappen", + "caption": "Konsolutgång av en OCI-behållare." + } + } + ] + }, + { + "id": "terminal", + "title": "Proxmox VE konsol", + "blocks": [ + { + "p": "En OCI-bild kör sin egen process som PID 1 och ingen inloggningstjänst, så standardkonsolen för en LXC skulle öppna en terminal där inget svarar. Containers installerade av OCI Manager Apps skapas med cmode: shell: Proxmox VE-konsolen öppnar ett skal med lxc-attach, motsvarande docker exec, med den löpande applikationen orörd." + }, + { + "list": { + "items": [ + "Skalet är det som /etc/passwd anger för root i avbildningen. Om root saknar skal eller avbildningen inte innehåller något behålls standardkonsolen.", + "Skalet körs som root i containern, utan ett lösenord. Vem kan öppna det beslutas av VM.Console tillstånd av Proxmox VE.", + "Monitorns terminal går in i containern med pct enter, som fungerar på samma sätt." + ] + } + } + ] + }, + { + "id": "mounts", + "title": "Monteringar", + "blocks": [ + { + "p": "Monteringar-fliken listar containerns diskar och värdkataloger. Användningen av en containerdisk på blocklagring som LVM-thin läses från diskens filsystem, som bara är monterat inuti containern." + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/stacks.json b/web/messages/sv/docs/oci-manager/stacks.json new file mode 100644 index 00000000..b50eadde --- /dev/null +++ b/web/messages/sv/docs/oci-manager/stacks.json @@ -0,0 +1,404 @@ +{ + "meta": { + "title": "Multi-container applikationer | ProxMenux", + "description": "Hur OCI Manager Apps förvandlar en applikation med sin databas och cache till samordnade inbyggda LXC:er: plan, privat nätverk, beroendehook, validering och transaktionsuppdateringar." + }, + "header": { + "title": "Program med flera containrar", + "description": "En applikation med sin databas, cache och andra tjänster blir flera samordnade inbyggda LXC:er, installerade och uppdaterade som en.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "En applikation för användaren, flera LXCs för Proxmox VE", + "body": "En multi-container definition är en post av katalogen. Installationsprogrammet skapar en inbyggd LXC per tjänst och håller sina beroenden explicit. Immich, Nextcloud, Paperless-ngx och Tandoor installeras på detta sätt." + } + }, + { + "mermaid": { + "chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}
{{appNet}}\"]\n O --> D[\"{{db}}
{{private}}\"]\n O --> R[\"{{cache}}
{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A", + "labels": { + "plan": "Stackplan", + "app": "Programmets LXC", + "appNet": "LAN + privat", + "db": "PostgreSQL LXC", + "cache": "Valkey LXC", + "private": "privat" + } + } + } + ] + }, + { + "id": "plan", + "title": "1. Planen, innan någon container finns", + "blocks": [ + { + "p": "Inget LXC skapas medan Compose definitionen tolkas. En komplett plan kommer först, med varje medlem, avbildning, VMID, nätverk, väg, hemlighet, ordning och hälsokontroll. Om planen inte är konsekvent startar installationen inte." + }, + { + "table": { + "headers": [ + "Planelement", + "Innehåll", + "Kontrolleras innan du skapar något" + ], + "rows": [ + [ + "Medlemmar", + "Huvudapplikation, databas, cache, maskininlärning och andra beroenden", + "Unika VMID, kända roller och exakt en huvudmedlem" + ], + [ + "Avbilder", + "Rullande referens, arkitektur och fastställd digest för varje tjänst", + "Alla finns, stöder arkitekturen och klarar OCI-integritetskontrollen" + ], + [ + "Nätverk", + "Privat bro, subnet, en fast adress per tjänst och LAN-åtkomst för huvudmedlemmen", + "Ingen kollision med befintliga broar eller undernät och ingen upprepad adress" + ], + [ + "Persistens", + "Container diskar, värdkataloger, ägare och backup", + "Inga överlappande vägar, lagring tillgängliga och deklarerade behörigheter" + ], + [ + "Hemligheter", + "Databaslösenord, applikationsnycklar och inledande inloggningsuppgifter", + "Genereras en gång och ges endast till de medlemmar som använder dem" + ], + [ + "Livscykel", + "Startorder, stopporder och hälsokontroll per medlem", + "Huvudapplikationen börjar sist och stoppar först" + ] + ] + } + } + ] + }, + { + "id": "create", + "title": "2. Skapa en medlem i taget", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Reservera varje VMID", + "body": "Proxmox VE-inventeringen och instansregistret kontrolleras. En befintlig CT tas inte över och ett kontrakt som fortfarande tillhör en annan instans återanvänds inte." + }, + { + "title": "Förbered varje avbildning", + "body": "Alla avbildningar löses, laddas ner och verifieras innan den första containern skapas." + }, + { + "title": "Skapa varje rootfs", + "body": "Den officiella OCI-metadatan importeras och ProxMenux-instansidentiteten och medlemsrollen läggs till." + }, + { + "title": "Privat nätverk", + "body": "En bro och undernät skapas och varje medlem får sin fasta adress; endast huvudmedlemmen får också LAN-gränssnittet." + }, + { + "title": "Persistens", + "body": "Varje databas och konfiguration får sin egen containerdisk; endast data avsedda att delas använder värdkataloger." + }, + { + "title": "Miljö", + "body": "Interna slutpunkter, delade hemligheter och servicevariabler skrivs. Programmet når sina beroenden på deras reserverade privata adresser." + }, + { + "title": "Post", + "body": "Varje medlem registrerar sin inbyggda konfiguration, de rootfs-ändringar som uppdateringar måste återskapa och sin relation till stacken." + } + ] + } + } + ] + }, + { + "id": "checks", + "title": "3. Börja och kontrollera varje container", + "intro": "En skapad LXC betyder inte att tjänsten är redo. Beroendena startar i ordning och varje tjänst genomgår en egen kontroll.", + "blocks": [ + { + "table": { + "headers": [ + "Tjänst", + "Kontroll", + "Vad det visar" + ], + "rows": [ + [ + "PostgreSQL", + "pg_isready i CT med förväntad värd, användare och databas", + "Servern accepterar anslutningar för den konfigurerade databasen" + ], + [ + "Redis / Valkey", + "redis-cli eller valkey-cli PING mot sin privata adress", + "Brokern lyssnar och svarar" + ], + [ + "Immich maskininlärning", + "HTTP GET /ping, plus en kontroll av den valda GPU runtime", + "Tjänsten svarar och den begärda accelerationen har inte fallit tillbaka till CPU" + ], + [ + "Nextcloud", + "GET /status.php med installed=true, maintenance=false och needsDbUpgrade=false", + "Initieringen är klar utan väntande migrering" + ], + [ + "Paperless-ngx / Tandoor", + "HTTP på LAN-adressen och tjänstens faktiska port", + "Frontend och dess beroenden tillhandahåller programmet" + ], + [ + "Huvudapplikation", + "Slutpunkten för dess mall, till exempel /api/server/ping i Immich", + "Hela stacken fungerar genom programmet som använder beroenden" + ] + ] + } + }, + { + "calloutWarning": { + "title": "Körs betyder inte att allt fungerar", + "body": "Det löpande tillståndet säger bara att LXC-processen existerar. Där tjänsten erbjuder en bättre check, en exec eller HTTP check med en timeout används. Om en medlem slutar eller misslyckas med sin kontroll, deklareras inte stacken installerad." + } + } + ] + }, + { + "id": "hook", + "title": "4. Beroende hook", + "blocks": [ + { + "p": "Kroken är endast inställd på huvudbehållaren av en beroende stack. Proxmox VE håller manuset som en snippet och kör det som hookscript av den CT. Receptet är inte skrivet i manuset: det lever i ett separat privat kontrakt." + }, + { + "codeGrid": { + "items": [ + { + "title": "Huvud CT konfiguration", + "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" + }, + { + "title": "Privat kontrakt (exempel)", + "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" + } + ] + } + }, + { + "snippet": { + "summary": "Fullständig källa till proxmenux-stack-dependencies.sh", + "pathCode": "local:snippets/proxmenux-stack-dependencies.sh", + "snippetCode": "stackDependencyHook" + } + }, + { + "p": "Skriptet är detsamma för varje stack. VMID, namn, checktyper och timeouts kommer från det privata kontraktet /etc/pve/priv/proxmenux-stack-VMID.json av varje stack." + }, + { + "steps": { + "items": [ + { + "title": "Proxmox VE anropar pre-start", + "body": "Innan CT startar körs kroken med sin VMID och livscykelfasen." + }, + { + "title": "Ett lås per stack", + "body": "flock på /run/lock/proxmenux-stack-VMID.lock förhindrar två startsekvenser samtidigt." + }, + { + "title": "Kontraktet läses och valideras", + "body": "Ett känt schema, numeriska beroenden, etiketter, en exec, http eller kör kontroll och en positiv timeout krävs." + }, + { + "title": "Beroende i ordning", + "body": "Varje CT måste finnas; en stoppad startas och en som redan körs inte startas om." + }, + { + "title": "Vänta tills tjänsten är redo", + "body": "Kontrollen körs vartannat sekund, och CT bekräftas också vara igång." + }, + { + "title": "Huvud CT startar", + "body": "När alla beroenden är redo avslutas pre-start och Proxmox VE startar programmet." + } + ] + } + }, + { + "calloutInfo": { + "title": "Kroken stoppar inte beroenden", + "body": "Faserna post-start, pre-stop och post-stop gör ingenting. Om huvud-CT:n stoppas fortsätter PostgreSQL, Redis, Valkey eller maskininlärningen att köras. Hooken ordnar starten; den gör inte flera LXC:er till en process." + } + }, + { + "table": { + "headers": [ + "Typ", + "Exempel", + "Senare starter" + ], + "rows": [ + [ + "Beroende stack", + "Immich, Nextcloud, applikation med PostgreSQL", + "Huvud-CT:ns hook startar beroendena och väntar på dem" + ], + [ + "Applikationssvit", + "Arr-programsviten", + "Ingen huvudmedlem: varje LXC följer sin egen onboot" + ], + [ + "Enskilt program", + "Jellyfin", + "Proxmox VE startar den LXC:n direkt" + ] + ] + } + } + ] + }, + { + "id": "stack-checks", + "title": "5. Kontroller på stacken som helhet", + "blocks": [ + { + "list": { + "items": [ + "Varje VMID finns, är unik och håller den förväntade instansidentiteten.", + "Varje kontrakt tillhör samma stack och behåller sin roll och registrerade inbyggda konfiguration.", + "Huvudmedlemmen är senast i start_order och först i stop_order.", + "Ingen medlem, volym eller rootfs anpassning saknas.", + "Kroken pekar på den officiella snippet, dess innehåll är oförändrat och dess kontrakt matchar stack receptet.", + "De observerade avbildningarna och digestvärdena matchar de förberedda OCI-arkiven.", + "Enheter, GPU-profiler, monteringar, hemligheter och ändpunkter matchar fortfarande kontrakten.", + "När alla beroenden har klarat kontrollerna verifierar huvudprogrammets ändpunkt integrationen mellan medlemmarna." + ] + } + } + ] + }, + { + "id": "manage", + "title": "Hantera en installerad stack", + "intro": "Under Hantera installerade OCI-program leder varje medlem till hela stacken. Menyn erbjuder Uppdatera alla programmets containrar, Ändra extra sökvägar och enheter, Återskapa alla containrar med sparad konfiguration och Ta bort: radera programmet och dess containrar. Återskapa bygger om varje container från dess ursprungliga avbildning med sparad digest, utan att leta efter en nyare. Samma steg som vid en uppdatering används; alternativet erbjuds för stackar som kan uppdateras.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Alla medlemmar", + "body": "Medlemmens kontrakt anger huvud-VMID och den fullständiga listan över medlemmar." + }, + { + "title": "Stacken är reproducerbar", + "body": "Identiteter, kontrakt, krok, anpassningar och en samordnad replay för det receptet valideras." + }, + { + "title": "Avbildningar först", + "body": "Programmet stoppas inte förrän varje digest har fastställts, hämtats och verifierats." + }, + { + "title": "Stoppa och backa upp setet", + "body": "Verifierade inbyggda säkerhetskopior tas när stacken är stoppad, så att program och databaser motsvarar samma tidpunkt." + }, + { + "title": "Uppdatera och kontrollera varje medlem", + "body": "Anpassning, nätverk, fästen, hemligheter och enheter appliceras igen innan hälsokontrollen av varje medlem." + }, + { + "title": "Publicera eller återställa allt", + "body": "De nya kontrakten publiceras endast när varje medlem passerar. Om man misslyckas återställs varje medlem." + } + ] + } + }, + { + "table": { + "headers": [ + "Ändra", + "Vilka förändringar", + "Vad som behålls" + ], + "rows": [ + [ + "Extra sökvägar och enheter", + "De läggs till eller tas bort från applikationsbehållaren, som startar om. Ingenting byggs om", + "Uppgifterna för programmet, dess databas och andra containrar" + ], + [ + "Vad som kör igenkänningen (Immich)", + "Maskininlärningsbehållaren tar avbildningen och enheterna i CPU eller en GPU av värden. Hela programmet stoppas och uppdateras, som i en uppdatering", + "Modellcachen, biblioteket och databasen. Om något misslyckas, återställs tidigare containrar och tidigare val" + ] + ] + } + }, + { + "calloutWarning": { + "title": "En stack utan samordnad replay uppdateras inte", + "body": "Om beredningen av en stack inte kan reproduceras vägras uppdateringen innan stacken stoppas. Applikationen fortsätter att fungera som det är." + } + } + ] + }, + { + "id": "failure", + "title": "6. När något misslyckas", + "blocks": [ + { + "p": "Under en installation stannar ett fel och tar bort de ofullständiga containrarna som skapats och den privata broen om operationen skapade den. En stack publiceras inte som giltig tills hela sekvensen avslutas." + }, + { + "p": "En uppdatering är transaktionell: alla avbildningar är förberedda först, sedan stacken stannar, varje medlem backas upp och backup verifieras, och först då är varje rootfs ersatt. Medlemmar startar en efter en med sin hälsokontroll; om man misslyckas återställs varje medlem från samma uppsättning säkerhetskopior, så databasen och programmet hör aldrig till olika ögonblick." + }, + { + "mermaid": { + "chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]", + "labels": { + "prepare": "Förbered varje avbildning", + "stop": "Sluta, huvudmedlem först", + "backup": "Verifierad säkerhetskopia av varje CT", + "replace": "Ersätt rootfs", + "healthy": "Fungerar alla?", + "yes": "Ja", + "no": "Nej", + "commit": "Publicera avtal", + "rollback": "Återställ varje medlem" + } + } + } + ] + }, + { + "id": "not-assumed", + "title": "Vad en stack inte inkluderar", + "blocks": [ + { + "list": { + "items": [ + "En svit installerad i ett flöde, till exempel Arr-sviten, är inte en beroende stack: dess containrar har ingen startordning mellan dem.", + "Det privata nätverket ersätter inte autentisering, TLS eller konfigurationen av varje applikation.", + "Säkerhetskopieringen av en LXC innehåller inte bron, kroken eller kontrakten i resten av stacken.", + "Prowlarr, Sonarr eller Radarr får inga indexerare, profiler eller leverantörer från ProxMenux." + ] + } + } + ] + } + ] +} diff --git a/web/messages/sv/docs/oci-manager/storage-network.json b/web/messages/sv/docs/oci-manager/storage-network.json new file mode 100644 index 00000000..db121e93 --- /dev/null +++ b/web/messages/sv/docs/oci-manager/storage-network.json @@ -0,0 +1,305 @@ +{ + "meta": { + "title": "Data, sökvägar och nätverk | ProxMenux", + "description": "Så bevarar OCI manager Apps data i en OCI-container: containerdiskar, kataloger på värden, rclone-monteringar, adresser och privata nätverk." + }, + "header": { + "title": "Data, sökvägar och nätverk", + "description": "Vad som finns i rootfs, vad som bevaras när det ersätts, hur data delas mellan containrar och hur varje container får sin adress.", + "section": "OCI manager Apps" + }, + "sections": [ + { + "id": "intro", + "blocks": [ + { + "calloutInfo": { + "title": "Beständig lagring bestäms innan LXC-containern skapas", + "body": "Volymerna som deklareras av avbildningen och dess Compose-fil läses innan containern skapas. Varje sökväg som måste bevaras vid en uppdatering blir en monteringspunkt som är oberoende av rootfs. Därmed innehåller rootfs bara det som tillhör avbildningen och kan ersättas." + } + } + ] + }, + { + "id": "questions", + "title": "Vad installationsguiden frågar efter", + "intro": "Mallen anger de sökvägar som programmet behöver. Var och en placeras på en containerdisk eller i en katalog på värden. Fler sökvägar kan läggas till före sammanfattningen.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Nödvändiga sökvägar", + "body": "/config, /data, bibliotek, hämtningar och alla volymer som programmet deklarerar listas." + }, + { + "title": "Plats", + "body": "Varje sökväg placeras på en containerdisk eller i en befintlig katalog på värden." + }, + { + "title": "Lagring och storlek", + "body": "En containerdisk skapas på en Proxmox VE-lagring med den angivna storleken. Standardinstallationen använder local-lvm. Den avancerade installationen frågar efter lagring för varje sökväg strax före storleken och föreslår lagringen för rootfs. Disken visas som vm-VMID-disk-N och ansluts som mpN." + }, + { + "title": "Katalog på värden", + "body": "Katalogen på värden anges med sin sökväg. Om den inte finns skapas den med den värdanvändare som containeranvändaren mappas till som ägare." + }, + { + "title": "Ytterligare sökvägar", + "body": "Före installationen kan fler kopplingar läggas till mellan en sökväg eller volym på värden och en sökväg i containern." + }, + { + "title": "Sammanfattning", + "body": "Hela sökvägsmappningen visas innan CT:n skapas och sparas i instanskontraktet." + } + ] + } + } + ] + }, + { + "id": "options", + "title": "De två platserna för beständig lagring", + "blocks": [ + { + "table": { + "headers": [ + "Egenskap", + "Containerdisk", + "Katalog på värden" + ], + "rows": [ + [ + "I konfigurationen", + "mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G", + "mpN: /mnt/oci-shared/media,mp=/data/media" + ], + [ + "Containerns säkerhetskopia (vzdump)", + "Ingår med backup=1", + "Ingår inte" + ], + [ + "Storlek", + "Fast; kan utökas genom att ändra monteringspunktens storlek", + "Det lediga utrymmet i värdens filsystem eller dataset" + ], + [ + "Andra containrar", + "Monteras bara av den container som den tillhör", + "Samma katalog kan monteras i flera containrar" + ], + [ + "Ögonblicksbilder och återställning", + "Hanteras av Proxmox VE tillsammans med CT:n", + "Hanteras på värdens lagring" + ], + [ + "När programmet tas bort", + "Tas bort med containern", + "Bevaras med sitt innehåll" + ], + [ + "När CT:n flyttas till en annan nod", + "Flyttas med CT:n", + "Samma sökväg måste finnas på den andra noden" + ] + ] + } + }, + { + "p": "Rootfs är reserverat för binärfilerna och avbildningens innehåll. En uppdatering eller ett återskapande ersätter det utan att röra någon av de två typerna av monteringspunkt." + } + ] + }, + { + "id": "example", + "title": "Exempel: en container med båda platserna", + "blocks": [ + { + "code": { + "title": "Jellyfin installerat som CT 151 på local-lvm (utdrag)", + "code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media" + } + }, + { + "p": "En uppdatering eller ett återskapande ersätter bara rootfs: mp0 bevarar användare, bibliotek och inställningar, och mp1 visar fortfarande samma medier. När CT:ns säkerhetskopia återställs kommer /config tillbaka. Mediekatalogen återställs vid behov från säkerhetskopian av värdens lagring." + }, + { + "flow": { + "nodes": [ + { + "label": "Kontrakt", + "detail": "/config" + }, + { + "label": "Plats", + "detail": "Containerdisk\neller katalog på värden" + }, + { + "label": "LXC", + "detail": "Alltid /config\nför programmet" + } + ], + "caption": "Programmet ser sökvägen som avbildningen anger. Bara lagringsplatsen ändras." + } + } + ] + }, + { + "id": "shared", + "title": "En katalog på värden, flera containrar", + "blocks": [ + { + "mermaid": { + "chartCode": "flowchart TB\n H[\"{{host}}
/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent
/data\"]\n H --> J[\"Jellyfin
/data\"]\n H --> R[\"Radarr / Sonarr
/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]", + "labels": { + "host": "Katalog på värden", + "config": "Egen konfiguration" + } + } + }, + { + "p": "Varje container sparar sin konfiguration på sin egen disk. Biblioteket eller hämtningarna ligger i en enda katalog på värden som monteras på samma interna sökväg i alla containrar. Därmed är sökvägen som ett program skriver till samma sökväg som ett annat läser från." + } + ] + }, + { + "id": "rclone", + "title": "Molnlagring via programmet rclone", + "intro": "Utöver installation erbjuder rclone-posten i katalogen Aktivera en montering i en befintlig OCI-container med rclone. En fjärrlagring som redan skapats och auktoriserats i rclones webbgränssnitt monteras och görs tillgänglig på värden. Andra containrar kan använda den som en katalog på värden.", + "blocks": [ + { + "steps": { + "items": [ + { + "title": "Container och fjärrlagring", + "body": "VMID för rclone-containern, fjärrlagringens exakta namn och, valfritt, en sökväg i den." + }, + { + "title": "Monteringens namn och cache", + "body": "Monteringens namn och VFS-cacheläget: off, minimal, writes eller full (standard)." + }, + { + "title": "Tillgängliga vyer", + "body": "En gemensam rot, som standard /mnt/oci-shared, innehåller en läs- och skrivbar vy i /mnt/oci-shared/remotes/NAME och en skrivskyddad vy i /mnt/oci-shared/remotes-ro/NAME." + }, + { + "title": "Aktivering", + "body": "Efter bekräftelsen stoppas CT:n, dess startkommando och ett Proxmox VE-hookscript konfigureras, och den startas igen. Åtgärden väntar tills båda vyerna är monterade på värden." + } + ] + } + }, + { + "calloutInfo": { + "title": "Om monteringen inte aktiveras", + "body": "Containerns tidigare konfiguration återställs och containern startas igen. En misslyckad aktivering lämnar därmed rclone som det var." + } + } + ] + }, + { + "id": "network", + "title": "Adresser och nätverk", + "intro": "Ett program med en enda container behöver en adress. Ett program med flera containrar behöver också ett stabilt nätverk mellan sina delar.", + "blocks": [ + { + "cards": { + "items": [ + { + "icon": "network", + "title": "Program med en container", + "body": "En nätverksbrygga väljs tillsammans med DHCP eller en fast adress i CIDR-format. LXC-containern har en egen adress och sammanfattningen visar tjänsternas fullständiga URL:er." + }, + { + "icon": "waypoints", + "title": "Program med flera containrar", + "body": "Ett ledigt subnät hittas och en beständig privat brygga skapas. Varje del – program, databas och cache – får en fast adress på nätverket." + } + ] + } + }, + { + "flow": { + "nodes": [ + { + "label": "LAN", + "detail": "Nåbar adress\nbara huvudtjänsten" + }, + { + "label": "Huvud-LXC", + "detail": "Webb / API\nLAN + privat nätverk" + }, + { + "label": "Privat nätverk", + "detail": "PostgreSQL · Valkey · ML\nfasta adresser" + } + ], + "caption": "Beroenden kommunicerar över det privata nätverket och har ingen adress på LAN:et." + } + }, + { + "p": "Stackkontraktet sparar brygga, subnät, adresser och relationerna mellan tjänsterna. När programmet uppdateras återanvänds samma topologi för alla delar. ProxMenux Monitor öppnar huvudcontainern på dess LAN-adress, inte dess adress på det privata nätverket." + } + ] + }, + { + "id": "ownership", + "title": "Ägarskap för filer och kataloger", + "blocks": [ + { + "list": { + "items": [ + "Nya kataloger skapas med de UID och GID som den oprivilegierade LXC-containern mappas till.", + "Ägarskapet för befintliga kataloger ändras inte rekursivt.", + "Socklar, systemfiler och känsliga sökvägar erbjuds inte som vanliga kataloger på värden." + ] + } + } + ] + }, + { + "id": "backup", + "title": "Vad en säkerhetskopia av containern innehåller", + "blocks": [ + { + "table": { + "headers": [ + "Del", + "I CT:ns vzdump-säkerhetskopia", + "Var det sparas" + ], + "rows": [ + [ + "OCI-rootfs", + "Ja", + "I CT:ns säkerhetskopia; kan också återskapas från avbildningen och kontraktet" + ], + [ + "Containerdisk med backup=1", + "Ja", + "CT:ns säkerhetskopia" + ], + [ + "Katalog på värden", + "Nej", + "Säkerhetskopian av värdens lagring" + ], + [ + "Instanskontrakt", + "Ja, kopian som finns i CT:n", + "/.proxmenux/oci-record.json i CT:n; ytterligare kopior på värden och i klustret sparas separat" + ], + [ + "Program med flera containrar", + "Varje del i sin egen säkerhetskopia", + "Säkerhetskopiorna av alla delar samt stackkontraktet och dess nätverksbrygga på värden" + ] + ] + } + } + ] + } + ] +} From f36b5e22a8cbb265b6291e54a48c1c8855b01e4e Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:08:53 +0200 Subject: [PATCH 16/53] docs: review project information in all seven translations --- .../tests/test_documentation_markdown.py | 16 + .github/workflows/test-i18n.yml | 10 + .../docs/about/code-of-conduct/page.tsx | 18 +- .../de/docs/about/code-of-conduct.json | 13 + web/messages/de/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/de/docs/about/contributors.json | 38 +++ web/messages/de/docs/about/faq.json | 94 ++++++ web/messages/de/docs/about/index.json | 67 ++++ .../en/docs/about/code-of-conduct.json | 3 +- web/messages/en/docs/about/faq.json | 12 +- .../es/docs/about/code-of-conduct.json | 7 +- web/messages/es/docs/about/contributing.json | 6 +- web/messages/es/docs/about/contributors.json | 18 +- web/messages/es/docs/about/faq.json | 28 +- web/messages/es/docs/about/index.json | 8 +- .../fr/docs/about/code-of-conduct.json | 13 + web/messages/fr/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/fr/docs/about/contributors.json | 38 +++ web/messages/fr/docs/about/faq.json | 94 ++++++ web/messages/fr/docs/about/index.json | 67 ++++ .../it/docs/about/code-of-conduct.json | 13 + web/messages/it/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/it/docs/about/contributors.json | 38 +++ web/messages/it/docs/about/faq.json | 94 ++++++ web/messages/it/docs/about/index.json | 67 ++++ .../pt/docs/about/code-of-conduct.json | 13 + web/messages/pt/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/pt/docs/about/contributors.json | 38 +++ web/messages/pt/docs/about/faq.json | 94 ++++++ web/messages/pt/docs/about/index.json | 67 ++++ .../sk/docs/about/code-of-conduct.json | 13 + web/messages/sk/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/sk/docs/about/contributors.json | 38 +++ web/messages/sk/docs/about/faq.json | 94 ++++++ web/messages/sk/docs/about/index.json | 67 ++++ .../sv/docs/about/code-of-conduct.json | 13 + web/messages/sv/docs/about/contributing.json | 293 ++++++++++++++++++ web/messages/sv/docs/about/contributors.json | 38 +++ web/messages/sv/docs/about/faq.json | 94 ++++++ web/messages/sv/docs/about/index.json | 67 ++++ 40 files changed, 3111 insertions(+), 45 deletions(-) create mode 100644 .github/scripts/tests/test_documentation_markdown.py create mode 100644 web/messages/de/docs/about/code-of-conduct.json create mode 100644 web/messages/de/docs/about/contributing.json create mode 100644 web/messages/de/docs/about/contributors.json create mode 100644 web/messages/de/docs/about/faq.json create mode 100644 web/messages/de/docs/about/index.json create mode 100644 web/messages/fr/docs/about/code-of-conduct.json create mode 100644 web/messages/fr/docs/about/contributing.json create mode 100644 web/messages/fr/docs/about/contributors.json create mode 100644 web/messages/fr/docs/about/faq.json create mode 100644 web/messages/fr/docs/about/index.json create mode 100644 web/messages/it/docs/about/code-of-conduct.json create mode 100644 web/messages/it/docs/about/contributing.json create mode 100644 web/messages/it/docs/about/contributors.json create mode 100644 web/messages/it/docs/about/faq.json create mode 100644 web/messages/it/docs/about/index.json create mode 100644 web/messages/pt/docs/about/code-of-conduct.json create mode 100644 web/messages/pt/docs/about/contributing.json create mode 100644 web/messages/pt/docs/about/contributors.json create mode 100644 web/messages/pt/docs/about/faq.json create mode 100644 web/messages/pt/docs/about/index.json create mode 100644 web/messages/sk/docs/about/code-of-conduct.json create mode 100644 web/messages/sk/docs/about/contributing.json create mode 100644 web/messages/sk/docs/about/contributors.json create mode 100644 web/messages/sk/docs/about/faq.json create mode 100644 web/messages/sk/docs/about/index.json create mode 100644 web/messages/sv/docs/about/code-of-conduct.json create mode 100644 web/messages/sv/docs/about/contributing.json create mode 100644 web/messages/sv/docs/about/contributors.json create mode 100644 web/messages/sv/docs/about/faq.json create mode 100644 web/messages/sv/docs/about/index.json diff --git a/.github/scripts/tests/test_documentation_markdown.py b/.github/scripts/tests/test_documentation_markdown.py new file mode 100644 index 00000000..98255351 --- /dev/null +++ b/.github/scripts/tests/test_documentation_markdown.py @@ -0,0 +1,16 @@ +"""Keep the canonical conduct policy and its English website source in sync.""" +import json +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[3] + + +class DocumentationMarkdownTests(unittest.TestCase): + def test_english_conduct_body_matches_canonical_policy(self): + source = json.loads((ROOT / 'web/messages/en/docs/about/code-of-conduct.json').read_text(encoding='utf-8')) + self.assertEqual(source['contentMarkdown'], (ROOT / 'CODE_OF_CONDUCT.md').read_text(encoding='utf-8')) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/workflows/test-i18n.yml b/.github/workflows/test-i18n.yml index 2b3e4685..7f4dfb51 100644 --- a/.github/workflows/test-i18n.yml +++ b/.github/workflows/test-i18n.yml @@ -9,15 +9,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' push: branches: [main, develop] paths: @@ -27,15 +32,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' workflow_dispatch: permissions: diff --git a/web/app/[locale]/docs/about/code-of-conduct/page.tsx b/web/app/[locale]/docs/about/code-of-conduct/page.tsx index de18716b..53365824 100644 --- a/web/app/[locale]/docs/about/code-of-conduct/page.tsx +++ b/web/app/[locale]/docs/about/code-of-conduct/page.tsx @@ -24,23 +24,27 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s } } -async function getCodeOfConductContent(notFoundMsg: string, loadFailedMsg: string) { +async function getCodeOfConductContent(notFoundMsg: string, loadFailedMsg: string, translatedMarkdown?: string) { try { const codeOfConductPath = path.join(process.cwd(), "..", "CODE_OF_CONDUCT.md") - if (!fs.existsSync(codeOfConductPath)) { + if (!translatedMarkdown && !fs.existsSync(codeOfConductPath)) { console.error("CODE_OF_CONDUCT.md file not found.") return `

${notFoundMsg}

` } - const fileContents = fs.readFileSync(codeOfConductPath, "utf8") + const fileContents = translatedMarkdown || fs.readFileSync(codeOfConductPath, "utf8") const result = await remark() .use(gfm.default || gfm) .use(html) .process(fileContents) - return result.toString() + // Repository-relative policy links otherwise resolve under the docs URL. + return result.toString().replaceAll( + 'href="./SECURITY.md"', + 'href="https://github.com/MacRimi/ProxMenux/blob/main/SECURITY.md"', + ) } catch (error) { console.error("Error reading the CODE_OF_CONDUCT.md file", error) return `

${loadFailedMsg}

` @@ -71,7 +75,11 @@ export default async function CodeOfConductPage({ params }: { params: Promise<{ const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.about.codeOfConduct" }) - const codeOfConductContent = await getCodeOfConductContent(t("errors.notFound"), t("errors.loadFailed")) + const translatedMarkdown = t.has("contentMarkdown") ? t.raw("contentMarkdown") : undefined + const codeOfConductContent = await getCodeOfConductContent( + t("errors.notFound"), t("errors.loadFailed"), + typeof translatedMarkdown === "string" ? translatedMarkdown : undefined, + ) const cleanedInlineCode = cleanInlineCode(codeOfConductContent) const parsedContent = wrapCodeBlocksWithCopyable(cleanedInlineCode) diff --git a/web/messages/de/docs/about/code-of-conduct.json b/web/messages/de/docs/about/code-of-conduct.json new file mode 100644 index 00000000..4e79d0fe --- /dev/null +++ b/web/messages/de/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Verhaltenskodex | ProxMenux-Dokumentation", + "description": "ProxMenux Verhaltenskodex und Best Practices für Mitwirkende, Tester und Nutzer.", + "ogTitle": "Verhaltenskodex | ProxMenux-Dokumentation", + "ogDescription": "Erwartungen an die Verantwortung für eingereichten Code, die Meldung von Sicherheitslücken und den Umfang von Pull Requests im ProxMenux-Projekt." + }, + "errors": { + "notFound": "Fehler: Die Datei CODE_OF_CONDUCT.md wurde nicht gefunden.", + "loadFailed": "Fehler: Der Inhalt des Verhaltenskodex kann nicht geladen werden." + }, + "contentMarkdown": "# 📝 Verhaltenskodex und bewährte Vorgehensweisen\n\nDieses Dokument legt die **Grundsätze und Richtlinien** für die Nutzung von **ProxMenux** fest. Es soll **Respekt, Zusammenarbeit und Sicherheit** bei der Entwicklung fördern.\n\n## 📌 1. Urheberangaben und Anerkennung\n\n- Werden **Skripte, Codeausschnitte oder Ideen** anderer Repositorys oder Entwickler verwendet, wird der ursprüngliche Urheber immer genannt.\n- Ein Kommentar im Code oder in der Dokumentation nennt die Quelle und verlinkt möglichst auf das ursprüngliche Repository.\n- Bei Code mit einer bestimmten **Lizenz** müssen deren Bedingungen eingehalten werden.\n\n### ✅ Beispiel für eine korrekte Quellenangabe im Code:\n\n```bash\n# Skript auf Grundlage der Arbeit von [Autor] unter [Repository-Link]\n```\n\n## 🔒 2. Sicherheit und Verantwortung für den Code\n\n- Der gesamte Code in diesem Repository folgt bewährten Vorgehensweisen für sichere Entwicklung.\n- Beiträge mit absichtlich eingebauten Schwachstellen oder unsicheren Vorgehensweisen sind nicht erlaubt.\n- Skripte dürfen niemals die Systemsicherheit oder Benutzerdaten gefährden.\n- Beiträge sollen vor der Implementierung auf Sicherheit und Stabilität geprüft werden.\n\n### ⚠️ Verantwortungsvolle Meldung von Sicherheitslücken\n\nEntdecken Sie eine Sicherheitslücke, melden Sie diese NICHT in öffentlichen Issues oder Foren.\n\nMelden Sie sie stattdessen vertraulich per E-Mail:\n\n📧 proxmenux@macrimi.pro\n\nWeitere Informationen zu Sicherheitslücken und deren Meldung finden Sie in unserer [Sicherheitsrichtlinie](./SECURITY.md).\n\n## 🤝 3. Regeln für die Community\n\nFür ein respektvolles und professionelles Umfeld müssen alle Benutzer diese Regeln beachten:\n\n- ✅ Bleiben Sie in Diskussionen respektvoll und konstruktiv.\n- ❌ Belästigung, Hassrede und beleidigendes Verhalten werden nicht toleriert.\n- ✅ Geben Sie Rückmeldungen sachlich und ohne persönliche Angriffe.\n- ❌ Verstöße können je nach Schwere zu Verwarnungen oder Ausschlüssen führen.\n\n##\n\nDieses Dokument fördert den ethischen Umgang mit Code, Sicherheitsbewusstsein und eine respektvolle Community.\n\n📢 Lassen Sie uns gemeinsam etwas Großartiges schaffen!\n" +} diff --git a/web/messages/de/docs/about/contributing.json b/web/messages/de/docs/about/contributing.json new file mode 100644 index 00000000..7827e51f --- /dev/null +++ b/web/messages/de/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Zu ProxMenux beitragen | ProxMenux-Dokumentation", + "description": "Zu ProxMenux beitragen: Branch-Modell (main / develop / feature/*), Pull-Request-Ablauf, Skript-Header mit Autorenangabe und optionalem Sponsoring-Link, zweiphasige UI-Richtlinie, Nachrichtenfunktionen und der Weg vom Feature-Branch zur stabilen Version.", + "ogTitle": "Zu ProxMenux beitragen", + "ogDescription": "Branch-Modell, Arbeitsablauf, Autorenangabe im Skript-Header und zweiphasige UI-Richtlinie für Mitwirkende." + }, + "header": { + "title": "Zu ProxMenux beitragen", + "description": "ProxMenux ist offen für Community-Beiträge - neue Skripte, Fixes für bestehende, Dialogverbesserungen, Übersetzungen, Integrationen. Diese Seite ist die formale Tür: wie das Repository strukturiert ist, das Verzweigungsmodell und der Pull-Request-Workflow, der Skript-Header mit Autorzuweisung und optionalem Sponsor-Link und die Design-Konventionen, denen jeder Beitrag folgen muss.", + "section": "Über" + }, + "twoPagesCallout": { + "title": "Zwei verwandte Seiten, ein Projekt", + "body": "Verwechseln Sie diese Seite nicht mit Mitwirkende, die bisherige Beiträge von Testern, Reviewern und Entwicklern würdigt. Diese Seite richtet sich an Sie als künftigen Mitwirkenden und erklärt, wie Sie einen Pull Request zur Aufnahme vorbereiten." + }, + "branching": { + "heading": "Verzweigungsmodell", + "intro": "ProxMenux verwendet ein dreistufiges Verzweigungsmodell:", + "headerBranch": "Branch", + "headerPurpose": "Zweck", + "rows": [ + { + "branch": "main", + "purposeRich": "Stabiler, produktionsreifer Code. Hier werden nur veröffentlichungsreife Änderungen aufgenommen. Diese Version erhalten Endnutzer bei einer normalen ProxMenux-Installation." + }, + { + "branch": "develop", + "purposeRich": "Aktiver Entwicklungsbranch. Neue Arbeiten werden zuerst hier aufgenommen; dies ist der Beta-Kanal. Ist eine stabile Version bereit, wird sie von hier nach main übernommen." + }, + { + "branch": "feature/*", + "purposeRich": "Kurzlebige Branchs pro Feature, Fix oder Verbesserung. Erstellt aus develop, nach Überprüfung über einen Pull Request wieder in develop zusammengeführt." + } + ], + "calloutTitle": "Was das für die Nutzer bedeutet", + "calloutBody": "Endbenutzer, die main verfolgen, erhalten getestete Releases. Benutzer, die die neuesten Funktionen frühzeitig wünschen - und gelegentliche raue Kanten nicht stören - können develop folgen. Neue Funktionen passieren immer zuerst develop; nichts erreicht main, ohne diesen Zyklus zu durchlaufen." + }, + "workflow": { + "heading": "Pull Request Workflow", + "intro": "Von der Idee zum fusionierten Release in fünf Schritten:", + "step1Lead": "Erstellen Sie einen Feature Branch aus develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Verwenden Sie einen beschreibenden Zweignamen: feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Arbeiten Sie an Ihren Änderungen und schieben Sie den Branch: ", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Öffnen Sie einen Pull Request für develop, nicht für main. Prüfen Sie auf GitHub, dass der Basisbranch auf develop steht. Beschreiben Sie, was das Skript tut und ändert und auf welchen Proxmox-VE-Versionen es getestet wurde.", + "step4": "Nach der Prüfung werden Änderungen in develop zusammengeführt. Ein Maintainer prüft Codequalität, Header-Konventionen und die zweiphasige UI-Richtlinie. Falls Änderungen nötig sind, pushen Sie weitere Commits in denselben Branch; der PR aktualisiert sich automatisch.", + "step5": "Stabile Versionen werden von develop nach main übernommen. Bei der Veröffentlichung wird main per Fast-Forward auf develop gebracht und mit einem Tag versehen. Ihr Beitrag wird Teil der nächsten stabilen Version." + }, + "scriptHeader": { + "heading": "Skript-Header — Metadaten und Beschreibung", + "intro": "Jedes Skript in ProxMenux wird mit zwei benachbarten Kommentarblöcken geöffnet, die zusammen den Header bilden. Sie sind beide erforderlich - zusammen lassen sie jeden Leser wissen, wer das Skript geschrieben hat und was es tut, alles ohne den Code selbst zu öffnen.", + "bullets": [ + "Oberer Block: Metadaten. Autor, optionale GitHub-/Sponsoring-Links, Maintainer, Copyright, Lizenz, Version und Aktualisierungsdatum. Hier erfolgt auch die Würdigung der Mitwirkenden: Bei einem neuen Skript tragen Sie Ihren Namen und optional Links zur persönlichen GitHub-Seite und einem Unterstützungsprofil ein, etwa Ko-fi, GitHub Sponsors oder Buy Me a Coffee.", + "Unterer Block: Beschreibung. Ein kurzer Absatz in einfachem Englisch erklärt die Funktion. Nutzer lesen ihn vor dem Code. Er muss für sich allein Zweck, Hauptaktionen, betroffene Ressourcen und Voraussetzungen verständlich machen." + ], + "licenseCalloutTitle": "Die Lizenzlinie ist festgelegt — GPL-3.0", + "licenseCalloutBody": "ProxMenux wird unter der GNU General Public License v3.0 veröffentlicht. Jedes Skript im Projekt wird unter derselben Lizenz ausgeliefert; die License-Zeile im Header ist immer die GPL-3.0-Referenz, die im folgenden Beispiel gezeigt wird - es ist keine Per-Script-Wahl. Indem Sie ein Skript beisteuern, erklären Sie sich damit einverstanden, es unter GPL-3.0 zu veröffentlichen, was bedeutet, dass jeder es lesen, ändern und weiterverteilen kann (einschließlich Änderungen), solange er es unter der gleichen Lizenz behält. Der vollständige Text befindet sich unter MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "Die Linien GitHub und Sponsor sind optional - lassen Sie sie aus, wenn Sie sie nicht veröffentlichen möchten. Alles andere ist erforderlich.", + "whyCalloutTitle": "Warum das wichtig ist", + "whyCalloutBody": "Open-Source-Beiträge sind freiwillige Arbeit. Name und optionaler Sponsoring-Link im Quellcode machen die Urheberschaft bei jedem Lesen sichtbar, statt sie hinter dem Projektnamen zu verbergen. Der Beschreibungsblock ist aus einem anderen Grund wichtig: Er ist der erste Anlaufpunkt künftiger Maintainer und neugieriger Nutzer. Eine klare Erklärung entscheidet oft darüber, ob sie einem Skript vertrauen oder es meiden." + }, + "structure": { + "heading": "Projektstruktur", + "intro": "Wo jede Art von Skript im Repository lebt:", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Jedes Skript lädt utils.sh beim Start per source, um Nachrichtenfunktionen, Spinner, Farbvariablen und Übersetzungssystem einzubinden. Gemeinsame Hilfsbibliotheken in scripts/global/ werden ausdrücklich von den Skripten geladen, die sie benötigen." + }, + "twoPhase": { + "heading": "Die zweiphasige UI Design Policy", + "intro": "Dies ist die wichtigste Konvention in ProxMenux und die PRs werden am häufigsten gebeten, sie zu beheben. Jedes Skript ist in genau zwei Phasen unterteilt:", + "headerPhase": "Phase", + "headerPurpose": "Zweck", + "headerScreen": "Bildschirmzustand", + "rows": [ + { + "phaseRich": "Phase 1 — Auswahl", + "purposeRich": "Sammeln Sie jede Benutzerentscheidung. Führen Sie alle erforderlichen Vorbereitungsarbeiten durch (Sonden, Scans, Überprüfungen).", + "screenRich": "dialog-Overlays. Wenn eine Suche zwischen zwei Menüs länger als ein oder zwei Sekunden dauert, zeigen Sie einen msg_info-Spinner an, damit der Benutzer weiß, dass das Skript nicht eingefroren ist, und rufen Sie dann stop_spinner direkt vor dem nächsten Dialog auf." + }, + { + "phaseRich": "Phase 2 — Ausführung", + "purposeRich": "Führen Sie jede Operation aus. Zeigen Sie den gesamten Fortschrittsverlauf an, der sich auf dem Bildschirm ansammelt.", + "screenRich": "Sichtbare msg_info / msg_ok Nachrichten; niemals dialog." + } + ], + "principle": "Das Prinzip: Sammeln Sie zuerst alles und führen Sie dann alles aus. Der Benutzer sieht ein sauberes dialoggesteuertes Menü (Phase 1), dann eine saubere Ausführungsansicht im Protokollstil (Phase 2). Kein Mischen — kein Dialog erscheint mitten in der Ausführung, kein Fortschrittsrauschen während der Auswahl.", + "phase1Heading": "Phase 1 — typisches Muster", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Regeln für Phase 1:", + "phase1Rules": [ + "Läuft gerade ein msg_info-Spinner und soll ein Menü mit dialog oder whiptail geöffnet werden, rufen Sie zuerst stop_spinner auf. Der Spinner verträgt sich nicht mit deren Bildschirmüberlagerung. Ohne aktiven Spinner ist der Aufruf nicht nötig.", + "Verwenden Sie show_proxmenux_logo + msg_title + msg_info, um in Phase 1 einen längeren Vorgang sichtbar einzuordnen, etwa eine Prüfung ab fünf Sekunden. Die Funktion leert bereits den Bildschirm; rufen Sie davor nicht clear auf.", + "Rufen Sie show_proxmenux_logo nicht zwischen Dialogmenüs auf, in denen nichts angezeigt werden kann - das Löschen des Bildschirms für ein leeres Terminal ist nur visuelles Rauschen.", + "Speichern Sie alle Entscheidungen und Sondenergebnisse in Variablen oder parallelen Arrays. Die sichtbare Zusammenfassung erfolgt zu Beginn der Phase 2, nicht in Phase 1." + ], + "phase2Heading": "Phase 2 — typisches Muster", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Regeln für Phase 2: Beginnen Sie mit show_proxmenux_logo + msg_title und fassen Sie Phase 1 sofort in msg_ok-Zeilen zusammen. Rufen Sie show_proxmenux_logo nicht erneut auf, da dies den bisherigen Verlauf löscht. Verwenden Sie kein dialog; ist eine Entscheidung zur Laufzeit unvermeidbar, nutzen Sie whiptail wie im nächsten Abschnitt." + }, + "dialogVsWhiptail": { + "headingRich": "Wann dialog vs whiptail zu verwenden", + "intro": "Beide Tools zeichnen Benutzeroberflächen im Textmodus, aber sie verhalten sich auf dem Bildschirm sehr unterschiedlich - und ProxMenux verwendet sie aus einem bestimmten Grund in zwei verschiedenen Phasen.", + "headerTool": "Werkzeug", + "headerWhen": "Wann man es benutzt", + "headerEffect": "Wirkung auf den Bildschirm", + "rows": [ + { + "toolRich": "dialog", + "whenRich": "Immer in Phase 1. Jede interaktive Auswahl – Auswahl einer VM, einer Festplatte, eines Dateisystems, das eine Aktion bestätigt – verwendet dialog. Dies ist das Standard-UI-Tool von ProxMenux.", + "effectRich": "Übernimmt das Terminal: löscht den Bildschirm, zieht sein Overlay und bringt das Terminal beim Schließen in seinen vorherigen Zustand zurück. Fein in Phase 1, weil sich noch nichts Nützliches zeigt." + }, + { + "toolRich": "whiptail", + "whenRich": "Nur in Phase 2, nur wenn unvermeidlich. Der typische Fall ist eine Neustartaufforderung am Ende der Ausführung. Greifen Sie nicht zu whiptail, um \"Weiter?\" mitten in der Ausführung zu fragen - diese Entscheidung hätte in Phase 1 getroffen werden sollen.", + "effectRich": "Leichteres Overlay, das den Terminalkontext nicht löscht. Der akkumulierte msg_ok / msg_info-Verlauf der Phase 2 bleibt hinter dem Dialogfeld sichtbar. Deshalb ist es die richtige Wahl, wenn der Fortschritt bereits auf dem Bildschirm zu sehen ist." + } + ], + "calloutTitle": "Warum die Trennung wichtig ist", + "calloutBody": "Wenn Sie dialog in der Mitte der Phase 2 anrufen, verschwindet jede msg_ok-Zeile, die der Benutzer beobachtet hat. Sie wischen den Audit-Trail. whiptail vermeidet das. Die Regel ist also nicht willkürlich - es geht darum, die Sicht des Benutzers auf das, was das Skript bisher getan hat, zu bewahren.", + "rebootHeading": "Neustartabfrage: das typische whiptail in Phase 2", + "rebootIntro": "Wenn ein Skript endet und ein Neustart erforderlich ist (z.B.) IOMMU aktiviert, Kernelparameter geändert), die Aufforderung am Ende der Phase 2 verwendet whiptail. Fügen Sie immer einen \"Nein\"-Zweig hinzu, der den Benutzer warnt, die betroffene Ressource nicht zu verwenden, bis er neu startet:", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Referenz der Nachrichtenfunktionen", + "intro": "Alle definiert in utils.sh. Verwenden Sie sie als Standard für jede benutzersichtliche Ausgabe - konsistente Visuals über Skripte hinweg ist der springende Punkt. Wenn Ihr Skript eine neue Funktion benötigt, die nicht zum vorhandenen Set passt (ein neuer Schweregrad, ein neuer Layout-Helfer usw.), schlagen Sie es in Ihrem Pull Request vor - es wird überprüft und zu utils.sh hinzugefügt, wenn es allgemein nützlich ist.", + "headerFunction": "Funktion", + "headerWhen": "Wann zu verwenden", + "headerSpinner": "Fortschrittsanzeige", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Operation in Arbeit.", + "spinner": "Startet" + }, + { + "function": "stop_spinner", + "whenRich": "Ende der stillen Vorbereitungsarbeiten in Phase 1 — tötet Spinner, löscht die Linie.", + "spinner": "Stoppt" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "Vorgang erfolgreich. Auch für „Funktion aktiviert“ verwenden, wenn noch ein Neustart erforderlich ist.", + "spinner": "Stoppt" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Tatsächliche Warnung oder gestörter Zustand.", + "spinner": "Stoppt" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Schwerwiegender Fehler.", + "spinner": "Stoppt" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Nicht blockierender Hinweis (türkisfarbene Infozeile).", + "spinner": "Stoppt" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Abschließende Aufforderung „Enter zum Zurückkehren“ am Ende von Phase 2.", + "spinner": "Stoppt" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Fetter Titel mit eingebautem Abstand. Wird zu Beginn der Phase 2 verwendet.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Leert den Bildschirm und zeigt das Logo. Zu Beginn von Phase 2 einmal aufrufen.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "Dialogkonventionen", + "bullets": [ + "Geben Sie --backtitle \"$BACKTITLE\" immer an jeden dialog- und whiptail-Aufruf weiter. $BACKTITLE ist immer \"ProxMenux\" - einmal im Skript-Header gesetzt und nie außer Kraft gesetzt. Der Benutzer muss den Projektnamen immer als Framing-Kontext sehen, niemals den eigenen Titel des Skripts.", + "Verpacken Sie Titel und Nachrichten immer mit $(translate \"...\").", + "Immer dialog Ausgabe mit 2>&1 >/dev/tty umleiten — die erfasste stdout wird die Auswahl des Benutzers, während der Dialog selbst auf das Terminal zieht.", + "Verwenden Sie die Standard-UI-Dimensionsvariablen ($UI_MENU_H, $UI_MSG_W, usw.) für eine konsistente Dimensionierung über Skripte hinweg.", + "Prüfen Sie immer auf leere oder abgebrochene Auswahlen und behandeln Sie diese sauber." + ], + "exampleIntro": "Vollständiges Beispiel — Erstellung eines VM-Auswahlmenüs und Bearbeitung der Stornierung:", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Übersetzungspolitik", + "intro": "Alle vom Benutzer sichtbaren Zeichenfolgen müssen mit der translate-Funktion gewickelt sein. ProxMenux übersetzt sie automatisch in alle unterstützten Sprachen - Sie schreiben Englisch, der Benutzer liest seine Muttersprache.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Schreibe Strings in Englisch – die Übersetzung wird automatisch vom Build bearbeitet.", + "Halten Sie Strings kurz. Vermeiden Sie das Einbetten von Variablen in lange Sätze, wenn möglich.", + "Variablennamen, Pfade oder technische Bezeichner nichtübersetzen." + ] + }, + "variableStyle": { + "heading": "Variable und Stilkonventionen", + "bullets": [ + "Verwenden Sie UPPER_CASE für Variablen auf Skriptebene.", + "Verwenden Sie lower_case für lokale Funktionsvariablen (mit local deklarieren).", + "Setzen Sie alle Variablenexpansionen in Anführungszeichen: \"$VAR\" statt $VAR.", + "Verwenden Sie [[ ]] für Konditionale, nicht [ ] (außer wenn POSIX erforderlich ist).", + "show_proxmenux_logo ist der geeignete Weg, um den Bildschirm zu löschen - er enthält das Clear und zeigt das Projektlogo, so dass der Benutzer immer einen visuellen Kontext hat. Rufen Sie es einmal zu Beginn der Phase 2 (und optional vor einem langen Phase 1-Spinnerblock) auf." + ], + "standardNamesIntro": "Standardvariablennamen im gesamten Projekt:", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Umleitung der Werkzeugausgabe während der Phase 2", + "redirectIntro": "Phase 2 zeigt ein sauberes Protokoll der msg_info → msg_ok-Zeilen an, die sich auf dem Bildschirm ansammeln. Wenn ein Tool, das Sie aufrufen (apt, mkfs, qm, pct, dd, etc.), seine eigene Ausgabe in stdout/stderr schreibt, scrollt es an Ihren Nachrichten vorbei und unterbricht den visuellen Fluss - Sie haben ein chaotisches Terminal, in dem der Benutzer die eigenen Fortschrittslinien des Skripts nicht vom Rauschen des zugrunde liegenden Tools unterscheiden kann.", + "withoutRedirectIntro": "Ohne Umleitung: die Ausgabe für Nutzer, wenn zusätzliche Werkzeugausgaben nicht abgefangen werden.", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": "Mit Umleitung: dieselbe Aktion, wenn zusätzliche Ausgaben an anderer Stelle landen.", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Zwei Muster zur Auswahl:", + "discardLead": "Ausgabe verwerfen, wenn sie nicht benötigt wird: die einfachste und schnellste Variante.", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Ausgabe in eine Protokolldatei schreiben, wenn Sie sie später prüfen möchten, etwa zur Fehlersuche bei einer Installation oder um dpkg-Aktionen nachzuvollziehen. Dies ist das bevorzugte Muster für apt-Aufrufe:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "scripts/global/update-pve9_2.sh dient als Referenz: Jeder apt-get-Aufruf schreibt in eine Protokolldatei. Nutzer sehen nur den Ablauf msg_info → msg_ok, während die Datei bei Fehlern genau zeigt, was apt getan hat." + }, + "dosAndDonts": { + "heading": "Do's und Don'ts", + "doHeading": "✅ Empfohlen", + "doBullets": [ + "stop_spinner vor einem dialog in Phase 1 nur dann, wenn ein msg_info Spinner gerade läuft.", + "Phase 2 beginnt mit show_proxmenux_logo + msg_title + msg_ok als Zusammenfassung der Ergebnisse von Phase 1.", + "Verwenden Sie msg_ok für erfolgreich aktivierte Funktionen – auch wenn ein Neustart erforderlich ist.", + "Verwenden Sie whiptail (nicht dialog) für jede Aufforderung nach der Ausführung, die in Phase 2 erscheinen muss.", + "Fügen Sie immer einen \"Nein\" -Zweig in Reboot-Dialoge ein, der den Benutzer warnt, die betroffene Ressource bis zum Neustart nicht zu starten.", + "Schützen Sie die reine VM-Logik, indem Sie [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]] überprüfen - Controller und NVMe PCIe können nicht zu LXC-Containern hinzugefügt werden.", + "Verwenden Sie ensure_repositories von utils-install-functions.sh anstelle von bedingungslosem apt-get update.", + "Verwenden Sie parallele Arrays in Phase 1, wenn jedes Element mehrere Dialoge benötigt." + ], + "dontHeading": "❌ Vermeiden", + "dontBullets": [ + "Rufen Sie dialog an, während ein Spinner aktiv ist.", + "Überspringen Sie die Phase 1 Zusammenfassung zu Beginn der Phase 2.", + "Rufen Sie show_proxmenux_logo ein zweites Mal an - es löscht alles, was Phase 2 gedruckt hat.", + "Verwenden Sie dialog in Phase 2 (verwenden Sie whiptail für den seltenen unvermeidlichen Fall).", + "Verwenden Sie reine clear.", + "msg_title mit Leerzeilen aus echo \"\" umgeben; die Abstände sind bereits enthalten.", + "Verwenden Sie msg_warn, um eine erfolgreich aktivierte Funktion zu melden - das ist ein msg_ok.", + "Führen Sie bedingungslos apt-get update aus - verwenden Sie ensure_repositories." + ] + }, + "submitting": { + "heading": "Einreichen Ihres Beitrags", + "steps": [ + "Fork MacRimi/ProxMenux auf GitHub und klonen Sie Ihre Fork.", + "Erstellen Sie einen feature/*-Zweig aus develop (siehe Branching-Modell oben).", + "Befolgen Sie diese Anleitung für neue oder geänderte Skripte. Schreiben Sie den Header mit Ihrem Namen; fügen Sie die optionale Sponsorenzeile hinzu, wenn Sie möchten.", + "Testen Sie Ihr Skript auf einer echten Proxmox VE-Instanz - sowohl Phase 1 (jeder Dialogzweig) als auch Phase 2 (jede Operation ist erfolgreich und wird bei Fehlern sauber zurückgesetzt).", + "Öffnen Sie eine Pull Request gegen develop mit einer klaren Beschreibung: was das Skript macht, was sich geändert hat, auf welcher Proxmox VE-Version es getestet wurde.", + "Stellen Sie sicher, dass Ihr Beitrag den Code of Conduct respektiert." + ], + "securityOutro": "Befolgen Sie bei sicherheitsrelevanten Problemen den Offenlegungsfluss in SECURITY.md, anstatt ein öffentliches Problem zu öffnen." + }, + "whereNext": { + "heading": "Wohin als nächstes", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (vollständige Anleitung)", + "tail": "— die Quelle der Wahrheit für jede Konvention, einschließlich der fortgeschrittenen Hardware-Muster." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Beitragende", + "tail": " — die Menschen, deren Arbeit ProxMenux-Versionen geprägt hat. Eine andere Seite als diese hier." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Verhaltenskodex", + "tail": "- die Standards, die jeder Mitwirkende zu befolgen bereit ist." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": "Fragen Sie, bevor Sie bauen, ob Sie nicht sicher sind, ob eine Idee passt, oder um Mitarbeiter für ein größeres Feature zu finden." + } + ] + } +} diff --git a/web/messages/de/docs/about/contributors.json b/web/messages/de/docs/about/contributors.json new file mode 100644 index 00000000..40718e21 --- /dev/null +++ b/web/messages/de/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Mitwirkende | ProxMenux-Dokumentation", + "description": "Anerkennung für die Menschen, die ProxMenux möglich machen: Tester, Reviewer und Entwickler, die jede Version mitgestalten.", + "ogTitle": "Mitwirkende | ProxMenux-Dokumentation", + "ogDescription": "Treffen Sie die Mitwirkenden hinter ProxMenux." + }, + "header": { + "title": "Mitwirkende", + "description": "Das ProxMenux-Projekt wächst dank seiner Mitwirkenden. Diese Seite würdigt ihre Arbeit: Versionen vor der Veröffentlichung testen, Regressionen erkennen, PRs prüfen und die Entwicklung neuer Funktionen mitgestalten.", + "section": "Über" + }, + "beyond": { + "title": "Weitere Mitwirkende", + "body": "Viele weitere Menschen helfen mit Fehlermeldungen, Funktionsideen in Discussions, Beta-Tests und Übersetzungen. Die vollständige Liste der Code-Mitwirkenden steht im GitHub-Mitwirkendendiagramm. Diese Seite hebt diejenigen hervor, die besonders aktiv ProxMenux-Versionen testen und prüfen." + }, + "testers": { + "heading": "Tester & Reviewer", + "intro": "Diese Personen gehörten zu den ersten Mitwirkenden. Mit Tests, Fehlermeldungen und Verbesserungsvorschlägen halfen sie ProxMenux dabei, zu dem zu werden, was es heute ist.", + "roles": { + "testing": "Testen", + "testingReviewer": "Tests und Review" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Möchten Sie dazu beitragen?", + "intro": "Jeder Beitrag ist willkommen - Code, Testen, Design, Dokumentation oder einfach nur das Teilen Ihrer Ideen:", + "tester": "Tester — probieren Sie den Beta-Kanal aus und melden Sie vor der stabilen Veröffentlichung, was funktioniert und was nicht.", + "developer": "Entwickler — Bash, Next.js (TypeScript) und etwas Python für die virtuelle Übersetzungsumgebung (venv). PRs auf GitHub sind willkommen.", + "designer": "Designer — Verbesserungen der Benutzeroberfläche, Icons, Screenshots zur Dokumentation.", + "ideas": "Ideen — eröffnen Sie zuerst ein Thema in GitHub Discussions, bevor Sie einen Funktionswunsch als Issue einreichen." + }, + "coc": { + "title": "Lesen Sie zuerst den Verhaltenskodex", + "body": "Lesen Sie vor dem Einreichen von Code oder Fehlermeldungen kurz den Verhaltenskodex und die bewährten Vorgehensweisen. Darin werden die Verantwortung für Code, die Meldung von Sicherheitslücken und der Umfang von PRs erläutert." + } +} diff --git a/web/messages/de/docs/about/faq.json b/web/messages/de/docs/about/faq.json new file mode 100644 index 00000000..579a8de6 --- /dev/null +++ b/web/messages/de/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Häufige Fragen | ProxMenux-Dokumentation", + "description": "Häufige Fragen zu ProxMenux: Funktionsumfang, Installation, Kompatibilität ab PVE 8, Anpassung, Updates, Sicherheit, Produktionseinsatz, Systemdateien, Beiträge und Deinstallation.", + "ogTitle": "Häufige Fragen | ProxMenux-Dokumentation", + "ogDescription": "Häufige Fragen zu Installation, Kompatibilität, Sicherheit und Beiträgen zu ProxMenux." + }, + "header": { + "title": "Häufig gestellte Fragen", + "description": "Häufige Fragen zu ProxMenux: Funktionsumfang, Zielgruppe, Installation, Updates, Deinstallation, Kompatibilität, Anpassung, Sicherheit und Beiträge. Fehlt Ihre Frage, sehen Sie in GitHub Discussions nach oder erstellen Sie ein Issue.", + "section": "Über" + }, + "quickLinks": { + "title": "Schnelle Links", + "installationLabel": "Installation", + "installationSuffix": "— der One-Line-Installationsbefehl + Abhängigkeiten.", + "introductionLabel": "Einleitung", + "introductionSuffix": "— was ProxMenux tut, in 5 Minuten.", + "uninstallLabel": "Deinstallieren", + "uninstallSuffix": "— sauberes Entfernen.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Diskussionen" + }, + "q1": { + "question": "Was ist ProxMenux und wofür wird es verwendet?", + "p1Rich": "ProxMenux ist ein interaktives menügesteuertes Tool, das Proxmox VE für alle Benutzer unabhängig von ihrer technischen Erfahrung zugänglich macht. Es vereinfacht die Befehlsausführung und ermöglicht es Benutzern, Aktionen auf ihrem System durchzuführen, ohne dass fortgeschrittene Linux-Kenntnisse erforderlich sind.", + "p2": "Weniger erfahrene Benutzer können Befehle über übersichtliche Menüs ausführen und müssen seltener selbst im Terminal tippen. Erfahrenen Administratoren erspart ProxMenux Eingaben bei wiederkehrenden Aufgaben wie der Absicherung nach der Installation, GPU-Passthrough, OVF-VM-Importen und der Rotation von ZFS-Snapshots.", + "p3": "Proxmox VE ist weit verbreitet für:", + "items": [ + "Virtualisierung auf Unternehmensebene", + "HomeLab und persönliche Cloud-Lösungen", + "Multimedia-Server, Automatisierung und mehr" + ] + }, + "q2": { + "question": "Wie installiere ich ProxMenux?", + "p1Rich": "Folgen Sie der Installationsanleitung. Kurz zusammengefasst:", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "Einmal installiert, starten Sie es einfach mit:", + "menuCmd": "menu" + }, + "q3": { + "question": "Ist ProxMenux mit allen Proxmox-Versionen kompatibel?", + "bodyRich": "Nein — ProxMenux benötigt Proxmox VE 8 oder neuer. PVE 7 und ältere Versionen werden nicht unterstützt. Aktualisieren Sie PVE 7 zunächst nach der offiziellen Proxmox-Anleitung auf 8.x. Den anschließenden Wechsel von 8 auf 9 können Sie über Werkzeuge → PVE 8 auf PVE 9 aktualisieren in ProxMenux durchführen." + }, + "q4": { + "question": "Kann ich ProxMenux anpassen?", + "p1Rich": "Lokale Änderungen an den Hauptskripten können beim Aktualisieren überschrieben werden. Bearbeiten Sie diese Dateien daher möglichst nicht direkt. Folgendes können Sie anpassen:", + "items": [ + "Das Konsolenbanner über das FastFetch Tool in den Post-Install-Optionen.", + "Welche optionalen Komponenten installiert werden (Monitor, Fail2Ban, Lynis...) – wählen Sie während der Installation oder später über das entsprechende Menü aus." + ] + }, + "q5": { + "question": "Wie aktualisiere ich ProxMenux?", + "p1Rich": "Wenn eine neue Version verfügbar ist, erkennt ProxMenux sie beim nächsten Aufruf von menu und bietet ein Update an. Stimmen Sie zu, werden die Dateien und die Konfiguration des Werkzeugs in der bestehenden Installation ersetzt. Ein manueller Download ist nicht nötig.", + "p2Rich": "Benutzer der stabilen Version erhalten stabile Updates, Beta-Benutzer die Beta-Updates. Sobald eine stabile Version erscheint, informiert die Beta beim nächsten Menüstart darüber und bietet den automatischen Wechsel an. Weitere Informationen finden Sie im Beta-Programm." + }, + "q6": { + "question": "Wo kann ich Probleme melden?", + "p1Rich": "Melden Sie Fehler über GitHub Issues. Beschreiben Sie die Schritte zum Reproduzieren, den Fehlertext und gegebenenfalls die Monitor-Ausgabe von journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "Bei Sicherheitsproblemen: Veröffentlichen Sie keine Details. Folgen Sie dem Verfahren zur verantwortungsvollen Meldung im Verhaltenskodex und den bewährten Vorgehensweisen." + }, + "q7": { + "question": "Kann ich zu ProxMenux beitragen?", + "p1Rich": "Unbedingt. ProxMenux ist ein gemeinschaftliches Open-Source-Projekt. Beiträge sind als Code (Bash, Next.js/TypeScript und Python-Übersetzungswerkzeuge), Tests, Dokumentation, Design oder Ideen willkommen.", + "item1Rich": "Besprechen Sie Ideen in GitHub Discussions, bevor Sie eine Feature Request öffnen.", + "item2Rich": "Lesen Sie vor einem Pull Request den Verhaltenskodex und die bewährten Vorgehensweisen.", + "item3Rich": "Auf der Mitwirkendenseite finden Sie die aktuellen Tester und Reviewer." + }, + "q8": { + "question": "Ändert ProxMenux kritische Systemdateien?", + "p1Rich": "Bei der Installation von ProxMenux werden folgende Komponenten hinzugefügt.", + "item1Rich": "Abhängigkeiten über apt: dialog, curl, jq und git. Die Übersetzungen verwenden vorgefertigte JSON-Kataloge. Der Installer legt auf dem Host keine virtuelle Python-Umgebung für Übersetzungen an.", + "item2Rich": "Der Skriptbaum bei /usr/local/share/proxmenux/.", + "item3Rich": "Das Startskript unter /usr/local/bin/menu.", + "item4Rich": "Optional: der systemd-Dienst von Monitor unter proxmenux-monitor.service.", + "p2Rich": "ProxMenux verändert die zentrale Proxmox-Konfiguration nicht, es sei denn, Sie fordern dies ausdrücklich über ein entsprechendes Menü an. Die Netzwerkreparatur bearbeitet beispielsweise /etc/network/interfaces, jedoch erst nach Ihrer Bestätigung und dem Anlegen einer Sicherung." + }, + "q9": { + "question": "Ist es sicher, ProxMenux in der Produktion zu verwenden?", + "bodyRich": "Ja. Da ProxMenux die zentralen Proxmox-Dateien nur auf ausdrücklichen Wunsch verändert, kann es in Produktionsumgebungen eingesetzt werden. Testen Sie automatisierte Abläufe zunächst in einer kontrollierten Umgebung und erstellen Sie Sicherungen, bevor Sie destruktive Vorgänge ausführen, etwa das Upgrade von PVE 8 auf 9, das Formatieren eines Datenträgers oder das Löschen einer VM." + }, + "q10": { + "question": "Wie deinstalliere ich ProxMenux?", + "p1Rich": "In ProxMenux: Einstellungen → ProxMenux deinstallieren. Die Deinstallation entfernt den Skriptbaum, das Startskript und den Monitor-Dienst sowie auf Wunsch ausgewählte Abhängigkeiten. /root/.bashrc und /etc/motd werden aus den bei der Installation angelegten Sicherungen wiederhergestellt, sofern diese vorhanden sind.", + "p2Rich": "Die vollständige Anleitung finden Sie unter ProxMenux deinstallieren." + }, + "q11": { + "question": "VirusTotal markiert die Installations-URL - ist es sicher?", + "p1Rich": "Der in Issue #162 gemeldete Treffer von Chong Lua Dao bei 1 von 95 Prüfungen wurde vom Maintainer als Fehlalarm eingestuft. Der Installer verwendet das Muster curl | bash und kann jq aus dessen offizieller GitHub-Veröffentlichung herunterladen. Dieses Verhalten kann heuristische Scanner auslösen. Der damalige Bericht ist keine Bewertung aller künftigen Downloads.", + "p2Rich": "ProxMenux ist vollständig quelloffen. Sie können das Installationsskript Zeile für Zeile prüfen: install_proxmenux.sh. Weitere Hintergründe finden Sie in Issue #162." + } +} diff --git a/web/messages/de/docs/about/index.json b/web/messages/de/docs/about/index.json new file mode 100644 index 00000000..335ab2d4 --- /dev/null +++ b/web/messages/de/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "Über das Projekt | ProxMenux-Dokumentation", + "description": "Über ProxMenux: Projektziele, Mitwirkende, FAQ und Verhaltenskodex. Open-Source-Menümanagement-Tool für Proxmox VE 8+, erstellt und gepflegt von MacRimi mit einem kleinen Team von Testern und Mitwirkenden.", + "ogTitle": "Über das Projekt | ProxMenux-Dokumentation", + "ogDescription": "Über ProxMenux — ein menügesteuertes Open-Source-Werkzeug zur Verwaltung von Proxmox VE 8+." + }, + "header": { + "title": "Über", + "description": "Projektinformationen, FAQ, Mitwirkende und Verhaltenskodex. ProxMenux ist ein Open-Source-Menümanagement-Tool für Proxmox VE 8+, das von MacRimi mit Hilfe eines kleinen Teams von Testern und Mitwirkenden entwickelt und gewartet wird.", + "section": "Über" + }, + "callout": { + "title": "Open Source, von der Community getragen", + "body": "ProxMenux steht unter der GPL-3.0-Lizenz und wird öffentlich auf GitHub entwickelt. Jedes Skript lässt sich prüfen, jede Änderung nachvollziehen. Beiträge in Form von Fehlermeldungen, Ideen, Code und Tests sind willkommen." + }, + "section": { + "heading": "In diesem Abschnitt", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Häufig gestellte Fragen: Was ist ProxMenux, Installation, Kompatibilität, Anpassung, Updates, Sicherheit, Produktionsnutzung, Deinstallation." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Mitwirkende", + "description": "Anerkennung für die Menschen, die ProxMenux möglich machen: Tester, Reviewer und Entwickler, die jede Version mitgestalten." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Verhaltenskodex", + "description": "Projektwerte, Verantwortung für eingereichten Code und Beitragsrichtlinien. Pflichtlektüre vor dem Einreichen eines PR oder einer Fehlermeldung." + } + ] + }, + "involved": { + "heading": "Beteiligen Sie sich", + "intro": "Drei Orte, um sich mit dem Projekt zu beschäftigen:", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Einen Bug melden", + "description": "GitHub Issues — fügen Sie Protokolle und Schritte zum Nachstellen bei." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Schlagen Sie ein Feature vor", + "description": "GitHub Discussions — teilen Sie Ideen, bevor Sie eine PR öffnen." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Einen PR einreichen", + "description": "GitHub Pull Requests – lesen Sie zuerst den Verhaltenskodex." + } + ] + }, + "support": { + "heading": "Unterstützung des Projekts", + "introRich": "Wenn ProxMenux Ihnen Zeit spart, ist der einfachste Weg, anderen zu helfen, es zu entdecken, dem Repo ein ⭐ auf GitHub zu geben. Wenn Sie weiter gehen wollen:", + "kofiLabel": "Auf Ko-fi unterstützen", + "kofiOutro": "- kauft dem Betreuer einen Kaffee und hält die Entwicklung am Laufen." + } +} diff --git a/web/messages/en/docs/about/code-of-conduct.json b/web/messages/en/docs/about/code-of-conduct.json index e8923f6a..6eefef4b 100644 --- a/web/messages/en/docs/about/code-of-conduct.json +++ b/web/messages/en/docs/about/code-of-conduct.json @@ -8,5 +8,6 @@ "errors": { "notFound": "Error: CODE_OF_CONDUCT.md file not found.", "loadFailed": "Error: Unable to load the Code of Conduct content." - } + }, + "contentMarkdown": "# 📝 Code of Conduct & Best Practices\n\nThis document defines the **principles and guidelines** for the use of **ProxMenux**, aiming to foster **respect, collaboration, and security** in development.\n\n## 📌 1. Code Attribution & Recognition\n\n- If **scripts, code snippets, or ideas** from other repositories or developers are used, the original author will always be credited. \n- A comment will be added in the code or documentation mentioning the source and linking to the original repository if possible. \n- When using code with a specific **license**, its terms must be respected. \n\n### ✅ Example of proper attribution in code:\n\n```bash\n# Script based on the work of [Author] at [Repository Link]\n```\n\n## 🔒 2. Security & Code Responsibility\n\n- All code in this repository follows secure development best practices.\n- Contributions introducing intentional vulnerabilities or unsafe practices are not allowed.\n- Scripts should never compromise system security or user data.\n- Before implementation, contributions should be reviewed to ensure security and stability.\n\n### ⚠️ Responsible Disclosure of Vulnerabilities\n\nIf you discover a security vulnerability, DO NOT report it in public Issues or forums.\n\nInstead, please report it privately via email:\n\n📧 proxmenux@macrimi.pro\n\nFor detailed information on security vulnerabilities and how to report them, please refer to our [Security Policy](./SECURITY.md).\n\n## 🤝 3. Community Guidelines\n\nTo maintain a respectful and professional environment, all users must follow these guidelines:\n\n- ✅ Be respectful and constructive in discussions.\n- ❌ No harassment, hate speech, or offensive behavior will be tolerated.\n- ✅ Provide feedback professionally, avoiding personal attacks.\n- ❌ Violating these guidelines may result in warnings or bans, depending on severity.\n\n\n##\n\nWith this document, we promote ethical code use, security awareness, and a respectful community.\n\n📢 Let's build something great, together!\n\n" } diff --git a/web/messages/en/docs/about/faq.json b/web/messages/en/docs/about/faq.json index ab16cfa6..e1a33f96 100644 --- a/web/messages/en/docs/about/faq.json +++ b/web/messages/en/docs/about/faq.json @@ -45,7 +45,7 @@ }, "q4": { "question": "Can I customise ProxMenux?", - "p1Rich": "The core scripts can't be modified directly because they're hosted on GitHub and refreshed by the auto-update flow. What you can customise:", + "p1Rich": "Local changes to the core scripts can be overwritten by the update flow, so avoid editing those files directly. What you can customise:", "items": [ "The console banner via the FastFetch tool in the Post-Install options.", "Which optional components are installed (Monitor, Fail2Ban, Lynis…) — pick during install or via the relevant menu later." @@ -63,15 +63,15 @@ }, "q7": { "question": "Can I contribute to ProxMenux?", - "p1Rich": "Absolutely. ProxMenux is open source and collaborative. Contributions can be code (bash, Next.js / TypeScript, the translation Python venv), testing, documentation, design, or just ideas.", + "p1Rich": "Absolutely. ProxMenux is open source and collaborative. Contributions can be code (Bash, Next.js/TypeScript and Python translation tools), testing, documentation, design, or just ideas.", "item1Rich": "Discuss ideas in GitHub Discussions before opening a feature request.", "item2Rich": "For PRs, read the Code of Conduct & Best Practices first.", "item3Rich": "See the Contributors page for current testers and reviewers." }, "q8": { "question": "Does ProxMenux modify critical system files?", - "p1Rich": "No. ProxMenux installs:", - "item1Rich": "Dependencies via apt: dialog, curl, jq, git (and python3 + googletrans on the Translation install).", + "p1Rich": "Installing ProxMenux adds the following components.", + "item1Rich": "Dependencies via apt: dialog, curl, jq and git. Translations use pre-built JSON catalogs; the installer does not add a translation virtualenv to the host.", "item2Rich": "The script tree at /usr/local/share/proxmenux/.", "item3Rich": "The launcher at /usr/local/bin/menu.", "item4Rich": "Optional: the Monitor systemd service at proxmenux-monitor.service.", @@ -83,12 +83,12 @@ }, "q10": { "question": "How do I uninstall ProxMenux?", - "p1Rich": "From inside ProxMenux: Settings → Uninstall ProxMenux. The uninstaller removes the script tree, the launcher and the Monitor service, optionally removes selected dependencies, and restores /root/.bashrc and /etc/motd backups taken at install time.", + "p1Rich": "From inside ProxMenux: Settings → Uninstall ProxMenux. The uninstaller removes the script tree, the launcher and the Monitor service, optionally removes selected dependencies, and restores /root/.bashrc and /etc/motd from install-time backups if those backups exist.", "p2Rich": "Full step-by-step: Uninstall ProxMenux." }, "q11": { "question": "VirusTotal flags the install URL — is it safe?", - "p1Rich": "A 1/95 detection by heuristic engines (e.g. Chong Lua Dao) is a known false positive. The installer uses the standard curl | bash pattern and downloads legitimate binaries (like jq from its official GitHub release), which aggressive scanners flag based on behaviour rather than actual malicious code.", + "p1Rich": "The 1/95 result reported in Issue #162, from Chong Lua Dao, was identified by the maintainer as a false positive. The installer uses the curl | bash pattern and can download jq from its official GitHub release. Such behaviour can trigger heuristic scanners; the historical report is not a verdict on every future download.", "p2Rich": "ProxMenux is 100% open source and the install script is reviewable line by line: install_proxmenux.sh. More context in Issue #162." } } diff --git a/web/messages/es/docs/about/code-of-conduct.json b/web/messages/es/docs/about/code-of-conduct.json index 77757c6a..b1ee353a 100644 --- a/web/messages/es/docs/about/code-of-conduct.json +++ b/web/messages/es/docs/about/code-of-conduct.json @@ -1,12 +1,13 @@ { "meta": { - "title": "Código de conducta | ProxMenux Documentation", + "title": "Código de conducta | Documentación de ProxMenux", "description": "Código de conducta y buenas prácticas de ProxMenux para colaboradores, testers y usuarios.", - "ogTitle": "Código de conducta | ProxMenux Documentation", + "ogTitle": "Código de conducta | Documentación de ProxMenux", "ogDescription": "Expectativas sobre responsabilidad del código, divulgación de problemas de seguridad y alcance de los PRs para el proyecto ProxMenux." }, "errors": { "notFound": "Error: archivo CODE_OF_CONDUCT.md no encontrado.", "loadFailed": "Error: no se pudo cargar el contenido del código de conducta." - } + }, + "contentMarkdown": "# 📝 Código de conducta y buenas prácticas\n\nEste documento define los **principios y las pautas** de uso de **ProxMenux** para promover el **respeto, la colaboración y la seguridad** durante el desarrollo.\n\n## 📌 1. Autoría del código y reconocimiento\n\n- Si se utilizan **scripts, fragmentos de código o ideas** de otros repositorios o desarrolladores, se reconocerá siempre la autoría original.\n- Se añadirá un comentario en el código o la documentación que indique la fuente y, si es posible, enlace al repositorio original.\n- Al utilizar código con una **licencia** concreta, se deben respetar sus condiciones.\n\n### ✅ Ejemplo de reconocimiento de autoría en el código:\n\n```bash\n# Script basado en el trabajo de [Autor] en [Enlace al repositorio]\n```\n\n## 🔒 2. Seguridad y responsabilidad del código\n\n- Todo el código de este repositorio sigue las buenas prácticas de desarrollo seguro.\n- No se permiten contribuciones que introduzcan vulnerabilidades intencionadas o prácticas inseguras.\n- Los scripts nunca deben comprometer la seguridad del sistema ni los datos de los usuarios.\n- Las contribuciones deben revisarse antes de implementarlas para comprobar su seguridad y estabilidad.\n\n### ⚠️ Divulgación responsable de vulnerabilidades\n\nSi descubres una vulnerabilidad de seguridad, NO la notifiques en incidencias o foros públicos.\n\nComunícala en privado por correo electrónico:\n\n📧 proxmenux@macrimi.pro\n\nPara más información sobre las vulnerabilidades y cómo notificarlas, consulta nuestra [política de seguridad](./SECURITY.md).\n\n## 🤝 3. Normas de la comunidad\n\nPara mantener un entorno respetuoso y profesional, todos los usuarios deben seguir estas pautas:\n\n- ✅ Sé respetuoso y constructivo en las discusiones.\n- ❌ No se tolerarán el acoso, el discurso de odio ni las conductas ofensivas.\n- ✅ Aporta comentarios de forma profesional, sin ataques personales.\n- ❌ El incumplimiento puede dar lugar a advertencias o expulsiones, según la gravedad.\n\n##\n\nEste documento promueve un uso ético del código, la conciencia de seguridad y una comunidad respetuosa.\n\n📢 ¡Construyamos algo grande juntos!\n" } diff --git a/web/messages/es/docs/about/contributing.json b/web/messages/es/docs/about/contributing.json index aeaeb889..5ac006ce 100644 --- a/web/messages/es/docs/about/contributing.json +++ b/web/messages/es/docs/about/contributing.json @@ -1,6 +1,6 @@ { "meta": { - "title": "Contribuir a ProxMenux | ProxMenux Documentation", + "title": "Contribuir a ProxMenux | Documentación de ProxMenux", "description": "Cómo contribuir a ProxMenux — modelo de ramas (main / develop / feature/*), flujo de pull requests, plantilla de cabecera de script con atribución de autor y enlace opcional de patrocinio, política de diseño UI en dos fases, funciones de mensajes y el camino desde una rama de función hasta un release estable.", "ogTitle": "Contribuir a ProxMenux", "ogDescription": "Modelo de ramas, flujo de trabajo, atribución en la cabecera del script y política de diseño UI en dos fases que los colaboradores deben seguir." @@ -129,7 +129,7 @@ "intro": "Todas definidas en utils.sh. Úsalas como el valor por defecto para cualquier salida visible para el usuario — visuales coherentes entre scripts es la idea. Si tu script necesita una función nueva que no encaja en el conjunto existente (un nuevo nivel de severidad, un nuevo helper de layout, etc.), propónla en tu Pull Request — se revisará y se añadirá a utils.sh si es útil de forma amplia.", "headerFunction": "Función", "headerWhen": "Cuándo usar", - "headerSpinner": "Spinner", + "headerSpinner": "Indicador de progreso", "rows": [ { "function": "msg_info \"text\"", @@ -163,7 +163,7 @@ }, { "function": "msg_success \"text\"", - "whenRich": "Prompt final \"Press Enter to return\" al final de la fase 2.", + "whenRich": "Mensaje final «Pulsa Intro para volver» al terminar la fase 2.", "spinner": "Para" }, { diff --git a/web/messages/es/docs/about/contributors.json b/web/messages/es/docs/about/contributors.json index 6cf0ca0f..bba0ff22 100644 --- a/web/messages/es/docs/about/contributors.json +++ b/web/messages/es/docs/about/contributors.json @@ -1,32 +1,32 @@ { "meta": { - "title": "Colaboradores | ProxMenux Documentation", - "description": "Reconocimiento a las personas que hacen posible ProxMenux — testers, revisores y desarrolladores que dan forma a cada release.", - "ogTitle": "Colaboradores | ProxMenux Documentation", + "title": "Colaboradores | Documentación de ProxMenux", + "description": "Reconocimiento a las personas que hacen posible ProxMenux: quienes prueban, revisan y desarrollan cada versión.", + "ogTitle": "Colaboradores | Documentación de ProxMenux", "ogDescription": "Conoce a los colaboradores detrás de ProxMenux." }, "header": { "title": "Colaboradores", - "description": "El proyecto ProxMenux crece y prospera gracias a la contribución de sus colaboradores. Este es el merecido reconocimiento a su trabajo — probando builds antes del release, cazando regresiones, revisando PRs y dando forma a la dirección de las nuevas funciones.", + "description": "ProxMenux crece gracias a sus colaboradores. Esta página reconoce su trabajo: probar versiones antes de publicarlas, detectar regresiones, revisar PR y ayudar a orientar las nuevas funciones.", "section": "Acerca de" }, "beyond": { "title": "Más allá de esta lista", - "body": "Mucha más gente contribuye vía reportes de bugs, ideas de funciones en Discussions, pruebas beta y traducciones. La lista completa de colaboradores de código vive en el grafo de colaboradores de GitHub. La página de abajo destaca a las personas que han estado más activamente involucradas en probar y revisar releases de ProxMenux." + "body": "Muchas más personas contribuyen notificando errores, proponiendo funciones en Discussions, probando versiones beta y traduciendo el proyecto. La lista completa de colaboradores del código está en el gráfico de colaboradores de GitHub. Esta página destaca a quienes participan más activamente en las pruebas y la revisión de las versiones de ProxMenux." }, "testers": { - "heading": "Testers y revisores", - "intro": "Estas personas fueron de las primeras en contribuir al proyecto, probando, reportando incidencias y proponiendo mejoras que ayudaron a que ProxMenux haya crecido hasta convertirse en lo que es hoy.", + "heading": "Pruebas y revisión", + "intro": "Estas personas estuvieron entre las primeras en contribuir al proyecto. Sus pruebas, avisos de errores y propuestas de mejora ayudaron a convertir ProxMenux en lo que es hoy.", "roles": { "testing": "Pruebas", - "testingReviewer": "Pruebas y revisor" + "testingReviewer": "Pruebas y revisión" }, "youtube": "YouTube" }, "contribute": { "heading": "¿Quieres colaborar?", "intro": "Cualquier contribución es bienvenida — código, pruebas, diseño, documentación o simplemente compartir tus ideas:", - "tester": "Tester — prueba el canal beta y reporta qué funciona y qué no antes de los releases estables.", + "tester": "Pruebas: prueba el canal beta y comunica qué funciona y qué no antes de que se publiquen las versiones estables.", "developer": "Desarrollador — Bash + Next.js (TypeScript) + un poco de Python para el venv de traducción. Los PRs son bienvenidos en GitHub.", "designer": "Diseñador — mejoras de UI, iconos, capturas para la documentación.", "ideas": "Ideas — abre un hilo en GitHub Discussions antes de abrir una incidencia de petición de función." diff --git a/web/messages/es/docs/about/faq.json b/web/messages/es/docs/about/faq.json index 404377e6..834d8ffa 100644 --- a/web/messages/es/docs/about/faq.json +++ b/web/messages/es/docs/about/faq.json @@ -1,8 +1,8 @@ { "meta": { - "title": "FAQ | ProxMenux Documentation", + "title": "Preguntas frecuentes | Documentación de ProxMenux", "description": "Preguntas frecuentes sobre ProxMenux: qué es, instalación, compatibilidad (solo PVE 8+), personalización, actualizaciones, seguridad, uso en producción, seguridad de archivos del sistema, contribuir, desinstalación.", - "ogTitle": "FAQ | ProxMenux Documentation", + "ogTitle": "Preguntas frecuentes | Documentación de ProxMenux", "ogDescription": "Preguntas frecuentes sobre instalación, compatibilidad, seguridad y contribución a ProxMenux." }, "header": { @@ -19,12 +19,12 @@ "uninstallLabel": "Desinstalación", "uninstallSuffix": " — eliminación limpia.", "issuesLabel": "GitHub Issues", - "discussionsLabel": "Discussions" + "discussionsLabel": "Discusiones" }, "q1": { "question": "¿Qué es ProxMenux y para qué sirve?", "p1Rich": "ProxMenux es una herramienta interactiva basada en menús diseñada para hacer Proxmox VE accesible a todos los usuarios, independientemente de su experiencia técnica. Simplifica la ejecución de comandos, permitiendo a los usuarios realizar acciones en su sistema sin necesidad de conocimientos avanzados de Linux.", - "p2": "Para usuarios menos experimentados, ProxMenux ofrece una forma intuitiva de ejecutar comandos mediante una interfaz de menús estructurada, reduciendo la necesidad de entrada manual por terminal. Para administradores con experiencia, recorta pulsaciones para flujos repetitivos (hardening post-instalación, passthrough de GPU, importación de VMs OVF, rotaciones de snapshots ZFS).", + "p2": "Para quienes tienen menos experiencia, ProxMenux ofrece menús estructurados que reducen la necesidad de escribir comandos a mano. A los administradores con experiencia les ahorra pasos en tareas repetitivas: reforzar la seguridad tras instalar, pasar una GPU, importar VM en formato OVF o gestionar la rotación de snapshots ZFS.", "p3": "Proxmox VE se usa ampliamente para:", "items": [ "Virtualización de nivel empresarial", @@ -34,7 +34,7 @@ }, "q2": { "question": "¿Cómo instalo ProxMenux?", - "p1Rich": "Sigue la guía de instalación. En corto:", + "p1Rich": "Sigue la guía de instalación. En resumen:", "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", "p2": "Una vez instalado, simplemente arráncalo con:", "menuCmd": "menu" @@ -45,7 +45,7 @@ }, "q4": { "question": "¿Puedo personalizar ProxMenux?", - "p1Rich": "Los scripts del núcleo no se pueden modificar directamente porque están alojados en GitHub y se refrescan con el flujo de auto-actualización. Lo que sí puedes personalizar:", + "p1Rich": "Las actualizaciones pueden sobrescribir los cambios locales en los scripts principales, así que evita editar esos archivos directamente. Lo que sí puedes personalizar:", "items": [ "El banner de la consola vía la herramienta FastFetch en las opciones de post-instalación.", "Qué componentes opcionales se instalan (Monitor, Fail2Ban, Lynis…) — elige durante la instalación o vía el menú correspondiente más tarde." @@ -53,8 +53,8 @@ }, "q5": { "question": "¿Cómo actualizo ProxMenux?", - "p1Rich": "Cuando hay una nueva versión disponible, ProxMenux la detecta en el siguiente lanzamiento de menu y te pide actualizar. Aceptar reemplaza los archivos de utilidad y las configuraciones en sitio. No hace falta descarga manual.", - "p2Rich": "Los usuarios estables reciben releases estables; los usuarios beta reciben releases beta. Cuando se publica una versión estable, ProxMenux notifica a los usuarios beta en el siguiente lanzamiento y ofrece cambiar automáticamente — mira la página del programa beta." + "p1Rich": "Cuando hay una nueva versión, ProxMenux la detecta la próxima vez que ejecutas menu y te propone actualizar. Si aceptas, sustituye los archivos de la herramienta y su configuración en la instalación existente; no necesitas descargar nada a mano.", + "p2Rich": "Los usuarios de la versión estable reciben las actualizaciones estables y los de la beta, las actualizaciones beta. Cuando se publica una versión estable, la beta avisa al volver a abrir el menú y ofrece cambiar automáticamente a la estable. Consulta el programa beta." }, "q6": { "question": "¿Dónde puedo reportar incidencias?", @@ -63,19 +63,19 @@ }, "q7": { "question": "¿Puedo contribuir a ProxMenux?", - "p1Rich": "Absolutamente. ProxMenux es de código abierto y colaborativo. Las contribuciones pueden ser código (bash, Next.js / TypeScript, el venv de Python de traducción), pruebas, documentación, diseño o simplemente ideas.", + "p1Rich": "Por supuesto. ProxMenux es un proyecto de código abierto y colaborativo. Puedes contribuir con código (Bash, Next.js/TypeScript y herramientas de traducción en Python), pruebas, documentación, diseño o ideas.", "item1Rich": "Discute ideas en GitHub Discussions antes de abrir una petición de función.", "item2Rich": "Para PRs, lee primero el código de conducta y buenas prácticas.", "item3Rich": "Mira la página de colaboradores para los testers y revisores actuales." }, "q8": { "question": "¿ProxMenux modifica archivos críticos del sistema?", - "p1Rich": "No. ProxMenux instala:", - "item1Rich": "Dependencias vía apt: dialog, curl, jq, git (y python3 + googletrans en la instalación con traducción).", + "p1Rich": "La instalación de ProxMenux añade los siguientes componentes.", + "item1Rich": "Dependencias mediante apt: dialog, curl, jq y git. Las traducciones usan catálogos JSON pregenerados; el instalador no añade al host un entorno virtual de traducción.", "item2Rich": "El árbol de scripts en /usr/local/share/proxmenux/.", "item3Rich": "El lanzador en /usr/local/bin/menu.", "item4Rich": "Opcional: el servicio systemd del Monitor en proxmenux-monitor.service.", - "p2Rich": "ProxMenux no toca la configuración del núcleo de Proxmox a menos que se lo pidas explícitamente vía un menú dedicado (p. ej. el flujo de reparación de red edita /etc/network/interfaces — pero solo después de que confirmes y se haga un backup)." + "p2Rich": "ProxMenux no modifica la configuración principal de Proxmox salvo que lo solicites expresamente desde un menú específico. Por ejemplo, la reparación de red edita /etc/network/interfaces, pero solo después de tu confirmación y de crear una copia de seguridad." }, "q9": { "question": "¿Es seguro usar ProxMenux en producción?", @@ -83,12 +83,12 @@ }, "q10": { "question": "¿Cómo desinstalo ProxMenux?", - "p1Rich": "Desde dentro de ProxMenux: Settings → Desinstalar ProxMenux. El desinstalador elimina el árbol de scripts, el lanzador y el servicio del Monitor, opcionalmente elimina las dependencias seleccionadas y restaura los backups de /root/.bashrc y /etc/motd tomados en el momento de la instalación.", + "p1Rich": "Desde ProxMenux: Configuración → Desinstalar ProxMenux. El desinstalador elimina el árbol de scripts, el lanzador y el servicio Monitor, permite eliminar dependencias seleccionadas y restaura /root/.bashrc y /etc/motd desde las copias de la instalación, si existen.", "p2Rich": "Paso a paso completo: Desinstalar ProxMenux." }, "q11": { "question": "VirusTotal marca la URL de instalación — ¿es seguro?", - "p1Rich": "Una detección 1/95 por motores heurísticos (p. ej. Chong Lua Dao) es un falso positivo conocido. El instalador usa el patrón estándar curl | bash y descarga binarios legítimos (como jq desde su release oficial en GitHub), que los escáneres agresivos marcan basándose en el comportamiento y no en código realmente malicioso.", + "p1Rich": "El resultado de 1/95 notificado en el issue #162, de Chong Lua Dao, fue identificado por el mantenedor como un falso positivo. El instalador usa el patrón curl | bash y puede descargar jq desde su versión oficial de GitHub. Este comportamiento puede activar análisis heurísticos; aquel informe histórico no determina la seguridad de futuras descargas.", "p2Rich": "ProxMenux es 100% código abierto y el script de instalación es revisable línea por línea: install_proxmenux.sh. Más contexto en la Issue #162." } } diff --git a/web/messages/es/docs/about/index.json b/web/messages/es/docs/about/index.json index e0ca4406..058cc210 100644 --- a/web/messages/es/docs/about/index.json +++ b/web/messages/es/docs/about/index.json @@ -1,8 +1,8 @@ { "meta": { - "title": "Acerca de | ProxMenux Documentation", + "title": "Acerca del proyecto | Documentación de ProxMenux", "description": "Acerca de ProxMenux: objetivos del proyecto, colaboradores, FAQ y código de conducta. Herramienta de gestión basada en menús, de código abierto, para Proxmox VE 8+, construida y mantenida por MacRimi con un pequeño equipo de testers y colaboradores.", - "ogTitle": "Acerca de | ProxMenux Documentation", + "ogTitle": "Acerca del proyecto | Documentación de ProxMenux", "ogDescription": "Acerca de ProxMenux — herramienta de gestión basada en menús, de código abierto, para Proxmox VE 8+." }, "header": { @@ -27,7 +27,7 @@ "icon": "Users", "href": "/docs/about/contributors", "title": "Colaboradores", - "description": "Reconocimiento a las personas que hacen posible ProxMenux — testers, revisores y desarrolladores que dan forma a cada release." + "description": "Reconocimiento a las personas que hacen posible ProxMenux: quienes prueban, revisan y desarrollan cada versión." }, { "icon": "ScrollText", @@ -38,7 +38,7 @@ ] }, "involved": { - "heading": "Implicarse", + "heading": "Participa", "intro": "Tres lugares para participar en el proyecto:", "cards": [ { diff --git a/web/messages/fr/docs/about/code-of-conduct.json b/web/messages/fr/docs/about/code-of-conduct.json new file mode 100644 index 00000000..4842f6b2 --- /dev/null +++ b/web/messages/fr/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Code de conduite | Documentation ProxMenux", + "description": "Code de conduite et bonnes pratiques de ProxMenux pour les contributeurs, les testeurs et les utilisateurs.", + "ogTitle": "Code de conduite | Documentation ProxMenux", + "ogDescription": "Attentes concernant la responsabilité du code, le signalement des vulnérabilités et le périmètre des PR du projet ProxMenux." + }, + "errors": { + "notFound": "Erreur : fichier CODE_OF_CONDUCT.md introuvable.", + "loadFailed": "Erreur : Impossible de charger le contenu du Code de conduite." + }, + "contentMarkdown": "# 📝 Code de conduite et bonnes pratiques\n\nCe document définit les **principes et les consignes** d’utilisation de **ProxMenux**, afin de promouvoir le **respect, la collaboration et la sécurité** dans le développement.\n\n## 📌 1. Attribution du code et reconnaissance\n\n- Si des **scripts, extraits de code ou idées** proviennent d’autres dépôts ou développeurs, leur auteur d’origine sera toujours cité.\n- Un commentaire dans le code ou la documentation indiquera la source et, si possible, un lien vers le dépôt original.\n- Lors de l’utilisation de code soumis à une **licence** particulière, ses conditions doivent être respectées.\n\n### ✅ Exemple d’attribution correcte dans le code :\n\n```bash\n# Script fondé sur le travail de [Auteur] dans [Lien du dépôt]\n```\n\n## 🔒 2. Sécurité et responsabilité du code\n\n- Tout le code de ce dépôt suit les bonnes pratiques de développement sécurisé.\n- Les contributions introduisant volontairement des vulnérabilités ou des pratiques dangereuses sont interdites.\n- Les scripts ne doivent jamais compromettre la sécurité du système ni les données des utilisateurs.\n- Avant leur mise en œuvre, les contributions doivent être examinées pour vérifier leur sécurité et leur stabilité.\n\n### ⚠️ Divulgation responsable des vulnérabilités\n\nSi vous découvrez une vulnérabilité de sécurité, ne la signalez PAS dans des issues ou forums publics.\n\nSignalez-la plutôt en privé par e-mail :\n\n📧 proxmenux@macrimi.pro\n\nPour en savoir plus sur les vulnérabilités et leur signalement, consultez notre [politique de sécurité](./SECURITY.md).\n\n## 🤝 3. Règles de la communauté\n\nPour maintenir un cadre respectueux et professionnel, tous les utilisateurs doivent suivre ces règles :\n\n- ✅ Restez respectueux et constructifs dans les discussions.\n- ❌ Le harcèlement, les propos haineux et les comportements offensants ne seront pas tolérés.\n- ✅ Formulez vos retours de manière professionnelle, sans attaques personnelles.\n- ❌ Les manquements peuvent entraîner des avertissements ou des exclusions selon leur gravité.\n\n##\n\nCe document encourage un usage éthique du code, la vigilance en matière de sécurité et une communauté respectueuse.\n\n📢 Construisons ensemble quelque chose de remarquable !\n" +} diff --git a/web/messages/fr/docs/about/contributing.json b/web/messages/fr/docs/about/contributing.json new file mode 100644 index 00000000..6b46bd4c --- /dev/null +++ b/web/messages/fr/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Contribution à ProxMenux | ProxMenux Documentation", + "description": "Comment contribuer à ProxMenux : modèle de branches (main / develop / feature/*), processus de pull request, en-tête de script avec attribution et lien de soutien facultatif, interface en deux phases, fonctions de message et passage d’une branche de fonctionnalité à une version stable.", + "ogTitle": "Contribuer à ProxMenux", + "ogDescription": "Modèle de branches, processus, attribution dans l’en-tête et règle d’interface en deux phases à respecter pour contribuer." + }, + "header": { + "title": "Contribuer à ProxMenux", + "description": "ProxMenux accueille les contributions de la communauté : nouveaux scripts, corrections, amélioration des dialogues, traductions et intégrations. Cette page présente la structure du dépôt, le modèle de branches, le processus de pull request, l’en-tête avec attribution et lien de soutien facultatif, ainsi que les conventions à respecter.", + "section": "À propos" + }, + "twoPagesCallout": { + "title": "Deux pages connexes, un projet", + "body": "Ne confondez pas cette page avec Contributeurs, qui met à l’honneur les testeurs, relecteurs et développeurs déjà impliqués. Cette page s’adresse à vous, futur contributeur, et explique comment préparer une pull request en vue de son intégration." + }, + "branching": { + "heading": "Modèle de branchement", + "intro": "ProxMenux utilise un modèle de branchement à trois niveaux :", + "headerBranch": "Branche", + "headerPurpose": "Objet", + "rows": [ + { + "branch": "main", + "purposeRich": "Code stable, prêt pour la production. Seules les modifications prêtes à être publiées sont intégrées ici. C’est la version obtenue lors d’une installation normale de ProxMenux." + }, + { + "branch": "develop", + "purposeRich": "Branche de développement actif : tous les nouveaux changements y sont intégrés en premier. Elle correspond au canal « bêta ». Lorsqu’une version stable est prête, elle est publiée dans main à partir de cette branche." + }, + { + "branch": "feature/*", + "purposeRich": "Branches temporaires pour une fonctionnalité, une correction ou une amélioration. Créées à partir de develop, puis fusionnées dans develop par pull request après relecture." + } + ], + "calloutTitle": "Ce que cela signifie pour les utilisateurs", + "calloutBody": "Les utilisateurs qui suivent main reçoivent des versions testées. Ceux qui souhaitent les nouveautés plus tôt et acceptent quelques imperfections peuvent suivre develop. Les nouvelles fonctions passent toujours d’abord par develop ; rien n’arrive dans main sans ce cycle." + }, + "workflow": { + "heading": "Processus de pull request", + "intro": "De l'idée à la version fusionnée en cinq étapes:", + "step1Lead": "Créer une branche de fonction à partir de develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Utilisez un nom descriptif de branche : feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Travailler sur vos changements et pousser la branche:", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Ouvrez une pull request vers develop, pas vers main. Vérifiez sur GitHub que la branche de base est develop. Décrivez le rôle du script, ses modifications et les versions de Proxmox VE utilisées pour les tests.", + "step4": "Après relecture, les modifications sont fusionnées dans develop. Un responsable vérifie le code, les conventions d’en-tête et la règle d’interface en deux phases. S’il demande des corrections, envoyez de nouveaux commits dans la même branche : la PR se met à jour automatiquement.", + "step5": "Les versions stables passent de develop à main. Lors d’une publication, main est avancée jusqu’à develop par fast-forward, puis étiquetée. Votre contribution entre dans la prochaine version stable." + }, + "scriptHeader": { + "heading": "En-tête de script — métadonnées et description", + "intro": "Chaque script de ProxMenux s'ouvre avec deux blocs de commentaires adjacents qui forment ensemble l'en-tête. Ils sont tous les deux requis — ensemble, ils font savoir à tout lecteur qui a écrit le script et ce qu'il fait, le tout sans ouvrir le code lui-même.", + "bullets": [ + "Bloc supérieur : métadonnées. Auteur, liens GitHub/de soutien facultatifs, responsable, copyright, licence, version et date de mise à jour. C’est aussi la reconnaissance des contributeurs : pour un nouveau script, ajoutez votre nom et, si vous le souhaitez, votre page GitHub et un profil de soutien tel que Ko-fi, GitHub Sponsors ou Buy Me a Coffee.", + "Bloc inférieur : description. Un court paragraphe en anglais simple explique le rôle du script. Il se lit avant le code et doit se suffire à lui-même : objectif, actions principales, ressources concernées et prérequis." + ], + "licenseCalloutTitle": "La ligne de licence est fixe — GPL-3.0", + "licenseCalloutBody": "ProxMenux est publié sous la licence publique générale GNU v3.0. Chaque script du projet est livré sous cette même licence; la ligne License dans l'en-tête est toujours la référence GPL-3.0 montrée dans l'exemple ci-dessous — ce n'est pas un choix par script. En contribuant un script, vous acceptez de le publier sous GPL-3.0, ce qui signifie que n'importe qui peut le lire, le modifier et le redistribuer (y compris les modifications) tant qu'ils le conservent sous la même licence. Le texte entier vit à MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "Les lignes GitHub et Sponsor sont optionnelles — laissez-les dehors si vous ne voulez pas les publier. Tout le reste est nécessaire.", + "whyCalloutTitle": "Pourquoi cela est important", + "whyCalloutBody": "Contribuer à l’open source est un travail bénévole. Le nom et le lien de soutien éventuel dans le code rendent l’auteur visible à chaque lecture, au lieu de le cacher derrière le projet. La description a un autre rôle : c’est la première chose qu’un futur responsable ou utilisateur curieux lit. Sa clarté détermine souvent si le script inspire confiance ou est évité." + }, + "structure": { + "heading": "Structure du projet", + "intro": "Où chaque type de script vit dans le dépôt :", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Chaque script charge utils.sh au démarrage avec source pour obtenir les fonctions de message, l’indicateur d’attente, les couleurs et le système de traduction. Les bibliothèques communes dans scripts/global/ sont chargées explicitement par les scripts qui les utilisent." + }, + "twoPhase": { + "heading": "La règle de conception de l’interface en deux phases", + "intro": "C’est la convention la plus importante de ProxMenux et celle qui entraîne le plus souvent des demandes de correction dans les PR. Chaque script est divisé en exactement deux phases :", + "headerPhase": "Phase", + "headerPurpose": "Objet", + "headerScreen": "État de l'écran", + "rows": [ + { + "phaseRich": "Phase 1 — Sélection", + "purposeRich": "Recueillir chaque décision de l'utilisateur. Effectuer tout travail préparatoire nécessaire (sondes, scans, vérifications).", + "screenRich": "dialog superpositions. Si une sonde entre deux menus prend plus d'une seconde ou deux, montrez un spinner msg_info afin que l'utilisateur sache que le script n'a pas gelé, puis appelez stop_spinner juste avant la boîte de dialogue suivante." + }, + { + "phaseRich": "Phase 2 — Exécution", + "purposeRich": "Exécutez toutes les opérations. Affichez l'historique complet des progrès accumulés à l'écran.", + "screenRich": "Messages visibles msg_info / msg_ok; jamais dialog." + } + ], + "principle": "Le principe: collecte tout d'abord, puis exécute tout. L'utilisateur voit un menu de dialogue propre (phase 1), puis une vue d'exécution propre (phase 2). Pas de mélange — pas de dialogue apparaissant au milieu de l'exécution, pas de bruit de progression pendant la sélection.", + "phase1Heading": "Phase 1 — modèle typique", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Règles pour la phase 1:", + "phase1Rules": [ + "Si un spinner msg_info est en cours d'exécution et que vous devez ouvrir un menu dialog ou whiptail, appelez d'abord stop_spinner — le spinner ne peut pas coexister avec la superposition dessinée par l'un ou l'autre des outils. Si aucun spinner n'est actif, vous n'avez pas besoin de l'appeler.", + "Utilisez show_proxmenux_logo + msg_title + msg_info pour donner un contexte visuel à une opération longue en phase 1, par exemple une vérification de plus de cinq secondes. La fonction efface déjà l’écran : n’appelez pas clear avant.", + "N'appelez pas show_proxmenux_logo entre les menus de dialogue où il n'y a rien à afficher — le nettoyage de l'écran pour un terminal vide n'est que du bruit visuel.", + "Conservez toutes les décisions et les résultats de la sonde dans des variables ou des tableaux parallèles. La récapitulation visible se produit au début de la phase 2, pas dans la phase 1." + ], + "phase2Heading": "Phase 2 — modèle typique", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Règles pour la phase 2: toujours commencer avec show_proxmenux_logo + msg_title; récapituler immédiatement les résultats de la phase 1 sous la forme de lignes msg_ok; n'appelez plus jamais show_proxmenux_logo (il effacerait les progrès accumulés); n'appelez jamais dialog dans la phase 2 — si une décision d'exécution est vraiment inévitable, utilisez whiptail (voir la section suivante)." + }, + "dialogVsWhiptail": { + "headingRich": "Quand utiliser dialog vs whiptail", + "intro": "Les deux outils dessinent des interfaces utilisateur en mode texte, mais ils se comportent très différemment à l'écran — et ProxMenux les utilise en deux phases distinctes pour une raison.", + "headerTool": "Outil", + "headerWhen": "Quand l'utiliser", + "headerEffect": "Effet sur l'écran", + "rows": [ + { + "toolRich": "dialog", + "whenRich": "Toujours dans la phase 1. Chaque sélection interactive — choisir un VM, un disque, un système de fichiers, confirmer une action — utilise dialog. C'est l'outil d'interface utilisateur par défaut de ProxMenux.", + "effectRich": "Prend le contrôle du terminal : efface l’écran, dessine son interface et rétablit l’état précédent à la fermeture. Cela convient à la phase 1, où aucun résultat utile n’est encore affiché." + }, + { + "toolRich": "whiptail", + "whenRich": "Seulement en phase 2, en cas de nécessité. Le cas typique est la proposition de redémarrage en fin d’exécution. N’utilisez pas whiptail pour demander « continuer ? » en cours d’exécution : cette décision devait être prise en phase 1.", + "effectRich": "Interface plus légère qui n’efface pas le contexte du terminal. L’historique msg_ok/msg_info de la phase 2 reste visible derrière la boîte de dialogue. Elle convient donc lorsque la progression est déjà affichée." + } + ], + "calloutTitle": "Pourquoi la scission est importante", + "calloutBody": "Si vous appelez dialog au milieu de la phase 2, chaque ligne msg_ok que l'utilisateur regarde disparaît. Vous effacez la piste de vérification. whiptail évite cela. Donc la règle n'est pas arbitraire — il s'agit de préserver la vision de l'utilisateur de ce que le script a fait jusqu'ici.", + "rebootHeading": "Proposition de redémarrage : le cas type de whiptail en phase 2", + "rebootIntro": "Si un script peut nécessiter un redémarrage après l’activation d’IOMMU ou la modification du noyau, la question de fin de phase 2 utilise whiptail. Prévoyez toujours une branche « Non » avertissant de ne pas utiliser la ressource concernée avant le redémarrage :", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Référence des fonctions du message", + "intro": "Toutes sont définies dans utils.sh. Utilisez-les par défaut pour les sorties visibles afin de garder une présentation cohérente entre scripts. Si une fonction manque, par exemple un niveau de sévérité ou un outil de mise en page, proposez-la dans votre pull request. Elle sera examinée et ajoutée à utils.sh si elle est utile à plusieurs scripts.", + "headerFunction": "Fonction", + "headerWhen": "Quand utiliser", + "headerSpinner": "Indicateur de progression", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Opération en cours.", + "spinner": "Démarre" + }, + { + "function": "stop_spinner", + "whenRich": "Fin des travaux préparatoires silencieux dans la phase 1 — tue spinner, efface la ligne.", + "spinner": "Arrête" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "Opération réussie. À utiliser aussi pour une fonction activée, même si un redémarrage reste nécessaire.", + "spinner": "Arrête" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Avertissement réel ou état dégradé.", + "spinner": "Arrête" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Erreur fatale.", + "spinner": "Arrête" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Conseil sans blocage, dans une ligne d’information cyan.", + "spinner": "Arrête" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Message final « Appuyez sur Entrée pour revenir » en fin de phase 2.", + "spinner": "Arrête" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Titre gras avec espacement intégré. Utilisé au début de la phase 2.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Efface l’écran et affiche le logo. À appeler une seule fois au début de la phase 2.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "conventions de dialogue", + "bullets": [ + "Passez toujours --backtitle \"$BACKTITLE\" à chaque appel dialog et whiptail. $BACKTITLE est toujours \"ProxMenux\" — réglé une fois à l'en-tête du script et jamais dépassé. L'utilisateur doit toujours voir le nom du projet comme le contexte de cadrage, jamais le propre titre du script.", + "Enveloppez toujours les titres et les messages avec $(translate \"...\").", + "Redirige toujours la sortie dialog avec 2>&1 >/dev/tty — le stdout capturé devient la sélection de l'utilisateur, tandis que la boîte de dialogue elle-même s'appuie sur le terminal.", + "Utilisez les variables standard de dimension UI ($UI_MENU_H, $UI_MSG_W, etc.) pour un calibrage cohérent entre les scripts.", + "Vérifiez toujours les choix vides ou annulés et gérez-les proprement." + ], + "exampleIntro": "Exemple complet — construire un menu de sélection VM et gérer l'annulation:", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Politique de traduction", + "intro": "Toutes les chaînes visibles par l'utilisateur doivent être enveloppées avec la fonction translate. ProxMenux les traduit automatiquement dans toutes les langues prises en charge — vous écrivez l'anglais, l'utilisateur lit leur langue maternelle.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Écrivez les chaînes en anglais : la traduction est gérée automatiquement lors du build.", + "Rédigez des chaînes concises. Évitez si possible les variables dans de longues phrases.", + "Ne traduisez pas les noms de variables, chemins ou identifiants techniques." + ] + }, + "variableStyle": { + "heading": "Conventions de variables et de style", + "bullets": [ + "Utilisez UPPER_CASE pour les variables de niveau script.", + "Utiliser lower_case pour les variables de fonction locales (déclarer avec local).", + "Entourez les expansions de variables de guillemets : \"$VAR\", pas $VAR.", + "Utiliser [[ ]] pour les conditions, et non [ ] (sauf lorsque POSIX est nécessaire).", + "show_proxmenux_logo est le moyen prévu pour effacer l’écran : il le nettoie et affiche le logo du projet pour conserver un contexte visuel. Appelez-le une fois au début de la phase 2, et éventuellement avant une longue attente en phase 1." + ], + "standardNamesIntro": "Noms de variables standard dans l'ensemble du projet :", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Réorienter la sortie de l'outil pendant la phase 2", + "redirectIntro": "La phase 2 affiche un journal de lignes msg_info → msg_ok. Si un outil appelé, comme apt, mkfs, qm, pct ou dd, écrit sur stdout/stderr, sa sortie fait défiler les messages et brouille la présentation. L’utilisateur ne distingue plus la progression du script des détails produits par l’outil.", + "withoutRedirectIntro": "Sans redirection — ce que l'utilisateur voit si vous ne manipulez pas le bruit:", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": "Avec redirection — à quoi ressemble la même opération lorsque le bruit est envoyé ailleurs:", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Deux modèles à choisir :", + "discardLead": "Éliminez la sortie lorsque vous n'en avez pas besoin — plus rapide et plus simple:", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Envoyez la sortie à un fichier journal lorsque vous voudrez peut-être l'inspecter plus tard (débogage d'une installation échouée, vérification de ce que dpkg a fait réellement). C'est le modèle préféré pour toute opération apt:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "Le script scripts/global/update-pve9_2.sh est une implémentation de référence — chaque appel apt-get envoie une sortie vers un fichier journal de sorte que l'utilisateur ne voit que le flux propre msg_info → msg_ok, tandis que le journal sur le disque vous permet de reconstruire exactement ce qu'apt a fait si quelque chose tourne mal." + }, + "dosAndDonts": { + "heading": "Faites et ne faites pas", + "doHeading": "✅ À faire", + "doBullets": [ + "stop_spinner avant un dialog dans la phase 1 seulement quand un spinner msg_info est en cours d'exécution.", + "La phase 2 commence par show_proxmenux_logo + msg_title + msg_ok pour récapituler les résultats de la phase 1.", + "Utilisez msg_ok pour des fonctionnalités activées avec succès, même si un redémarrage est nécessaire.", + "Utilisez le whiptail (et non le dialog) pour toute invitation après exécution qui doit apparaître dans la phase 2.", + "Prévoyez toujours une branche « Non » dans les dialogues de redémarrage pour avertir de ne pas démarrer la ressource concernée avant le redémarrage.", + "Protégez la logique VM uniquement en vérifiant [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]] — les contrôleurs et NVMe PCIe ne peuvent pas être ajoutés aux conteneurs LXC.", + "Utiliser ensure_repositories de utils-install-functions.sh au lieu de apt-get update inconditionnel.", + "Utiliser des tableaux parallèles dans la phase 1 lorsque chaque élément nécessite plusieurs dialogues." + ], + "dontHeading": "❌ À éviter", + "dontBullets": [ + "Appelez dialog alors qu'un spinner est actif.", + "Passez la récapitulation de la phase 1 au début de la phase 2.", + "Appelez show_proxmenux_logo une deuxième fois — il efface tout ce que la phase 2 a imprimé.", + "Utiliser dialog dans la phase 2 (utiliser whiptail pour le cas inévitable rare).", + "Utiliser clear seul.", + "Enveloppez msg_title dans des lignes vierges echo \"\" — il inclut déjà l'espacement.", + "Utilisez msg_warn pour signaler une fonctionnalité activée avec succès — c'est un msg_ok.", + "Exécuter sans condition apt-get update — utiliser ensure_repositories." + ] + }, + "submitting": { + "heading": "Soumettre votre contribution", + "steps": [ + "Créez un fork de MacRimi/ProxMenux sur GitHub, puis clonez votre fork.", + "Créer une branche feature/* à partir de develop (voir Modèle de branchage ci-dessus).", + "Suivez ce guide pour tout script nouveau ou modifié. Écrivez l'en-tête avec votre nom; ajoutez la ligne de sponsor optionnelle si vous voulez.", + "Testez le script sur une véritable instance Proxmox VE : phase 1 avec toutes les branches de dialogue, et phase 2 avec chaque opération et son retour arrière propre en cas d’erreur.", + "Ouvrez une pull request vers develop avec une description claire : rôle du script, changements et version Proxmox VE utilisée pour les tests.", + "Assurez-vous que votre contribution respecte le Code de conduite." + ], + "securityOutro": "Pour les questions sensibles à la sécurité, suivez le flux de divulgation dans SECURITY.md plutôt que d'ouvrir une question publique." + }, + "whereNext": { + "heading": "Où aller ensuite", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (guide complet)", + "tail": "— la source de vérité pour chaque convention, y compris les modèles matériels avancés." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Contributeurs", + "tail": " — les personnes dont le travail a façonné les versions de ProxMenux. Il s’agit d’une autre page." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Code de conduite", + "tail": "— les normes que chaque contributeur accepte de respecter." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": "— demander avant de construire si vous n'êtes pas sûr si une idée correspond, ou de trouver des collaborateurs sur une fonctionnalité plus grande." + } + ] + } +} diff --git a/web/messages/fr/docs/about/contributors.json b/web/messages/fr/docs/about/contributors.json new file mode 100644 index 00000000..47f3e6f5 --- /dev/null +++ b/web/messages/fr/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Contributeurs | Documentation ProxMenux", + "description": "Un hommage aux personnes qui rendent ProxMenux possible : testeurs, relecteurs et développeurs qui contribuent à chaque version.", + "ogTitle": "Contributeurs | Documentation ProxMenux", + "ogDescription": "Rencontrez les contributeurs derrière ProxMenux." + }, + "header": { + "title": "Contributeurs", + "description": "Le projet ProxMenux grandit grâce à ses contributeurs. Cette page reconnaît leur travail : tester les versions avant leur publication, détecter les régressions, examiner les PR et orienter les nouvelles fonctionnalités.", + "section": "À propos" + }, + "beyond": { + "title": "Au-delà de cette liste", + "body": "De nombreuses autres personnes contribuent en signalant des bugs, en proposant des fonctionnalités dans Discussions, en testant les versions bêta ou en traduisant le projet. La liste complète des contributeurs au code figure dans le graphique des contributeurs GitHub. La page ci-dessous met en avant les personnes les plus actives dans les tests et la vérification des versions de ProxMenux." + }, + "testers": { + "heading": "Testeurs et évaluateurs", + "intro": "Ces personnes ont été parmi les premières à contribuer au projet. Leurs tests, signalements de problèmes et suggestions ont aidé ProxMenux à devenir ce qu’il est aujourd’hui.", + "roles": { + "testing": "Tests", + "testingReviewer": "Tests et relecture" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Vous voulez contribuer ?", + "intro": "Toute contribution est la bienvenue — code, test, conception, documentation, ou tout simplement partager vos idées:", + "tester": "Testeur — essayez le canal bêta et signalez ce qui fonctionne ou non avant la publication des versions stables.", + "developer": "Développeur — Bash, Next.js (TypeScript) et un peu de Python pour l’environnement virtuel de traduction (venv). Les PR sont les bienvenues sur GitHub.", + "designer": "Designer — Améliorations de l'interface utilisateur, icônes, captures d'écran pour la documentation.", + "ideas": "Idées — ouvrez un sujet dans GitHub Discussions avant de créer une issue pour demander une fonctionnalité." + }, + "coc": { + "title": "Lisez d'abord le Code de conduite", + "body": "Avant de proposer du code ou de signaler un problème, lisez le Code de conduite et les bonnes pratiques. Il précise les attentes sur la responsabilité du code, le signalement des vulnérabilités et le périmètre des PR." + } +} diff --git a/web/messages/fr/docs/about/faq.json b/web/messages/fr/docs/about/faq.json new file mode 100644 index 00000000..af1d0db6 --- /dev/null +++ b/web/messages/fr/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Questions fréquentes | Documentation ProxMenux", + "description": "Questions fréquentes sur ProxMenux : présentation, installation, compatibilité à partir de PVE 8, personnalisation, mises à jour, sécurité, utilisation en production, fichiers système, contributions et désinstallation.", + "ogTitle": "Questions fréquentes | Documentation ProxMenux", + "ogDescription": "Questions fréquentes sur l’installation de ProxMenux, sa compatibilité, sa sécurité et les façons de contribuer." + }, + "header": { + "title": "Foire aux questions", + "description": "Questions courantes sur ProxMenux : présentation, public visé, installation, mise à jour, désinstallation, compatibilité, personnalisation, sécurité et contributions. Si votre question n’est pas ici, consultez GitHub Discussions ou ouvrez une issue.", + "section": "À propos" + }, + "quickLinks": { + "title": "Liens rapides", + "installationLabel": "Installation", + "installationSuffix": "— la commande d'installation en une seule ligne + dépendances.", + "introductionLabel": "Présentation", + "introductionSuffix": " — ce que fait ProxMenux, en cinq minutes.", + "uninstallLabel": "Désinstaller", + "uninstallSuffix": " — désinstallation propre.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Discussions" + }, + "q1": { + "question": "Qu'est-ce que ProxMenux et dans quel cas est-il utilisé?", + "p1Rich": "ProxMenux est un outil interactif à menus qui rend Proxmox VE accessible à tous, quel que soit le niveau technique. Il simplifie l’exécution des commandes et permet d’agir sur le système sans connaissances avancées de Linux.", + "p2": "Les utilisateurs moins expérimentés exécutent les commandes depuis des menus structurés, avec moins de saisie manuelle dans le terminal. Les administrateurs expérimentés gagnent du temps sur les tâches répétitives : sécurisation après installation, passthrough GPU, importation de VM OVF et rotation des instantanés ZFS.", + "p3": "Proxmox VE est largement utilisé pour:", + "items": [ + "Virtualisation de qualité entreprise", + "HomeLab et solutions cloud personnelles", + "Serveurs multimédias, automatisation et plus encore" + ] + }, + "q2": { + "question": "Comment installer ProxMenux ?", + "p1Rich": "Suivez le guide d’installation. En résumé :", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "Une fois l’installation terminée, lancez simplement :", + "menuCmd": "menu" + }, + "q3": { + "question": "ProxMenux est-il compatible avec toutes les versions Proxmox ?", + "bodyRich": "Non — ProxMenux nécessite Proxmox VE 8 ou une version ultérieure. PVE 7 et les versions antérieures ne sont pas pris en charge. Passez d’abord de PVE 7 à 8.x en suivant la procédure officielle Proxmox. ProxMenux peut ensuite gérer le passage de 8 à 9 via Utilitaires → Mettre à niveau PVE 8 vers PVE 9." + }, + "q4": { + "question": "Puis-je personnaliser ProxMenux ?", + "p1Rich": "Les mises à jour peuvent écraser les modifications locales des scripts principaux. Évitez donc de modifier directement ces fichiers. Voici ce que vous pouvez personnaliser :", + "items": [ + "La bannière console via l'outil FastFetch dans les options Post-Install.", + "Quels composants optionnels sont installés (Monitor, Fail2Ban, Lynis...) — choisissez lors de l'installation ou via le menu correspondant plus tard." + ] + }, + "q5": { + "question": "Comment mettre à jour ProxMenux?", + "p1Rich": "Lorsqu’une nouvelle version est disponible, ProxMenux la détecte au prochain lancement de menu et propose la mise à jour. Si vous acceptez, les fichiers et la configuration de l’outil sont remplacés dans l’installation existante ; aucun téléchargement manuel n’est nécessaire.", + "p2Rich": "Les utilisateurs de la version stable reçoivent les mises à jour stables, et ceux de la bêta, les mises à jour bêta. Lorsqu’une version stable est publiée, la bêta le signale à la prochaine ouverture du menu et propose de basculer automatiquement. Consultez le programme bêta." + }, + "q6": { + "question": "Où puis-je signaler les problèmes?", + "p1Rich": "Signalez les bogues sur GitHub Issues. Indiquez les étapes pour les reproduire, le message d’erreur et, si nécessaire, la sortie de Monitor obtenue avec journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "Pour les problèmes de sécurité, ne les divulguez pas publiquement. Suivez la procédure de signalement responsable du code de conduite et des bonnes pratiques." + }, + "q7": { + "question": "Puis-je contribuer au ProxMenux?", + "p1Rich": "Bien sûr. ProxMenux est un projet libre et collaboratif. Vous pouvez contribuer au code (Bash, Next.js/TypeScript et outils de traduction en Python), aux tests, à la documentation, au design ou proposer des idées.", + "item1Rich": "Discutez des idées dans GitHub Discussions avant d'ouvrir une demande de fonctionnalité.", + "item2Rich": "Avant de soumettre une pull request, lisez le code de conduite et les bonnes pratiques.", + "item3Rich": "Consultez la page des contributeurs pour connaître les personnes qui participent actuellement aux tests et aux revues." + }, + "q8": { + "question": "Est-ce que ProxMenux modifie les fichiers système critiques?", + "p1Rich": "L’installation de ProxMenux ajoute les composants suivants.", + "item1Rich": "Dépendances via apt : dialog, curl, jq et git. Les traductions utilisent des catalogues JSON préconstruits ; l’installateur n’ajoute aucun environnement virtuel de traduction à l’hôte.", + "item2Rich": "L'arborescence des scripts à /usr/local/share/proxmenux/.", + "item3Rich": "Le lanceur situé dans /usr/local/bin/menu.", + "item4Rich": "En option : le service systemd de Monitor, proxmenux-monitor.service.", + "p2Rich": "ProxMenux ne modifie pas la configuration principale de Proxmox, sauf si vous le demandez explicitement depuis un menu dédié. Par exemple, la réparation du réseau modifie /etc/network/interfaces, mais seulement après votre confirmation et la création d’une sauvegarde." + }, + "q9": { + "question": "Est-il sûr d'utiliser ProxMenux en production?", + "bodyRich": "Oui. ProxMenux ne modifie les fichiers principaux de Proxmox que sur demande explicite et peut donc être utilisé en production. Comme pour toute automatisation, testez d’abord dans un environnement contrôlé et prévoyez des sauvegardes avant les opérations destructives : mise à niveau de PVE 8 vers 9, formatage d’un disque, suppression d’une VM, etc." + }, + "q10": { + "question": "Comment désinstaller ProxMenux ?", + "p1Rich": "Dans ProxMenux : Paramètres → Désinstaller ProxMenux. La désinstallation supprime l’arborescence des scripts, le lanceur et le service Monitor, ainsi que les dépendances sélectionnées si vous le souhaitez. Elle restaure /root/.bashrc et /etc/motd depuis les sauvegardes de l’installation, si elles existent.", + "p2Rich": "Étape par étape : Désinstaller ProxMenux." + }, + "q11": { + "question": "VirusTotal affiche l'URL d'installation — est-ce sûr?", + "p1Rich": "Le résultat de 1/95 attribué à Chong Lua Dao et signalé dans l’issue #162 a été identifié par le mainteneur comme un faux positif. L’installateur utilise le modèle curl | bash et peut télécharger jq depuis sa version officielle sur GitHub. Ce comportement peut déclencher des analyses heuristiques ; ce rapport historique ne préjuge pas de tous les téléchargements futurs.", + "p2Rich": "ProxMenux est entièrement open source. Vous pouvez examiner le script d’installation ligne par ligne : install_proxmenux.sh. Plus de contexte dans l’issue #162." + } +} diff --git a/web/messages/fr/docs/about/index.json b/web/messages/fr/docs/about/index.json new file mode 100644 index 00000000..d0bb7318 --- /dev/null +++ b/web/messages/fr/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "À propos | Documentation ProxMenux", + "description": "À propos de ProxMenux : objectifs du projet, contributeurs, FAQ et code de conduite. Cet outil de gestion open source à menus pour Proxmox VE 8+ est développé et maintenu par MacRimi avec une petite équipe de testeurs et de contributeurs.", + "ogTitle": "À propos | Documentation ProxMenux", + "ogDescription": "À propos de ProxMenux — un outil de gestion open source à menus pour Proxmox VE 8+." + }, + "header": { + "title": "À propos", + "description": "Informations sur le projet, FAQ, contributeurs et code de conduite. ProxMenux est un outil de gestion open source à menus pour Proxmox VE 8+, développé et maintenu par MacRimi avec l’aide d’une petite équipe de testeurs et de contributeurs.", + "section": "À propos" + }, + "callout": { + "title": "Open source, porté par la communauté", + "body": "ProxMenux est distribué sous licence GPL-3.0 et développé publiquement sur GitHub. Chaque script peut être examiné et chaque modification retracée. Les contributions sont les bienvenues : signalements de bugs, idées, code et tests." + }, + "section": { + "heading": "Dans cette section", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Questions fréquentes : présentation de ProxMenux, installation, compatibilité, personnalisation, mises à jour, sécurité, utilisation en production et désinstallation." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Contributeurs", + "description": "Un hommage aux personnes qui rendent ProxMenux possible : testeurs, relecteurs et développeurs qui contribuent à chaque version." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Code de conduite", + "description": "Valeurs du projet, attentes en matière de responsabilité du code et lignes directrices en matière de contribution. Lecture obligatoire avant d'ouvrir un PR ou un rapport." + } + ] + }, + "involved": { + "heading": "Participer", + "intro": "Trois endroits pour participer au projet :", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Signaler un bug", + "description": "GitHub Issues — joignez les journaux et les étapes pour reproduire le problème." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Proposer une fonctionnalité", + "description": "GitHub Discussions — échangez des idées avant d'ouvrir un PR." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Proposer une PR", + "description": "GitHub Pull Requests — lisez d’abord le code de conduite." + } + ] + }, + "support": { + "heading": "Soutenir le projet", + "introRich": "Si ProxMenux vous fait gagner du temps, le moyen le plus simple de le faire découvrir est de donner une ⭐ au dépôt sur GitHub. Pour aller plus loin :", + "kofiLabel": "Soutien sur Ko-fi", + "kofiOutro": "— achète au responsable un café et continue le développement." + } +} diff --git a/web/messages/it/docs/about/code-of-conduct.json b/web/messages/it/docs/about/code-of-conduct.json new file mode 100644 index 00000000..e452eda3 --- /dev/null +++ b/web/messages/it/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Codice di condotta | Documentazione ProxMenux", + "description": "Codice di condotta e buone pratiche di ProxMenux per collaboratori, tester e utenti.", + "ogTitle": "Codice di condotta | Documentazione ProxMenux", + "ogDescription": "Aspettative sulla responsabilità del codice, sulla segnalazione delle vulnerabilità e sull’ambito delle PR nel progetto ProxMenux." + }, + "errors": { + "notFound": "Errore: file CODE_OF_CONDUCT.md non trovato.", + "loadFailed": "Errore: impossibile caricare il contenuto del codice di condotta." + }, + "contentMarkdown": "# 📝 Codice di condotta e buone pratiche\n\nQuesto documento definisce i **principi e le linee guida** per l’uso di **ProxMenux**, promuovendo **rispetto, collaborazione e sicurezza** nello sviluppo.\n\n## 📌 1. Attribuzione del codice e riconoscimenti\n\n- Se si usano **script, frammenti di codice o idee** di altri repository o sviluppatori, l’autore originale verrà sempre citato.\n- Un commento nel codice o nella documentazione indicherà la fonte e, se possibile, un collegamento al repository originale.\n- Quando si utilizza codice con una **licenza** specifica, occorre rispettarne le condizioni.\n\n### ✅ Esempio di attribuzione corretta nel codice:\n\n```bash\n# Script basato sul lavoro di [Autore] in [Link al repository]\n```\n\n## 🔒 2. Sicurezza e responsabilità del codice\n\n- Tutto il codice di questo repository segue le buone pratiche di sviluppo sicuro.\n- Non sono ammessi contributi che introducano vulnerabilità intenzionali o pratiche non sicure.\n- Gli script non devono mai compromettere la sicurezza del sistema o i dati degli utenti.\n- Prima dell’implementazione, i contributi vanno revisionati per verificarne sicurezza e stabilità.\n\n### ⚠️ Divulgazione responsabile delle vulnerabilità\n\nSe scopri una vulnerabilità di sicurezza, NON segnalarla in issue o forum pubblici.\n\nComunicala invece in privato tramite e-mail:\n\n📧 proxmenux@macrimi.pro\n\nPer maggiori informazioni sulle vulnerabilità e su come segnalarle, consulta la nostra [politica di sicurezza](./SECURITY.md).\n\n## 🤝 3. Regole della comunità\n\nPer mantenere un ambiente rispettoso e professionale, tutti gli utenti devono seguire queste regole:\n\n- ✅ Mantieni un atteggiamento rispettoso e costruttivo nelle discussioni.\n- ❌ Non saranno tollerati molestie, discorsi d’odio o comportamenti offensivi.\n- ✅ Fornisci riscontri in modo professionale, evitando attacchi personali.\n- ❌ Le violazioni possono comportare avvertimenti o esclusioni, in base alla gravità.\n\n##\n\nQuesto documento promuove l’uso etico del codice, la consapevolezza della sicurezza e una comunità rispettosa.\n\n📢 Costruiamo insieme qualcosa di straordinario!\n" +} diff --git a/web/messages/it/docs/about/contributing.json b/web/messages/it/docs/about/contributing.json new file mode 100644 index 00000000..58d394ec --- /dev/null +++ b/web/messages/it/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Contributo a ProxMenux | Documentazione ProxMenux", + "description": "Come contribuire a ProxMenux: modello dei rami (main / develop / feature/*), procedura delle pull request, intestazione con autore e link di sostegno facoltativo, interfaccia in due fasi, funzioni dei messaggi e passaggio da un ramo di funzionalità a una versione stabile.", + "ogTitle": "Contributi a ProxMenux", + "ogDescription": "Modello dei rami, procedura, attribuzione nell’intestazione e regola dell’interfaccia in due fasi per chi contribuisce." + }, + "header": { + "title": "Contributi a ProxMenux", + "description": "ProxMenux è aperto ai contributi comunitari — nuovi script, correzioni per quelli esistenti, miglioramenti di dialogo, traduzioni, integrazioni. Questa pagina è la porta formale: come il repository è strutturato, il modello di ramificazione e il flusso di lavoro di pull-request, l'intestazione dello script con attribuzione dell'autore e link di sponsor opzionale, e le convenzioni di progettazione ogni contributo deve seguire.", + "section": "Informazioni" + }, + "twoPagesCallout": { + "title": "Due pagine correlate, un progetto", + "body": "Non confondere questa pagina con Collaboratori, che riconosce il lavoro di tester, revisori e sviluppatori già coinvolti. Questa pagina è per te, futuro collaboratore, e spiega come preparare una pull request da integrare." + }, + "branching": { + "heading": "Modello di ramificazione", + "intro": "ProxMenux utilizza un modello di ramificazione a tre livelli:", + "headerBranch": "Ramo", + "headerPurpose": "Scopo", + "rows": [ + { + "branch": "main", + "purposeRich": "Codice stabile, pronto per la produzione. Qui vengono integrate solo modifiche pronte per il rilascio. È la versione ricevuta con un’installazione normale di ProxMenux." + }, + { + "branch": "develop", + "purposeRich": "Ramo di sviluppo attivo. Il nuovo lavoro viene integrato prima qui: è il canale beta. Quando una versione stabile è pronta, viene trasferita da qui a main." + }, + { + "branch": "feature/*", + "purposeRich": "Rami temporanei per una funzionalità, una correzione o un miglioramento. Creati da develop e reintegrati in develop tramite pull request dopo la revisione." + } + ], + "calloutTitle": "Ciò significa per gli utenti", + "calloutBody": "Chi segue main riceve versioni testate. Chi vuole prima le novità e accetta qualche imperfezione può seguire develop. Le nuove funzioni passano sempre prima da develop; nulla arriva in main senza questo ciclo." + }, + "workflow": { + "heading": "Procedura delle pull request", + "intro": "Dall'idea alla fusione del rilascio in cinque fasi:", + "step1Lead": "Crea un ramo per la funzionalità da develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Utilizzare un nome di ramo descrittivo: feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Lavora alle modifiche e pubblica il ramo:", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Apri una pull request verso develop, non verso main. Su GitHub verifica che il ramo di base sia develop. Descrivi cosa fa e modifica lo script e su quali versioni di Proxmox VE è stato testato.", + "step4": "Dopo la revisione le modifiche vengono integrate in develop. Un manutentore verifica codice, intestazione e interfaccia in due fasi. Se richiede modifiche, aggiungi commit allo stesso ramo: la PR si aggiorna automaticamente.", + "step5": "Le versioni stabili passano da develop a main. Al rilascio, main viene portato a develop con un fast-forward e riceve un tag. Il tuo contributo entra nella prossima versione stabile." + }, + "scriptHeader": { + "heading": "Intestazione script — metadati & descrizione", + "intro": "Ogni script ProxMenux inizia con due blocchi di commenti adiacenti che formano l’intestazione. Sono entrambi obbligatori: indicano autore e scopo senza dover leggere il corpo del codice.", + "bullets": [ + "Blocco superiore: metadati. Autore, link GitHub/di sostegno facoltativi, manutentore, copyright, licenza, versione e data di aggiornamento. Qui avviene il riconoscimento dei collaboratori: per un nuovo script inserisci il tuo nome e, facoltativamente, la pagina GitHub e un profilo come Ko-fi, GitHub Sponsors o Buy Me a Coffee.", + "Blocco inferiore: descrizione. Un breve paragrafo in inglese semplice spiega il compito dello script. Si legge prima del codice e deve chiarire da solo scopo, azioni principali, risorse coinvolte e prerequisiti." + ], + "licenseCalloutTitle": "La linea di licenza è fissa — GPL-3.0", + "licenseCalloutBody": "ProxMenux è pubblicato sotto GNU General Public License v3.0. Tutti gli script usano la stessa licenza: la riga License nell’intestazione deve essere il riferimento GPL-3.0 dell’esempio seguente, non una scelta per singolo script. Contribuendo accetti il rilascio sotto GPL-3.0: chiunque può leggere, modificare e ridistribuire lo script e le modifiche mantenendo la stessa licenza. Testo completo: MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "Le linee GitHub e Sponsor sono facoltative: lasciatele fuori se non volete pubblicarle. Tutto il resto è richiesto.", + "whyCalloutTitle": "Perché questo è importante", + "whyCalloutBody": "Contribuire all’open source è un lavoro volontario. Il nome e l’eventuale link di sostegno nel codice rendono visibile l’autore a ogni lettura, senza nasconderlo dietro il progetto. La descrizione ha un altro scopo: è la prima cosa letta da un futuro manutentore o utente curioso. La sua chiarezza spesso determina se ci si fida dello script o lo si evita." + }, + "structure": { + "heading": "Struttura del progetto", + "intro": "Dove ogni tipo di script vive nel repository:", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Ogni script carica utils.sh all’avvio tramite source per avere funzioni dei messaggi, indicatore di attesa, colori e sistema di traduzione. Le librerie condivise in scripts/global/ vengono caricate esplicitamente dagli script che ne hanno bisogno." + }, + "twoPhase": { + "heading": "La politica di progettazione dell'interfaccia utente bifase", + "intro": "È la convenzione più importante di ProxMenux e quella per cui vengono richieste più spesso correzioni alle PR. Ogni script è diviso in esattamente due fasi:", + "headerPhase": "Fase", + "headerPurpose": "Scopo", + "headerScreen": "Stato dello schermo", + "rows": [ + { + "phaseRich": "Phase 1 — Selezione", + "purposeRich": "Raccogliere ogni decisione dell'utente. Eseguire qualsiasi lavoro preparatorio necessario (sonde, scansioni, controlli).", + "screenRich": "Finestre dialog. Se una verifica tra due menu richiede più di uno o due secondi, mostra l’indicatore di attesa msg_info perché l’utente sappia che lo script non è bloccato, poi chiama stop_spinner subito prima del prossimo dialogo." + }, + { + "phaseRich": "Phase 2 — Esecuzione", + "purposeRich": "Eseguire ogni operazione. Visualizza la storia del progresso completo che si accumula sullo schermo.", + "screenRich": "Messaggi msg_info / msg_ok visibili; mai dialog." + } + ], + "principle": "Il principio è raccogliere prima tutte le scelte, poi eseguire tutto. L’utente vede prima menu ordinati con dialoghi (fase 1), poi una vista di esecuzione come un log (fase 2). Le fasi non si mescolano: niente dialoghi durante l’esecuzione né messaggi di avanzamento durante la selezione.", + "phase1Heading": "Fase 1 — modello tipico", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Regole per la fase 1:", + "phase1Rules": [ + "Se è attivo un indicatore msg_info e devi aprire un menu dialog o whiptail, chiama prima stop_spinner: l’indicatore non può convivere con l’overlay dei due strumenti. Se nessun indicatore è attivo, la chiamata non serve.", + "Usa show_proxmenux_logo + msg_title + msg_info per dare un contesto visivo a un’operazione lunga nella fase 1, ad esempio una verifica di oltre cinque secondi. La funzione pulisce già lo schermo: non chiamare prima clear.", + "Non chiamare show_proxmenux_logo tra i menu di dialogo in cui non c'è nulla da mostrare — cancellare lo schermo per un terminale vuoto è solo rumore visivo.", + "Conservare tutte le decisioni e i risultati della sonda in variabili o array paralleli. Il recap visibile avviene all'inizio della fase 2, non in fase 1." + ], + "phase2Heading": "Fase 2 — modello tipico", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Regole per la fase 2: inizia con show_proxmenux_logo + msg_title e riepiloga subito la fase 1 con righe msg_ok. Non richiamare show_proxmenux_logo, perché cancellerebbe il progresso accumulato. Non usare dialog; se una scelta durante l’esecuzione è inevitabile, usa whiptail come spiegato nella sezione seguente." + }, + "dialogVsWhiptail": { + "headingRich": "Quando utilizzare dialog vs whiptail", + "intro": "Entrambi gli strumenti disegnano interfacce utente in modalità testo, ma si comportano molto diversamente sullo schermo — e ProxMenux li utilizza in due fasi distinte per un motivo.", + "headerTool": "Strumento", + "headerWhen": "Quando usarlo", + "headerEffect": "Effetti sullo schermo", + "rows": [ + { + "toolRich": "dialog", + "whenRich": " Sempre in Fase 1. Ogni selezione interattiva — raccogliendo un VM, un disco, un filesystem, confermando un'azione — utilizza dialog. Questo è lo strumento UI predefinito di ProxMenux.", + "effectRich": "Prende il controllo del terminale: pulisce lo schermo, disegna l’interfaccia e alla chiusura ripristina lo stato precedente. Va bene nella fase 1, quando non ci sono ancora risultati utili da mostrare." + }, + { + "toolRich": "whiptail", + "whenRich": "Solo in Fase 2, solo quando inevitabile. Il caso tipico è un prompt di riavvio alla fine dell'esecuzione. Non raggiungere whiptail per chiedere “continua?” mid-execution — tale decisione dovrebbe essere presa in fase 1.", + "effectRich": "Overlay più leggero che non cancella il contesto del terminale. La cronologia msg_ok/msg_info della fase 2 resta visibile dietro il dialogo. È quindi adatto quando l’avanzamento è già sullo schermo." + } + ], + "calloutTitle": "Perché la divisione conta", + "calloutBody": "Se si chiama dialog nel mezzo della fase 2, ogni linea msg_ok l'utente sta guardando scompare. Tu pulisci la pista di controllo. whiptail lo evita. Quindi la regola non è arbitraria — si tratta di preservare la vista dell'utente di ciò che lo script ha fatto finora.", + "rebootHeading": "Richiesta di riavvio: il caso tipico di whiptail nella fase 2", + "rebootIntro": "Quando uno script termina e un riavvio può essere richiesto (ad es. IOMMU abilitato, parametri del kernel modificati), il prompt alla fine della fase 2 utilizza whiptail. Sempre includere un ramo “No” che avverte l’utente di non utilizzare la risorsa interessata fino al riavvio:", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Funzioni di messaggio", + "intro": "Tutti definiti in utils.sh. Utilizzarli come predefinito per qualsiasi output visibile dall'utente — visuali coerenti tra gli script è l'intero punto. Se il tuo script ha bisogno di una nuova funzione che non si adatta al set esistente (un nuovo livello di gravità, un nuovo aiutante di layout, ecc.), proporlo nella tua richiesta di pull — sarà rivisto e aggiunto a utils.sh se è ampiamente utile.", + "headerFunction": "Funzione", + "headerWhen": "Quando usare", + "headerSpinner": "Indicatore di avanzamento", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Operazione in corso.", + "spinner": "Inizia" + }, + { + "function": "stop_spinner", + "whenRich": "Fine del lavoro preparatorio silenzioso in Fase 1 — uccide spinner, cancella la linea.", + "spinner": "Arresta" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "L'operazione è riuscita. Utilizzare anche per “funzione abilitata” anche quando è richiesto un riavvio.", + "spinner": "Arresta" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Avvertenza effettiva o stato degradato.", + "spinner": "Arresta" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Errore fatale.", + "spinner": "Arresta" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Avviso non bloccante, su una riga informativa ciano.", + "spinner": "Arresta" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Messaggio finale «Premi Invio per tornare» alla fine della fase 2.", + "spinner": "Arresta" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Titolo in grassetto con spaziatura integrata. Usato all’inizio della fase 2.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Pulisce lo schermo e mostra il logo. Da chiamare una sola volta all’inizio della fase 2.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "convenzioni di dialogo", + "bullets": [ + "Passa sempre --backtitle \"$BACKTITLE\" ad ogni chiamata dialog e whiptail. $BACKTITLE è sempre \"ProxMenux\" — impostato una volta all'intestazione dello script e mai sovrascritto. L'utente deve sempre vedere il nome del progetto come il contesto di inquadramento, mai il titolo dello script.", + "Avvolgi sempre titoli e messaggi con $(translate \"...\").", + "Reindirizza sempre l'output dialog con 2>&1 >/dev/tty — lo stdout catturato diventa la selezione dell'utente, mentre la finestra di dialogo si disegna sul terminale.", + "Utilizzare le variabili standard di dimensione dell'interfaccia utente ($UI_MENU_H, $UI_MSG_W, ecc.) per un dimensionamento coerente tra gli script.", + "Controlla sempre le selezioni vuote o annullate e gestiscile correttamente." + ], + "exampleIntro": "Esempio completo: creare un menu di selezione della VM e gestire l’annullamento.", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Politica di traduzione", + "intro": "Tutte le stringhe visualizzabili dall'utente devono essere avvolte con la funzione translate. ProxMenux li traduce automaticamente in tutte le lingue supportate — scrivi inglese, l'utente legge la loro lingua madre.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Scrivere stringhe in inglese — la traduzione viene gestita automaticamente dalla build.", + "Mantieni concise le stringhe. Evita, se possibile, variabili all’interno di frasi lunghe.", + "non tradurre nomi di variabili, percorsi o identificatori tecnici." + ] + }, + "variableStyle": { + "heading": "Convenzioni variabili e di stile", + "bullets": [ + "Utilizzare UPPER_CASE per le variabili a livello di script.", + "Utilizzare lower_case per le variabili di funzione locali (dichiarazione con local).", + "Quotare tutte le espansioni variabili: \"$VAR\" — non $VAR.", + "Utilizzare [[ ]] per condizionali, non [ ] (eccetto dove è richiesto il POSIX).", + "show_proxmenux_logo è il modo previsto per pulire lo schermo: lo cancella e mostra il logo del progetto per dare un contesto visivo. Chiamalo una volta all’inizio della fase 2, e facoltativamente prima di una lunga attesa nella fase 1." + ], + "standardNamesIntro": "Nomi variabili standard nel progetto:", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Reindirizza l'output dello strumento durante la fase 2", + "redirectIntro": "La fase 2 mostra un log di righe msg_info → msg_ok. Se uno strumento chiamato, come apt, mkfs, qm, pct o dd, scrive su stdout/stderr, il suo output fa scorrere i messaggi e confonde la vista. L’utente non distingue più l’avanzamento dello script dai dettagli dello strumento.", + "withoutRedirectIntro": "Senza reindirizzare — ciò che l'utente vede se non si gestisce il rumore:", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": "Con redirect — quale sia la stessa operazione quando il rumore viene inviato altrove:", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Due modelli tra cui scegliere:", + "discardLead": "Scarta l’output quando non serve: è la soluzione più semplice e rapida.", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Inviare l'output a un file di registro quando si può desiderare di ispezionare in seguito (debugging un'installazione fallita, controllando ciò che dpkg effettivamente fatto). Questo è il modello preferito per qualsiasi operazione apt:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "Lo script scripts/global/update-pve9_2.sh è un'implementazione di riferimento — ogni chiamata apt-get invia l'output a un file di registro in modo che l'utente vede solo il flusso msg_info → msg_ok pulito, mentre il log su disco consente di ricostruire esattamente ciò che ha fatto apt se qualcosa va storto." + }, + "dosAndDonts": { + "heading": "Cosa fare e cosa non fare", + "doHeading": "✅ Da fare", + "doBullets": [ + "stop_spinner prima di un dialog in Fase 1 solo quando un girante msg_info è in esecuzione.", + "La fase 2 inizia con show_proxmenux_logo + msg_title + msg_ok per riepilogare i risultati della fase 1.", + "Utilizzare msg_ok per funzioni abilitate con successo, anche se è necessario un riavvio.", + "Utilizzare whiptail (non dialog) per qualsiasi prompt post-esecuzione che deve apparire in Fase 2.", + "Sempre includere un ramo “No” nelle finestre di dialogo di riavvio che avverte l’utente di non avviare la risorsa interessata fino al riavvio.", + "Limita la logica riservata alle VM verificando [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]]: controller e NVMe PCIe non possono essere assegnati a LXC con passthrough PCI.", + "Utilizzare ensure_repositories da utils-install-functions.sh invece di apt-get update incondizionato.", + "Utilizzare array paralleli in Fase 1 quando ogni elemento ha bisogno di finestre di dialogo multiple." + ], + "dontHeading": "❌ Da evitare", + "dontBullets": [ + "Chiama dialog mentre uno spinner è attivo.", + "Saltare la fase 1 recap all'inizio della fase 2.", + "Chiama show_proxmenux_logo una seconda volta — cancella tutto Fase 2 ha stampato.", + "Utilizzare dialog in Fase 2 (usare whiptail per il raro caso inevitabile).", + "Usare il solo clear.", + "Avvolgere msg_title in linee vuote echo \"\" — include già spaziatura.", + "Utilizzare msg_warn per segnalare una funzione abilitata con successo: è un msg_ok.", + "Eseguire apt-get update incondizionato — utilizzare ensure_repositories." + ] + }, + "submitting": { + "heading": "Inviare il tuo contributo", + "steps": [ + "Crea un fork di MacRimi/ProxMenux su GitHub e clonalo.", + "Crea un ramo feature/* da develop (vedi Modello dei rami sopra).", + "Segui questa guida per qualsiasi script nuovo o modificato. Scrivi l'intestazione con il tuo nome; aggiungi la linea di sponsor opzionale se vuoi.", + "Prova lo script su una vera istanza Proxmox VE: fase 1 con tutti i rami dei dialoghi e fase 2 con ogni operazione e il ripristino pulito in caso di errore.", + "Apri una pull request verso develop con una descrizione chiara: cosa fa lo script, cosa è cambiato e su quale versione di Proxmox VE è stato testato.", + "Assicurati che il contributo rispetti il Codice di condotta." + ], + "securityOutro": "Per problemi di sicurezza sensibili, segui la procedura di divulgazione in SECURITY.md invece di aprire una segnalazione pubblica." + }, + "whereNext": { + "heading": "Come proseguire", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (guida completa)", + "tail": "— la fonte di verità per ogni convenzione, compresi i modelli hardware avanzati." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Contributori", + "tail": "— le persone il cui lavoro ha plasmato le versioni ProxMenux (pagina diversa da questa)." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Codice di condotta", + "tail": "— gli standard che ogni collaboratore accetta di seguire." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": "— chiedere prima di costruire se non sei sicuro se un'idea si adatta, o trovare collaboratori su una caratteristica più grande." + } + ] + } +} diff --git a/web/messages/it/docs/about/contributors.json b/web/messages/it/docs/about/contributors.json new file mode 100644 index 00000000..3704a329 --- /dev/null +++ b/web/messages/it/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Contributori | Documentazione ProxMenux", + "description": "Un riconoscimento alle persone che rendono possibile ProxMenux: tester, revisori e sviluppatori che contribuiscono a ogni versione.", + "ogTitle": "Contributori | Documentazione ProxMenux", + "ogDescription": "Incontra i contributori dietro ProxMenux." + }, + "header": { + "title": "Contributori", + "description": "Il progetto ProxMenux cresce grazie ai suoi collaboratori. Questa pagina riconosce il loro lavoro: provare le versioni prima della pubblicazione, individuare regressioni, esaminare le PR e orientare le nuove funzionalità.", + "section": "Informazioni" + }, + "beyond": { + "title": "Oltre questa lista", + "body": "Molte altre persone contribuiscono segnalando bug, proponendo funzionalità in Discussions, provando le versioni beta e traducendo il progetto. L’elenco completo dei collaboratori al codice si trova nel grafico dei contributori di GitHub. Questa pagina presenta chi partecipa più attivamente ai test e alla revisione delle versioni di ProxMenux." + }, + "testers": { + "heading": "Tester e recensori", + "intro": "Queste persone sono state tra le prime a contribuire al progetto. I loro test, le segnalazioni di problemi e le proposte di miglioramento hanno aiutato ProxMenux a diventare ciò che è oggi.", + "roles": { + "testing": "Test", + "testingReviewer": "Test e revisione" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Vuoi contribuire?", + "intro": "Ogni contributo è benvenuto: codice, test, design, documentazione o anche solo la condivisione delle tue idee:", + "tester": "Tester — prova il canale beta e segnala ciò che funziona e ciò che non funziona prima delle versioni stabili.", + "developer": "Sviluppatore — Bash, Next.js (TypeScript) e un po’ di Python per l’ambiente virtuale di traduzione (venv). Le PR sono benvenute su GitHub.", + "designer": "Designer — Miglioramenti dell'interfaccia utente, icone, screenshot per la documentazione.", + "ideas": "Idee — apri una discussione in GitHub Discussions prima di creare una issue per richiedere una funzionalità." + }, + "coc": { + "title": "Leggi prima il codice di condotta", + "body": "Prima di proporre codice o segnalare problemi, leggi il Codice di condotta e le buone pratiche. Descrive le aspettative sulla responsabilità del codice, sulla segnalazione delle vulnerabilità e sull’ambito delle PR." + } +} diff --git a/web/messages/it/docs/about/faq.json b/web/messages/it/docs/about/faq.json new file mode 100644 index 00000000..a0252618 --- /dev/null +++ b/web/messages/it/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "FAQ | Documentazione ProxMenux", + "description": "Domande frequenti su ProxMenux: funzionalità, installazione, compatibilità da PVE 8, personalizzazione, aggiornamenti, sicurezza, uso in produzione, file di sistema, contributi e disinstallazione.", + "ogTitle": "FAQ | Documentazione ProxMenux", + "ogDescription": "Domande frequenti su installazione, compatibilità, sicurezza e modi per contribuire a ProxMenux." + }, + "header": { + "title": "Domande frequenti", + "description": "Domande comuni su ProxMenux: che cos’è, a chi serve, installazione, aggiornamento, disinstallazione, compatibilità, personalizzazione, sicurezza e contributi. Se non trovi la tua domanda, consulta GitHub Discussions o apri una issue.", + "section": "Informazioni" + }, + "quickLinks": { + "title": "Collegamenti rapidi", + "installationLabel": "Installazione", + "installationSuffix": " — il comando di installazione su una sola riga e le dipendenze.", + "introductionLabel": "Introduzione", + "introductionSuffix": " — cosa fa ProxMenux, in cinque minuti.", + "uninstallLabel": "Disinstalla", + "uninstallSuffix": "— rimozione pulita.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Discussioni" + }, + "q1": { + "question": "Che cosa è ProxMenux, e per cosa è usato?", + "p1Rich": "ProxMenux è uno strumento interattivo a menu che rende Proxmox VE accessibile a tutti, a prescindere dall’esperienza tecnica. Semplifica l’esecuzione dei comandi e consente di gestire il sistema senza conoscenze avanzate di Linux.", + "p2": "Gli utenti meno esperti possono eseguire comandi da menu strutturati, riducendo ciò che devono digitare nel terminale. Gli amministratori esperti risparmiano passaggi nelle attività ripetitive: protezione dopo l’installazione, passthrough GPU, importazione di VM OVF e rotazione degli snapshot ZFS.", + "p3": "Proxmox VE è ampiamente usato per:", + "items": [ + "Virtualizzazione di livello aziendale", + "HomeLab e soluzioni cloud personali", + "Server multimediali, automazione e altro ancora" + ] + }, + "q2": { + "question": "Come posso installare ProxMenux?", + "p1Rich": "Segui la guida all’installazione. In breve:", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "Una volta installato, basta avviarlo con:", + "menuCmd": "menu" + }, + "q3": { + "question": "ProxMenux è compatibile con tutte le versioni Proxmox?", + "bodyRich": "No — ProxMenux richiede Proxmox VE 8 o successivo. PVE 7 e le versioni precedenti non sono supportati. Se usi ancora PVE 7, passa prima a 8.x seguendo la procedura ufficiale Proxmox. Puoi poi gestire il passaggio da 8 a 9 con ProxMenux da Utilità → Aggiorna PVE 8 a PVE 9." + }, + "q4": { + "question": "Posso personalizzare ProxMenux?", + "p1Rich": "Gli aggiornamenti possono sovrascrivere le modifiche locali agli script principali: evita quindi di modificare direttamente questi file. Ecco cosa puoi personalizzare:", + "items": [ + "Il banner della console tramite lo strumento FastFetch nelle opzioni Post-Install.", + "Quali componenti opzionali sono installati (Monitor, Fail2Ban, Lynis...) — scegliere durante l'installazione o tramite il menu pertinente in seguito." + ] + }, + "q5": { + "question": "Come posso aggiornare ProxMenux?", + "p1Rich": "Quando è disponibile una nuova versione, ProxMenux la rileva al successivo avvio di menu e propone l’aggiornamento. Se accetti, sostituisce i file e la configurazione dello strumento nell’installazione esistente, senza richiedere download manuali.", + "p2Rich": "Chi usa la versione stabile riceve aggiornamenti stabili; chi usa la beta riceve quelli beta. Quando viene pubblicata una versione stabile, la beta lo segnala alla successiva apertura del menu e propone il passaggio automatico. Consulta il programma beta." + }, + "q6": { + "question": "Dove posso segnalare i problemi?", + "p1Rich": "Segnala i bug su GitHub Issues. Includi i passaggi per riprodurli, il messaggio di errore e, se pertinente, l’output di Monitor ottenuto con journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "Per i problemi di sicurezza, non pubblicare i dettagli. Segui la procedura di segnalazione responsabile nel codice di condotta e nelle buone pratiche." + }, + "q7": { + "question": "Posso contribuire a ProxMenux?", + "p1Rich": "Certamente. ProxMenux è un progetto collaborativo e open source. Puoi contribuire con codice (Bash, Next.js/TypeScript e strumenti di traduzione in Python), test, documentazione, design o idee.", + "item1Rich": "Discuti le idee in GitHub Discussions prima di aprire una richiesta di funzionalità.", + "item2Rich": "Prima di inviare una pull request, leggi il codice di condotta e le buone pratiche.", + "item3Rich": "Consulta la pagina dei collaboratori per conoscere chi si occupa attualmente di test e revisioni." + }, + "q8": { + "question": "ProxMenux modifica i file di sistema critici?", + "p1Rich": "L’installazione di ProxMenux aggiunge i seguenti componenti.", + "item1Rich": "Dipendenze tramite apt: dialog, curl, jq e git. Le traduzioni usano cataloghi JSON già generati; l’installazione non aggiunge all’host un ambiente virtuale per la traduzione.", + "item2Rich": "L'albero di script a /usr/local/share/proxmenux/.", + "item3Rich": "Il comando di avvio in /usr/local/bin/menu.", + "item4Rich": "Facoltativo: il servizio systemd di Monitor, proxmenux-monitor.service.", + "p2Rich": "ProxMenux non modifica la configurazione principale di Proxmox, a meno che tu non lo richieda esplicitamente da un menu dedicato. Per esempio, la riparazione della rete modifica /etc/network/interfaces, ma solo dopo la tua conferma e la creazione di un backup." + }, + "q9": { + "question": "È sicuro usare ProxMenux in produzione?", + "bodyRich": "Sì. Poiché ProxMenux modifica i file principali di Proxmox solo su richiesta esplicita, può essere usato in produzione. Come per ogni automazione, provalo prima in un ambiente controllato e prepara i backup prima delle operazioni distruttive: aggiornamento da PVE 8 a 9, formattazione di dischi, eliminazione di VM e simili." + }, + "q10": { + "question": "Come posso disinstallare ProxMenux?", + "p1Rich": "Da ProxMenux: Impostazioni → Disinstalla ProxMenux. La disinstallazione rimuove gli script, il comando di avvio e il servizio Monitor, oltre alle dipendenze selezionate se richiesto. Ripristina /root/.bashrc e /etc/motd dalle copie create all’installazione, se presenti.", + "p2Rich": "Procedura completa: Disinstallare ProxMenux." + }, + "q11": { + "question": "VirusTotal contrassegna l'URL di installazione — è sicuro?", + "p1Rich": "Il risultato di 1/95 del motore Chong Lua Dao, riportato nella issue #162, è stato identificato dal manutentore come falso positivo. L’installazione usa il modello curl | bash e può scaricare jq dalla sua release ufficiale su GitHub. Questo comportamento può attivare gli scanner euristici; il rapporto storico non costituisce una valutazione di ogni download futuro.", + "p2Rich": "ProxMenux è completamente open source e puoi esaminare lo script di installazione riga per riga: install_proxmenux.sh. Altri dettagli nella issue #162." + } +} diff --git a/web/messages/it/docs/about/index.json b/web/messages/it/docs/about/index.json new file mode 100644 index 00000000..1be9dc8d --- /dev/null +++ b/web/messages/it/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "Informazioni su | Documentazione ProxMenux", + "description": "Informazioni su ProxMenux: obiettivi del progetto, collaboratori, FAQ e codice di condotta. Uno strumento di gestione open source a menu per Proxmox VE 8+, sviluppato e mantenuto da MacRimi con un piccolo gruppo di tester e collaboratori.", + "ogTitle": "Informazioni su | Documentazione ProxMenux", + "ogDescription": "Informazioni su ProxMenux — strumento di gestione a menu open source per Proxmox VE 8+." + }, + "header": { + "title": "Informazioni", + "description": "Informazioni sul progetto, FAQ, contributori e codice di condotta. ProxMenux è uno strumento di gestione a menu open source per Proxmox VE 8+, costruito e mantenuto da MacRimi con l'aiuto di un piccolo team di tester e collaboratori.", + "section": "Informazioni" + }, + "callout": { + "title": "Open source, sviluppato dalla comunità", + "body": "ProxMenux è distribuito con licenza GPL-3.0 e sviluppato pubblicamente su GitHub. Ogni script può essere esaminato e ogni modifica tracciata. Sono benvenuti segnalazioni di bug, idee, codice e test." + }, + "section": { + "heading": "In questa sezione", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Domande frequenti: che cos’è ProxMenux, installazione, compatibilità, personalizzazione, aggiornamenti, sicurezza, uso in produzione e disinstallazione." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Contributori", + "description": "Un riconoscimento alle persone che rendono possibile ProxMenux: tester, revisori e sviluppatori che contribuiscono a ogni versione." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Codice di condotta", + "description": "Valori del progetto, responsabilità del codice e linee guida per contribuire. Da leggere prima di aprire una PR o segnalare un problema." + } + ] + }, + "involved": { + "heading": "Partecipa", + "intro": "Tre modi per partecipare al progetto:", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Segnala un bug", + "description": "GitHub Issues — includi i log e i passaggi per riprodurre il problema." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Suggerisci una funzionalità", + "description": "GitHub Discussions — condividi le tue idee prima di aprire una PR." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Invia una PR", + "description": "GitHub Pull Requests — leggere prima il codice di condotta." + } + ] + }, + "support": { + "heading": "Sostieni il progetto", + "introRich": "Se ProxMenux ti fa risparmiare tempo, il modo più semplice per farlo conoscere è assegnare una ⭐ al repository su GitHub. Se vuoi fare di più:", + "kofiLabel": "Supporto su Ko-fi", + "kofiOutro": " — offri un caffè al manutentore e contribuisci a portare avanti lo sviluppo." + } +} diff --git a/web/messages/pt/docs/about/code-of-conduct.json b/web/messages/pt/docs/about/code-of-conduct.json new file mode 100644 index 00000000..681e28e0 --- /dev/null +++ b/web/messages/pt/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Código de conduta | Documentação ProxMenux", + "description": "Código de conduta e boas práticas do ProxMenux para colaboradores, responsáveis pelos testes e utilizadores.", + "ogTitle": "Código de conduta | Documentação ProxMenux", + "ogDescription": "Expectativas sobre a responsabilidade pelo código, a comunicação de vulnerabilidades e o âmbito dos PR no projeto ProxMenux." + }, + "errors": { + "notFound": "Erro: O ficheiro CODE_OF_CONDUCT.md não foi encontrado.", + "loadFailed": "Erro: Não foi possível carregar o conteúdo do Código de Conduta." + }, + "contentMarkdown": "# 📝 Código de conduta e boas práticas\n\nEste documento define os **princípios e as orientações** para o uso do **ProxMenux**, promovendo o **respeito, a colaboração e a segurança** no desenvolvimento.\n\n## 📌 1. Autoria do código e reconhecimento\n\n- Se forem usados **scripts, excertos de código ou ideias** de outros repositórios ou programadores, o autor original será sempre reconhecido.\n- Será acrescentado um comentário no código ou na documentação que indique a origem e, se possível, uma ligação para o repositório original.\n- Ao usar código com uma **licença** específica, é obrigatório respeitar as suas condições.\n\n### ✅ Exemplo de atribuição correta no código:\n\n```bash\n# Script baseado no trabalho de [Autor] em [Ligação do repositório]\n```\n\n## 🔒 2. Segurança e responsabilidade pelo código\n\n- Todo o código deste repositório segue boas práticas de desenvolvimento seguro.\n- Não são permitidos contributos que introduzam vulnerabilidades intencionais ou práticas inseguras.\n- Os scripts nunca devem comprometer a segurança do sistema nem os dados dos utilizadores.\n- Antes da implementação, os contributos devem ser revistos para verificar a segurança e a estabilidade.\n\n### ⚠️ Divulgação responsável de vulnerabilidades\n\nSe descobrir uma vulnerabilidade de segurança, NÃO a comunique em issues ou fóruns públicos.\n\nComunique-a em privado por e-mail:\n\n📧 proxmenux@macrimi.pro\n\nPara mais informações sobre vulnerabilidades e como as comunicar, consulte a nossa [política de segurança](./SECURITY.md).\n\n## 🤝 3. Regras da comunidade\n\nPara manter um ambiente respeitador e profissional, todos os utilizadores devem seguir estas regras:\n\n- ✅ Seja respeitador e construtivo nas discussões.\n- ❌ Não serão tolerados assédio, discurso de ódio ou comportamentos ofensivos.\n- ✅ Dê opiniões de forma profissional, evitando ataques pessoais.\n- ❌ O incumprimento pode resultar em avisos ou exclusões, consoante a gravidade.\n\n##\n\nEste documento promove o uso ético do código, a consciência de segurança e uma comunidade respeitadora.\n\n📢 Vamos construir algo extraordinário em conjunto!\n" +} diff --git a/web/messages/pt/docs/about/contributing.json b/web/messages/pt/docs/about/contributing.json new file mode 100644 index 00000000..a18924b9 --- /dev/null +++ b/web/messages/pt/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Contribuir para ProxMenux | ProxMenux Documentação", + "description": "Como contribuir para o ProxMenux: modelo de ramos (main / develop / feature/*), processo de pull request, cabeçalho com autoria e ligação de apoio opcional, interface em duas fases, funções de mensagens e percurso até uma versão estável.", + "ogTitle": "Contribuição para ProxMenux", + "ogDescription": "Modelo de ramos, processo, autoria no cabeçalho e regra da interface em duas fases a seguir pelos colaboradores." + }, + "header": { + "title": "Contribuição para ProxMenux", + "description": "O ProxMenux está aberto a contribuições comunitárias — novos scripts, correções para os existentes, melhorias de diálogo, traduções, integrações. Esta página é a porta formal: como o repositório é estruturado, o modelo de ramificação e fluxo de trabalho pull-request, o cabeçalho do script com atribuição do autor e link de patrocinador opcional, e as convenções de projeto cada contribuição deve seguir.", + "section": "Sobre" + }, + "twoPagesCallout": { + "title": "Duas páginas relacionadas, um projecto", + "body": "Não confunda esta página com Colaboradores, que reconhece testadores, revisores e programadores já envolvidos. Esta página é para si, futuro colaborador, e explica como preparar uma pull request para integração." + }, + "branching": { + "heading": "Modelo de ramificação", + "intro": "ProxMenux usa um modelo de ramificação de três camadas:", + "headerBranch": "Ramo", + "headerPurpose": "Finalidade", + "rows": [ + { + "branch": "main", + "purposeRich": "Código estável, pronto para produção. Só são integradas aqui alterações prontas para lançamento. É a versão recebida numa instalação normal do ProxMenux." + }, + { + "branch": "develop", + "purposeRich": "Ramo de desenvolvimento ativo. O trabalho novo é integrado primeiro aqui: é o canal beta. Quando uma versão estável está pronta, passa daqui para main." + }, + { + "branch": "feature/*", + "purposeRich": "Ramos de curta duração por recurso, correção ou melhoria. Criado a partir de develop, fundiu-se novamente em develop através de um Pull Request após revisão." + } + ], + "calloutTitle": "O que isto significa para os utilizadores", + "calloutBody": "Usuários finais rastreando main recebem versões testadas. Usuários que querem os recursos mais recentes cedo — e não se importem com as bordas ásperas ocasionais — podem seguir o develop. Novos recursos sempre passam por develop primeiro; nada atinge main sem passar por esse ciclo." + }, + "workflow": { + "heading": "Processo de pull request", + "intro": "Da ideia ao lançamento mesclado em cinco etapas:", + "step1Lead": "Criar um ramo de recursos de develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Use um nome de ramo descritivo: feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Trabalhe nas suas alterações e empurre o branch:", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Abra um pedido de pull contra develop (não contra main). Na página de criação de GitHub PR, verifique novamente se o ramo base está definido como develop. Incluir na descrição: o que o script faz, o que ele muda, e em quais versões Proxmox VE você testou.", + "step4": "Após a revisão, as alterações são integradas em develop. Um responsável verifica o código, o cabeçalho e a interface em duas fases. Se pedir alterações, envie novos commits para o mesmo ramo: a PR atualiza-se automaticamente.", + "step5": "As versões estáveis passam de develop para main. No lançamento, main avança até develop por fast-forward e recebe uma etiqueta. A contribuição passa a integrar a próxima versão estável." + }, + "scriptHeader": { + "heading": "Cabeçalho do programa — metadados e descrição", + "intro": "Cada script em ProxMenux abre com dois blocos de comentários adjacentes que juntos formam o cabeçalho. Eles são ambos necessários — juntos eles deixam qualquer leitor saber quem escreveu o script e o que ele faz, tudo sem abrir o próprio código.", + "bullets": [ + "Bloco superior: metadados. Autor, ligações GitHub/de apoio opcionais, responsável, copyright, licença, versão e data de atualização. É também o reconhecimento dos colaboradores: num script novo, inclua o seu nome e, se quiser, a página GitHub e um perfil como Ko-fi, GitHub Sponsors ou Buy Me a Coffee.", + "Bloco inferior: descrição. Um parágrafo curto em inglês simples explica o script. É lido antes do código e deve bastar para compreender o objetivo, ações principais, recursos afetados e requisitos." + ], + "licenseCalloutTitle": "A linha de licença é fixa — GPL-3.0", + "licenseCalloutBody": "ProxMenux é publicado sob a Licença Pública Geral GNU v3.0. Cada script no projeto envia sob essa mesma licença; a linha License no cabeçalho é sempre a referência GPL-3.0 mostrada no exemplo abaixo — não é uma escolha por script. Ao contribuir com um script, você concorda em lançá-lo sob GPL-3.0, o que significa que qualquer um pode lê-lo, modificá-lo e redistribuí-lo (incluindo modificações), desde que o mantenha sob a mesma licença. O texto completo vive em MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "As linhas GitHub e Sponsor são opcionais — deixe-as de fora se não quiser publicá-las. Tudo o resto é necessário.", + "whyCalloutTitle": "Por que isso importa?", + "whyCalloutBody": "A contribuição de código aberto é trabalho voluntário. Colocar o nome do contribuinte e (quando fornecido) o link do patrocinador diretamente na fonte dá às pessoas que constroem os scripts reconhecimento visível cada vez que alguém lê o código — sua autoria é preservada, não escondida sob “o projeto”. O bloco de descrição importa por uma razão diferente: é a primeira coisa que um futuro mantenedor ou usuário curioso lê, e uma descrição clara é muitas vezes a diferença entre um script que as pessoas confiam e uma que elas evitam." + }, + "structure": { + "heading": "Estrutura do projecto", + "intro": "Onde cada tipo de script vive no repositório:", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Cada script carrega utils.sh no arranque através de source para obter funções de mensagens, indicador de espera, cores e sistema de tradução. As bibliotecas partilhadas em scripts/global/ são carregadas explicitamente pelos scripts que delas precisam." + }, + "twoPhase": { + "heading": "A política de design de IU em duas fases", + "intro": "É a convenção mais importante do ProxMenux e a que mais frequentemente leva a pedidos de correção nas PR. Cada script divide-se em exatamente duas fases:", + "headerPhase": "Fase", + "headerPurpose": "Finalidade", + "headerScreen": "Estado da tela", + "rows": [ + { + "phaseRich": "Fase 1 — Selecção", + "purposeRich": "Recolha todas as decisões do utilizador. Executar todos os trabalhos preparatórios necessários (sondas, sondagens, verificações).", + "screenRich": "Janelas dialog. Se uma verificação entre dois menus demorar mais de um ou dois segundos, mostre um indicador msg_info para sinalizar que o script não bloqueou, e chame stop_spinner imediatamente antes do próximo diálogo." + }, + { + "phaseRich": "Fase 2 — Execução", + "purposeRich": "Executar todas as operações. Mostra o histórico de progresso completo acumulado na tela.", + "screenRich": "Mensagens visíveis msg_info / msg_ok; nunca dialog." + } + ], + "principle": "O princípio: colha tudo primeiro, depois executa tudo. O usuário vê um menu de diálogo limpo (Fase 1), em seguida, uma visão de execução de log-style limpo (Fase 2). Nenhuma mistura — nenhuma janela que apareça no meio da execução, nenhum ruído de progresso durante a selecção.", + "phase1Heading": "Fase 1 — padrão típico", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Regras para fase 1:", + "phase1Rules": [ + "Se estiver ativo um indicador msg_info e precisar de abrir um menu dialog ou whiptail, chame primeiro stop_spinner: o indicador não pode coexistir com a sobreposição das ferramentas. Se nenhum estiver ativo, a chamada não é necessária.", + "Use show_proxmenux_logo + msg_title + msg_info quando você precisa dar ao usuário contexto visual para uma operação de longa duração em fase 1 (por exemplo, uma sonda que leva 5+ segundos). A função inclui uma tela limpa, então não chame clear antes.", + "Não chame o show_proxmenux_logo entre menus de diálogo onde não há nada para exibir — limpar a tela para um terminal vazio é apenas ruído visual.", + "Armazene todas as decisões e resultados de sonda em variáveis ou arrays paralelos. A recapitulação visível ocorre no início da Fase 2, não na Fase 1." + ], + "phase2Heading": "Fase 2 — padrão típico", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Regras para a fase 2: comece com show_proxmenux_logo + msg_title e resuma de imediato a fase 1 em linhas msg_ok. Não volte a chamar show_proxmenux_logo, pois apagaria o progresso acumulado. Não utilize dialog; se uma decisão durante a execução for inevitável, utilize whiptail como na secção seguinte." + }, + "dialogVsWhiptail": { + "headingRich": "Quando usar dialog vs whiptail", + "intro": "Ambas as ferramentas desenham interfaces de usuário de modo texto, mas elas se comportam de forma muito diferente na tela — e ProxMenux as usa em duas fases distintas por uma razão.", + "headerTool": "Ferramenta", + "headerWhen": "Quando usá-lo", + "headerEffect": "Efeito no ecrã", + "rows": [ + { + "toolRich": "dialog", + "whenRich": "Sempre em fase 1. Cada seleção interativa — escolhendo um VM, um disco, um sistema de arquivos, confirmando uma ação — usa dialog. Esta é a ferramenta padrão de UI do ProxMenux.", + "effectRich": "Assume o terminal: limpa a tela, desenha sua sobreposição, e ao fechar retorna o terminal ao seu estado anterior. Tudo bem na fase 1 porque nada de útil está mostrando ainda." + }, + { + "toolRich": "whiptail", + "whenRich": "Apenas na Fase 2, somente quando inevitável. O caso típico é um prompt de reinicialização no final da execução. Não procure o whiptail para pedir “continuar?” no meio da execução — essa decisão deveria ter sido tomada na Fase 1.", + "effectRich": "Sobreposição mais leve que não limpa o contexto do terminal. O histórico msg_ok/msg_info da fase 2 permanece visível atrás do diálogo. É por isso a escolha indicada quando o progresso já está no ecrã." + } + ], + "calloutTitle": "Por que a divisão importa", + "calloutBody": "Se você chamar dialog no meio da fase 2, cada linha msg_ok que o usuário tem assistido desaparece. Limpas o rasto da auditoria. whiptail evita isso. Então a regra não é arbitrária — é sobre preservar a visão do usuário sobre o que o script tem feito até agora.", + "rebootHeading": "Pedido de reinício: o caso típico de whiptail na fase 2", + "rebootIntro": "Quando um script termina e uma reinicialização pode ser necessária (por exemplo: IOMMU habilitado, parâmetros do kernel alterados), o prompt no final da Fase 2 usa whiptail. Sempre inclua um branch “Não” que avisa o usuário para não usar o recurso afetado até que ele reinicie:", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Referência das funções da mensagem", + "intro": "Todos definidos em utils.sh. Use-os como padrão para qualquer saída visível pelo usuário — visual consistente entre scripts é o ponto principal. Se seu script precisar de uma nova função que não se encaixe no conjunto existente (um novo nível de gravidade, um novo auxiliar de layout, etc.), proponha-o em seu pedido de pull — ele será revisado e adicionado ao utils.sh se for amplamente útil.", + "headerFunction": "Função", + "headerWhen": "Quando utilizar", + "headerSpinner": "Indicador de progresso", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Operação em curso.", + "spinner": "Inicia" + }, + { + "function": "stop_spinner", + "whenRich": "Fim dos trabalhos preparatórios silenciosos na fase 1 — mata spinner, limpa a linha.", + "spinner": "Para" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "Operação concluída. Utilize também para uma funcionalidade ativada, mesmo que ainda seja necessário reiniciar.", + "spinner": "Para" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Aviso real ou estado degradado.", + "spinner": "Para" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Erro fatal.", + "spinner": "Para" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Informação sem bloqueio, numa linha ciano.", + "spinner": "Para" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Final “Pressione Enter para retornar” no final da fase 2.", + "spinner": "Para" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Título negrito com espaçamento embutido. Utilizado no início da fase 2.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Limpa o ecrã, mostra o logótipo. Chamado uma vez apenas no início da Fase 2.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "convenções de diálogo", + "bullets": [ + "Passe sempre --backtitle \"$BACKTITLE\" para cada chamada dialog e whiptail. $BACKTITLE é sempre \"ProxMenux\" — definido uma vez no cabeçalho do script e nunca substituído. O usuário deve sempre ver o nome do projeto como o contexto de enquadramento, nunca o próprio título do script.", + "Enrole sempre títulos e mensagens com $(translate \"...\").", + "Sempre redirecionar saída dialog com 2>&1 >/dev/tty - o stdout capturado torna-se a seleção do usuário, enquanto o diálogo em si desenha no terminal.", + "Use as variáveis de dimensão padrão de UI ($UI_MENU_H, $UI_MSG_W, etc.) para dimensionamento consistente entre scripts.", + "Verifique sempre escolhas vazias ou canceladas e trate-as corretamente." + ], + "exampleIntro": "Exemplo completo: criar um menu de seleção de VM e tratar o cancelamento.", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Política de tradução", + "intro": "Todas as strings visíveis ao usuário devem ser enroladas com a função translate. ProxMenux traduz-os automaticamente em todas as línguas suportadas — você escreve inglês, o usuário lê sua língua nativa.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Escreva strings em inglês — a tradução é tratada automaticamente pela compilação.", + "Mantenha as cadeias de texto concisas. Evite, se possível, variáveis dentro de frases longas.", + "Não traduza nomes de variáveis, caminhos ou identificadores técnicos." + ] + }, + "variableStyle": { + "heading": "Convenções de variáveis e estilo", + "bullets": [ + "Usar UPPER_CASE para variáveis de nível de script.", + "Use lower_case para variáveis de função local (declare com local).", + "Coloque todas as expansões de variáveis entre aspas: \"$VAR\", não $VAR.", + "Usar [[ ]] para condicionantes, não [ ] (exceto onde POSIX é necessário).", + "show_proxmenux_logo é a forma prevista de limpar o ecrã: limpa-o e mostra o logótipo do projeto para manter o contexto visual. Chame-o uma vez no início da fase 2, e opcionalmente antes de uma espera longa na fase 1." + ], + "standardNamesIntro": "Nomes de variáveis padrão em todo o projeto:", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Redirecionando a saída da ferramenta durante a fase 2", + "redirectIntro": "A fase 2 mostra um registo de linhas msg_info → msg_ok. Se uma ferramenta chamada, como apt, mkfs, qm, pct ou dd, escrever em stdout/stderr, a sua saída desloca as mensagens e confunde o ecrã. O utilizador deixa de distinguir o progresso do script dos detalhes da ferramenta.", + "withoutRedirectIntro": "Sem redirecionar — o que o usuário vê se você não lidar com o ruído:", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": "Com redirecionamento — como é a mesma operação quando o ruído é enviado para outro lugar:", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Dois padrões para escolher:", + "discardLead": "Descarte a saída quando você não precisar — mais rápido, mais simples:", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Envie a saída para um arquivo de log quando você quiser inspeccioná-lo mais tarde (depurando uma instalação falhada, verificando o que o dpkg realmente fez). Este é o padrão preferido para qualquer operação apt:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "O script scripts/global/update-pve9_2.sh é uma implementação de referência — cada chamada apt-get envia saída para um arquivo de log para que o usuário apenas veja o fluxo limpo msg_info → msg_ok, enquanto o log no disco permite reconstruir exatamente o que o apt fez se algo der errado." + }, + "dosAndDonts": { + "heading": "Fazer e Não Fazer", + "doHeading": "✅ Fazer", + "doBullets": [ + "stop_spinner antes de um dialog em fase 1 somente quando um spinner msg_info está em execução atualmente.", + "Fase 2 começa com show_proxmenux_logo + msg_title + msg_ok recap dos resultados da Fase 1.", + "Use msg_ok para recursos habilitados com sucesso — mesmo que seja necessário reiniciar.", + "Use whiptail (não dialog) para qualquer prompt pós-execução que deve aparecer na Fase 2.", + "Sempre inclua um branch \"Não\" nas janelas de reinicialização que alerta o usuário para não iniciar o recurso afetado até reiniciar.", + "Limite a lógica exclusiva de VM verificando [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]]: controladores e NVMe PCIe não podem ser atribuídos a LXC por passthrough PCI.", + "Utilizar ensure_repositories de utils-install-functions.sh em vez de apt-get update incondicional.", + "Use arrays paralelos na Fase 1 quando cada item precisar de múltiplas janelas." + ], + "dontHeading": "❌ Não fazer", + "dontBullets": [ + "Chame dialog enquanto um spinner está ativo.", + "Pular a fase 1 recap no início da fase 2.", + "Chame o show_proxmenux_logo pela segunda vez — ele apaga tudo que a Fase 2 imprimiu.", + "Utilizar dialog na Fase 2 (utilizar whiptail para o caso raro inevitável).", + "Utilizar apenas clear.", + "Enrole msg_title em linhas em branco echo \"\" — já inclui espaçamento.", + "Use o msg_warn para relatar um recurso habilitado com sucesso — que é um msg_ok.", + "Executar apt-get update incondicional — utilizar ensure_repositories." + ] + }, + "submitting": { + "heading": "Submissão da sua contribuição", + "steps": [ + "Crie um fork de MacRimi/ProxMenux no GitHub e clone esse fork.", + "Crie um ramo feature/* a partir de develop (veja Modelo de ramos acima).", + "Siga este guia para quaisquer scripts novos ou modificados. Escreva o cabeçalho com o seu nome; adicione a linha de patrocinador opcional se quiser.", + "Teste o script numa instância real do Proxmox VE: fase 1 com todos os ramos dos diálogos e fase 2 com cada operação e a reversão correta em caso de erro.", + "Abra um Pull Request contra develop com uma descrição clara: o que o script faz, o que mudou, em que versão Proxmox VE foi testado.", + "Certifique-se de que sua contribuição respeite o Código de Conduta." + ], + "securityOutro": "Para questões sensíveis à segurança, siga o fluxo de divulgação em SECURITY.md em vez de abrir um problema público." + }, + "whereNext": { + "heading": "Para onde seguir", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (guia completo)", + "tail": " — a fonte da verdade para cada convenção, incluindo os padrões avançados de hardware." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Contribuidores", + "tail": " — as pessoas cuja obra moldou os lançamentos do ProxMenux (página diferente deste)." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Código de Conduta", + "tail": " — as normas que cada contribuinte aceita respeitar." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": " — pergunte antes de você construir se você não tem certeza se uma ideia se encaixa, ou para encontrar colaboradores em um recurso maior." + } + ] + } +} diff --git a/web/messages/pt/docs/about/contributors.json b/web/messages/pt/docs/about/contributors.json new file mode 100644 index 00000000..c7d3a8e7 --- /dev/null +++ b/web/messages/pt/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Colaboradores | Documentação ProxMenux", + "description": "Reconhecimento às pessoas que tornam o ProxMenux possível: quem testa, revê e desenvolve cada versão.", + "ogTitle": "Colaboradores | Documentação ProxMenux", + "ogDescription": "Conheça os colaboradores por trás do ProxMenux." + }, + "header": { + "title": "Colaboradores", + "description": "O projeto ProxMenux cresce graças aos seus colaboradores. Esta página reconhece o seu trabalho: testar versões antes da publicação, detetar regressões, rever PR e ajudar a orientar novas funcionalidades.", + "section": "Sobre" + }, + "beyond": { + "title": "Para além desta lista", + "body": "Muitas outras pessoas contribuem ao comunicar erros, propor funcionalidades em Discussions, testar versões beta e traduzir o projeto. A lista completa de colaboradores do código está no gráfico de colaboradores do GitHub. Esta página destaca quem participa mais ativamente nos testes e na revisão das versões do ProxMenux." + }, + "testers": { + "heading": "Testadores e revisores", + "intro": "Estas pessoas estiveram entre as primeiras a contribuir para o projeto. Os seus testes, relatos de problemas e sugestões de melhoria ajudaram o ProxMenux a tornar-se no que é hoje.", + "roles": { + "testing": "Teste", + "testingReviewer": "Testes e revisão" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Queres contribuir?", + "intro": "Todos os contributos são bem-vindos: código, testes, design, documentação ou simplesmente partilhar ideias:", + "tester": "Testes — experimente o canal beta e comunique o que funciona ou não antes da publicação das versões estáveis.", + "developer": "Desenvolvimento — Bash, Next.js (TypeScript) e algum Python para o ambiente virtual de tradução (venv). Os PR são bem-vindos no GitHub.", + "designer": "Designer — Melhorias de interface, ícones, capturas de tela para documentação.", + "ideas": "Ideias — abra um tópico em GitHub Discussions antes de criar uma issue para pedir uma funcionalidade." + }, + "coc": { + "title": "Leia primeiro o Código de Conduta", + "body": "Antes de propor código ou comunicar problemas, leia o Código de conduta e as boas práticas. Explica as expectativas sobre a responsabilidade pelo código, a comunicação de vulnerabilidades e o âmbito dos PR." + } +} diff --git a/web/messages/pt/docs/about/faq.json b/web/messages/pt/docs/about/faq.json new file mode 100644 index 00000000..ba153648 --- /dev/null +++ b/web/messages/pt/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Perguntas frequentes | Documentação ProxMenux", + "description": "Perguntas frequentes sobre o ProxMenux: funcionalidades, instalação, compatibilidade a partir do PVE 8, personalização, atualizações, segurança, uso em produção, ficheiros do sistema, contributos e desinstalação.", + "ogTitle": "Perguntas frequentes | Documentação ProxMenux", + "ogDescription": "Perguntas frequentes sobre instalação, compatibilidade, segurança e contribuição do ProxMenux." + }, + "header": { + "title": "Perguntas frequentes", + "description": "Perguntas comuns sobre o ProxMenux: o que é, a quem se destina, instalação, atualização, desinstalação, compatibilidade, personalização, segurança e contributos. Se não encontrar a sua pergunta, consulte GitHub Discussions ou abra uma issue.", + "section": "Sobre" + }, + "quickLinks": { + "title": "Ligações rápidas", + "installationLabel": "Instalação", + "installationSuffix": " — o comando de instalação de uma linha + dependências.", + "introductionLabel": "Introdução", + "introductionSuffix": " — o que faz ProxMenux, em 5 minutos.", + "uninstallLabel": "Desinstalar", + "uninstallSuffix": " — remoção limpa.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Discussões" + }, + "q1": { + "question": "O que é o ProxMenux e para que é utilizado?", + "p1Rich": "ProxMenux é uma ferramenta de menu interativa projetada para tornar o Proxmox VE acessível a todos os usuários, independentemente de sua experiência técnica. Ele simplifica a execução de comandos, permitindo que os usuários executem ações em seu sistema sem exigir conhecimento avançado do Linux.", + "p2": "Os utilizadores menos experientes executam comandos através de menus estruturados, reduzindo a escrita manual no terminal. Os administradores experientes poupam passos em tarefas repetitivas: reforço de segurança após a instalação, passthrough de GPU, importação de VM OVF e rotação de snapshots ZFS.", + "p3": "Proxmox VE é amplamente utilizado para:", + "items": [ + "Virtualização de nível empresarial", + "HomeLab e soluções de nuvem pessoal", + "Servidores multimídia, automação e muito mais" + ] + }, + "q2": { + "question": "Como faço para instalar ProxMenux?", + "p1Rich": "Siga o guia de instalação. Em resumo:", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "Uma vez instalado, basta iniciar com:", + "menuCmd": "menu" + }, + "q3": { + "question": "O ProxMenux é compatível com todas as versões do Proxmox?", + "bodyRich": "Não — o ProxMenux requer Proxmox VE 8 ou posterior. O PVE 7 e as versões anteriores não são suportados. Se ainda usa o PVE 7, atualize primeiro para 8.x segundo o procedimento oficial do Proxmox. Depois, o ProxMenux pode tratar da passagem de 8 para 9 em Utilitários → Atualizar PVE 8 para PVE 9." + }, + "q4": { + "question": "Posso personalizar ProxMenux?", + "p1Rich": "As atualizações podem substituir alterações locais aos scripts principais, pelo que deve evitar editar esses ficheiros diretamente. O que pode personalizar:", + "items": [ + "O banner do console através da ferramenta FastFetch nas opções de Post-Install.", + "Quais componentes opcionais são instalados (Monitor, Fail2Ban, Lynis...) — escolha durante a instalação ou através do menu relevante mais tarde." + ] + }, + "q5": { + "question": "Como faço para atualizar o ProxMenux?", + "p1Rich": "Quando há uma nova versão, o ProxMenux deteta-a na próxima execução de menu e propõe a atualização. Se aceitar, os ficheiros e a configuração da ferramenta são substituídos na instalação existente, sem downloads manuais.", + "p2Rich": "Os utilizadores da versão estável recebem atualizações estáveis; os da beta recebem atualizações beta. Quando é publicada uma versão estável, a beta avisa na próxima abertura do menu e propõe a mudança automática. Consulte o programa beta." + }, + "q6": { + "question": "Onde posso comunicar problemas ou sugerir funcionalidades?", + "p1Rich": "Comunique erros em GitHub Issues. Inclua os passos para os reproduzir, a mensagem de erro e, quando relevante, a saída do Monitor obtida com journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "No caso de problemas de segurança, não publique os detalhes. Siga o procedimento de divulgação responsável no código de conduta e boas práticas." + }, + "q7": { + "question": "Posso contribuir para o ProxMenux?", + "p1Rich": "Claro. O ProxMenux é um projeto colaborativo e de código aberto. Pode contribuir com código (Bash, Next.js/TypeScript e ferramentas de tradução em Python), testes, documentação, design ou ideias.", + "item1Rich": "Discuta as ideias em GitHub Discussions antes de abrir um pedido de funcionalidade.", + "item2Rich": "Antes de enviar uma pull request, leia o código de conduta e boas práticas.", + "item3Rich": "Consulte a página de colaboradores para saber quem participa atualmente nos testes e revisões." + }, + "q8": { + "question": "ProxMenux modifica arquivos críticos do sistema?", + "p1Rich": "A instalação do ProxMenux adiciona os seguintes componentes.", + "item1Rich": "Dependências via apt: dialog, curl, jq e git. As traduções usam catálogos JSON pré-gerados; o instalador não adiciona ao anfitrião um ambiente virtual de tradução.", + "item2Rich": "A árvore de scripts em /usr/local/share/proxmenux/.", + "item3Rich": "O comando de arranque em /usr/local/bin/menu.", + "item4Rich": "Opcional: o serviço systemd do Monitor, proxmenux-monitor.service.", + "p2Rich": "O ProxMenux não altera a configuração principal do Proxmox, salvo se o pedir explicitamente num menu específico. Por exemplo, a reparação de rede edita /etc/network/interfaces, mas apenas após a sua confirmação e a criação de uma cópia de segurança." + }, + "q9": { + "question": "É seguro usar ProxMenux na produção?", + "bodyRich": "Sim. Como o ProxMenux só altera os ficheiros principais do Proxmox quando solicitado explicitamente, pode ser usado em ambientes de produção. Como em qualquer automação, teste primeiro num ambiente controlado e tenha cópias de segurança antes de operações destrutivas: atualização do PVE 8 para 9, formatação de discos, eliminação de VM, entre outras." + }, + "q10": { + "question": "Como faço para desinstalar ProxMenux?", + "p1Rich": "No ProxMenux: Definições → Desinstalar ProxMenux. A desinstalação remove os scripts, o comando de arranque e o serviço Monitor, além das dependências selecionadas, se assim escolher. Restaura /root/.bashrc e /etc/motd a partir das cópias criadas na instalação, caso existam.", + "p2Rich": "Passo a passo completo: Desinstalar ProxMenux." + }, + "q11": { + "question": "VirusTotal sinaliza o URL de instalação — é seguro?", + "p1Rich": "O resultado de 1/95 do motor Chong Lua Dao, comunicado na issue #162, foi identificado pelo responsável do projeto como um falso positivo. O instalador usa o padrão curl | bash e pode descarregar jq da sua versão oficial no GitHub. Este comportamento pode ativar análises heurísticas; esse relatório histórico não avalia todos os downloads futuros.", + "p2Rich": "O ProxMenux é totalmente de código aberto e pode analisar o script de instalação linha a linha: install_proxmenux.sh. Mais contexto na issue #162." + } +} diff --git a/web/messages/pt/docs/about/index.json b/web/messages/pt/docs/about/index.json new file mode 100644 index 00000000..47bd27c2 --- /dev/null +++ b/web/messages/pt/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "Sobre o projeto | Documentação ProxMenux", + "description": "Sobre o ProxMenux: objetivos do projeto, colaboradores, FAQ e Código de Conduta. Ferramenta de gerenciamento orientada por menus de código aberto para Proxmox VE 8+, construída e mantida pela MacRimi com uma pequena equipe de testadores e colaboradores.", + "ogTitle": "Sobre o projeto | Documentação ProxMenux", + "ogDescription": "Sobre ProxMenux — ferramenta de gestão orientada por menus de código aberto para Proxmox VE 8+." + }, + "header": { + "title": "Sobre", + "description": "Informações do projeto, perguntas frequentes, colaboradores e código de conduta. O ProxMenux é uma ferramenta de gestão por menus, de código aberto, para Proxmox VE 8+, desenvolvida e mantida por MacRimi com a ajuda de uma pequena equipa de testes e colaboradores.", + "section": "Sobre" + }, + "callout": { + "title": "Código aberto, baseado na comunidade", + "body": "O ProxMenux é distribuído sob a licença GPL-3.0 e desenvolvido publicamente no GitHub. Cada script pode ser revisto e cada alteração rastreada. São bem-vindos relatos de erros, ideias, código e testes." + }, + "section": { + "heading": "Nesta secção", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Perguntas frequentes: o que é o ProxMenux, instalação, compatibilidade, personalização, atualizações, segurança, uso em produção e desinstalação." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Colaboradores", + "description": "Reconhecimento às pessoas que tornam o ProxMenux possível: quem testa, revê e desenvolve cada versão." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Código de Conduta", + "description": "Valores do projeto, expectativas de responsabilidade de código e diretrizes de contribuição. Leitura necessária antes de abrir um PR ou relatório." + } + ] + }, + "involved": { + "heading": "Participe", + "intro": "Três lugares para se envolver com o projeto:", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Comunicar um erro", + "description": "GitHub Issues — inclua os registos e os passos para reproduzir o problema." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Sugerir uma funcionalidade", + "description": "GitHub Discussions — partilhe ideias antes de abrir um PR." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Enviar um PR", + "description": "GitHub Pull Requests — leia primeiro o Código de Conduta." + } + ] + }, + "support": { + "heading": "Apoiar o projecto", + "introRich": "Se o ProxMenux lhe poupa tempo, a forma mais simples de o dar a conhecer é atribuir uma ⭐ ao repositório no GitHub. Se quiser ir mais longe:", + "kofiLabel": "Apoio em Ko-fi", + "kofiOutro": " — compra um café ao mantenedor e mantém o desenvolvimento." + } +} diff --git a/web/messages/sk/docs/about/code-of-conduct.json b/web/messages/sk/docs/about/code-of-conduct.json new file mode 100644 index 00000000..e46fc339 --- /dev/null +++ b/web/messages/sk/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Pravidlá správania | Dokumentácia ProxMenux", + "description": "Kódex správania ProxMenux a najlepšie postupy pre prispievateľov, testerov a používateľov.", + "ogTitle": "Pravidlá správania | Dokumentácia ProxMenux", + "ogDescription": "Očakávania týkajúce sa zodpovednosti za kód, oznamovania bezpečnostných chýb a rozsahu PR v projekte ProxMenux." + }, + "errors": { + "notFound": "Chyba: Súbor CODE_OF_CONDUCT.md nebol nájdený.", + "loadFailed": "Chyba: Nemožno načítať obsah kódexu správania." + }, + "contentMarkdown": "# 📝 Kódex správania a odporúčané postupy\n\nTento dokument stanovuje **zásady a usmernenia** na používanie **ProxMenuxu** s cieľom podporovať **rešpekt, spoluprácu a bezpečnosť** pri vývoji.\n\n## 📌 1. Uvedenie autorstva kódu a uznanie\n\n- Pri použití **skriptov, úryvkov kódu alebo nápadov** z iných repozitárov či od iných vývojárov bude vždy uvedený pôvodný autor.\n- V kóde alebo dokumentácii bude komentár s uvedením zdroja a podľa možnosti s odkazom na pôvodný repozitár.\n- Pri použití kódu s konkrétnou **licenciou** je potrebné dodržať jej podmienky.\n\n### ✅ Príklad správneho uvedenia zdroja v kóde:\n\n```bash\n# Skript vychádza z práce [Autor] v [Odkaz na repozitár]\n```\n\n## 🔒 2. Bezpečnosť a zodpovednosť za kód\n\n- Všetok kód v tomto repozitári dodržiava odporúčané postupy bezpečného vývoja.\n- Príspevky, ktoré úmyselne zavádzajú zraniteľnosti alebo nebezpečné postupy, nie sú povolené.\n- Skripty nikdy nesmú ohroziť bezpečnosť systému ani údaje používateľov.\n- Pred implementáciou je potrebné príspevky skontrolovať z hľadiska bezpečnosti a stability.\n\n### ⚠️ Zodpovedné nahlasovanie zraniteľností\n\nAk objavíte bezpečnostnú zraniteľnosť, NEHLÁSTE ju vo verejných issues ani na fórach.\n\nNamiesto toho ju nahláste súkromne e-mailom:\n\n📧 proxmenux@macrimi.pro\n\nPodrobnosti o bezpečnostných zraniteľnostiach a ich nahlasovaní nájdete v našich [bezpečnostných zásadách](./SECURITY.md).\n\n## 🤝 3. Pravidlá komunity\n\nNa zachovanie rešpektujúceho a profesionálneho prostredia musia všetci používatelia dodržiavať tieto pravidlá:\n\n- ✅ V diskusiách buďte úctiví a konštruktívni.\n- ❌ Obťažovanie, nenávistné prejavy ani urážlivé správanie sa nebudú tolerovať.\n- ✅ Spätnú väzbu poskytujte profesionálne a bez osobných útokov.\n- ❌ Porušenie pravidiel môže podľa závažnosti viesť k upozorneniu alebo vylúčeniu.\n\n##\n\nTento dokument podporuje etické používanie kódu, povedomie o bezpečnosti a rešpektujúcu komunitu.\n\n📢 Vytvorme spolu niečo skvelé!\n" +} diff --git a/web/messages/sk/docs/about/contributing.json b/web/messages/sk/docs/about/contributing.json new file mode 100644 index 00000000..b92d38f7 --- /dev/null +++ b/web/messages/sk/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Prispievanie k ProxMenux | ProxMenux Dokumentácia", + "description": "Ako prispieť do ProxMenux: model vetiev (main / develop / feature/*), postup pull requestu, hlavička skriptu s autorstvom a voliteľným odkazom na podporu, dvojfázové rozhranie, funkcie správ a cesta od vetvy funkcie po stabilné vydanie.", + "ogTitle": "Prispievanie k ProxMenux", + "ogDescription": "Model vetiev, pracovný postup, autorstvo v hlavičke a dvojfázové rozhranie, ktoré musia prispievatelia dodržiavať." + }, + "header": { + "title": "Prispievanie k ProxMenux", + "description": "ProxMenux víta príspevky komunity: nové skripty, opravy existujúcich, vylepšenia dialógov, preklady aj integrácie. Táto stránka vysvetľuje štruktúru repozitára, model vetiev a postup pull requestu, hlavičku skriptu s autorstvom a voliteľným odkazom na podporu aj pravidlá návrhu pre každý príspevok.", + "section": "O projekte" + }, + "twoPagesCallout": { + "title": "Dve súvisiace stránky, jeden projekt", + "body": "Nemýľte si túto stránku s Prispievateľmi: tá oslavuje ľudí, ktorí už prispievajú (testeri, recenzenti, vývojári). Táto stránka je pre vás – potenciálneho prispievateľa – a vysvetľuje, ako poslať PR, ktoré bude zlúčené." + }, + "branching": { + "heading": "Model vetiev", + "intro": "ProxMenux používa trojstupňový vetviaci model:", + "headerBranch": "Vetva", + "headerPurpose": "Účel", + "rows": [ + { + "branch": "main", + "purposeRich": "Stabilný kód pripravený na produkciu. Sem sa začleňujú iba zmeny pripravené na vydanie. Túto verziu dostanú používatelia pri bežnej inštalácii ProxMenux." + }, + { + "branch": "develop", + "purposeRich": "Aktívna vývojová vetva. Všetky nové zmeny sa najskôr začleňujú sem; ide o kanál „beta“. Keď je stabilné vydanie pripravené, z tejto vetvy sa prenesie do main." + }, + { + "branch": "feature/*", + "purposeRich": "Krátkodobé vetvy pre funkciu, opravu alebo vylepšenie. Vzniknú z develop a po kontrole sa cez pull request zlúčia späť do develop." + } + ], + "calloutTitle": "Čo to znamená pre užívateľov", + "calloutBody": "Používatelia vetvy main dostávajú otestované vydania. Kto chce nové funkcie skôr a znesie občasné nedostatky, môže sledovať develop. Nové funkcie vždy najprv prejdú cez develop; bez tohto cyklu sa do main nedostanú." + }, + "workflow": { + "heading": "Postup pull requestu", + "intro": "Od nápadu po začlenenie do vydania v piatich krokoch:", + "step1Lead": "Vytvorte vetvu pre funkciu z develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Použite výstižný názov vetvy: feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Pracujte na svojich zmenách a posuňte vetvu:", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Otvorte pull request do develop, nie do main. Na GitHube overte, že základná vetva je develop. Opíšte účel skriptu, jeho zmeny a verzie Proxmox VE, na ktorých ste ho testovali.", + "step4": "Po kontrole sa zmeny zlúčia do develop. Správca skontroluje kvalitu kódu, hlavičky a dvojfázové rozhranie. Ak vyžiada úpravy, odošlite ďalšie commity do rovnakej vetvy; PR sa aktualizuje automaticky.", + "step5": "Stabilné vydania sa prenášajú z develop do main. Pri vydaní sa main posunie na develop pomocou fast-forward a označí sa tagom. Váš príspevok sa stane súčasťou ďalšej stabilnej verzie." + }, + "scriptHeader": { + "heading": "Hlavička skriptu: metadáta a popis", + "intro": "Každý skript ProxMenux začína dvoma susednými blokmi komentárov, ktoré tvoria hlavičku. Oba sú povinné: prezradia autora a účel bez potreby čítať samotný kód.", + "bullets": [ + "Horný blok: metadáta. Autor, voliteľné odkazy na GitHub a podporu, správca, autorské práva, licencia, verzia a dátum aktualizácie. Tu prebieha aj uznanie prispievateľov: pri novom skripte uveďte svoje meno a voliteľne stránku GitHub a profil podpory, napríklad Ko-fi, GitHub Sponsors alebo Buy Me a Coffee.", + "Dolný blok: popis. Krátky odsek v jednoduchej angličtine vysvetľuje skript. Číta sa pred kódom a musí sám objasniť účel, hlavné akcie, dotknuté zdroje a predpoklady." + ], + "licenseCalloutTitle": "Licenčná linka je pevne stanovená GPL-3.0", + "licenseCalloutBody": "ProxMenux je vydaný pod GNU General Public License v3.0. Všetky skripty používajú rovnakú licenciu; riadok License v hlavičke musí obsahovať odkaz GPL-3.0 z príkladu nižšie. Nie je to voľba pre jednotlivý skript. Prispením súhlasíte s vydaním pod GPL-3.0: každý môže skript čítať, upraviť a ďalej šíriť vrátane zmien pri zachovaní rovnakej licencie. Úplné znenie: MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "Riadky GitHub a Sponsor sú voliteľné – vynechajte ich, ak ich nechcete zverejniť. Všetko ostatné sa vyžaduje.", + "whyCalloutTitle": "Prečo na tom záleží", + "whyCalloutBody": "Prispievanie do otvoreného softvéru je dobrovoľná práca. Meno a prípadný odkaz na podporu priamo v zdrojovom kóde zviditeľňujú autorstvo pri každom čítaní; nestratí sa za názvom projektu. Popis má iný význam: je prvou vecou, ktorú číta budúci správca alebo zvedavý používateľ. Jasný popis často rozhoduje, či skriptu dôverujú alebo sa mu vyhnú." + }, + "structure": { + "heading": "Štruktúra projektu", + "intro": "Kde sa v repozitári nachádzajú jednotlivé druhy skriptov:", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Každý skript pri štarte načíta utils.sh pomocou source, aby získal funkcie správ, indikátor priebehu, farby a prekladový systém. Zdieľané pomocné knižnice v scripts/global/ výslovne načítavajú skripty, ktoré ich potrebujú." + }, + "twoPhase": { + "heading": "Dvojfázová politika návrhu UI", + "intro": "Ide o najdôležitejšie pravidlo ProxMenux a najčastejší dôvod požiadaviek na úpravu PR. Každý skript je rozdelený na presne dve fázy:", + "headerPhase": "Fáza", + "headerPurpose": "Účel", + "headerScreen": "Stav obrazovky", + "rows": [ + { + "phaseRich": "Fáza 1: výber", + "purposeRich": "Zbierajte každé rozhodnutie užívateľa. Spustite všetky potrebné prípravné práce (sondy, skeny, kontroly).", + "screenRich": "Prekrytia dialog. Ak kontrola medzi ponukami trvá viac ako sekundu či dve, zobrazte indikátor msg_info, aby používateľ vedel, že skript nezamrzol. Tesne pred ďalším dialógom zavolajte stop_spinner." + }, + { + "phaseRich": "Fáza 2: vykonanie", + "purposeRich": "Vykonajte všetky operácie. Na obrazovke postupne zobrazujte úplnú históriu priebehu.", + "screenRich": "Viditeľné hlásenia msg_info / msg_ok; nikdy nie dialog." + } + ], + "principle": "Zásada: najprv zhromaždiť všetky voľby, potom všetko vykonať. Používateľ najprv pracuje s prehľadným dialógovým menu (fáza 1) a potom sleduje vykonávanie vo forme záznamu (fáza 2). Fázy sa nemiešajú: počas vykonávania sa neotvárajú dialógy a počas výberu sa nevypisuje priebeh.", + "phase1Heading": "Fáza 1: typický vzor", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Pravidlá pre fázu 1:", + "phase1Rules": [ + "Ak beží indikátor msg_info a potrebujete otvoriť ponuku dialog alebo whiptail, najprv zavolajte stop_spinner. Indikátor nemôže bežať súčasne s prekrytím týchto nástrojov. Ak indikátor nebeží, volanie nie je potrebné.", + "Použite show_proxmenux_logo + msg_title + msg_info na vizuálny kontext dlhej operácie vo fáze 1, napríklad kontroly trvajúcej päť sekúnd či viac. Funkcia už vyčistí obrazovku, preto pred ňou nevolajte clear.", + "Nevolajte show_proxmenux_logo medzi dialógovými menumi, kde nie je čo zobraziť, a vyčistenie obrazovky prázdneho terminálu je len vizuálny hluk.", + "Uložte všetky rozhodnutia a výsledky sondy v premenných alebo paralelných poliach. Viditeľné zhrnutie sa uskutoční na začiatku fázy 2, nie vo fáze 1." + ], + "phase2Heading": "Fáza 2: typický vzor", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Pravidlá fázy 2: začnite s show_proxmenux_logo + msg_title a hneď zhrňte výsledky fázy 1 riadkami msg_ok. Už nevolajte show_proxmenux_logo, pretože by vymazalo nahromadený priebeh. Nevolajte dialog; pri nevyhnutnom rozhodnutí za behu použite whiptail podľa nasledujúcej časti." + }, + "dialogVsWhiptail": { + "headingRich": "Kedy používať dialog vs whiptail", + "intro": "Oba nástroje vykresľujú textové používateľské rozhranie, ale na obrazovke sa správajú odlišne. ProxMenux ich preto používa v dvoch samostatných fázach.", + "headerTool": "Nástroj", + "headerWhen": "Kedy ju použiť", + "headerEffect": "Účinok na obrazovku", + "rows": [ + { + "toolRich": "dialog", + "whenRich": "Vždy vo fáze 1. Každý interaktívny výber, napríklad VM, disk, súborový systém alebo potvrdenie akcie, používa dialog. Ide o predvolený nástroj rozhrania ProxMenux.", + "effectRich": "Preberá terminál: vyčistí obrazovku, kreslí jej prekrývanie a pri zatvorení vráti terminál do svojho predchádzajúceho stavu. Fáza 1, pretože zatiaľ nič užitočné neukazuje." + }, + { + "toolRich": "whiptail", + "whenRich": "Iba vo fáze 2, iba ak je to nevyhnutné. Typickým prípadom je výzva na reštartovanie na konci vykonávania. Nesiahajte po whiptail, aby ste sa spýtali „pokračovať?“ uprostred vykonávania — toto rozhodnutie malo byť urobené vo fáze 1.", + "effectRich": "Ľahšie prekrytie, ktoré nevymaže kontext terminálu. Nahromadená história msg_ok/msg_info z fázy 2 zostane viditeľná za dialógom. Preto je vhodné, keď už obrazovka zobrazuje priebeh." + } + ], + "calloutTitle": "Prečo je rozdelenie dôležité", + "calloutBody": "Ak zavoláte dialog uprostred fázy 2, zmiznú všetky riadky msg_ok, ktoré používateľ dovtedy sledoval. Vymažete tým viditeľný záznam vykonaných krokov. whiptail tomu zabráni. Pravidlo teda nie je svojvoľné: zachováva používateľovi prehľad o tom, čo skript doteraz urobil.", + "rebootHeading": "Výzva na reštart: typické použitie whiptail vo fáze 2", + "rebootIntro": "Ak môže byť po skončení skriptu potrebný reštart, napríklad po zapnutí IOMMU alebo zmene parametrov jadra, výzva na konci fázy 2 používa whiptail. Vždy pridajte vetvu Nie, ktorá upozorní, aby používateľ nepoužíval dotknutý zdroj pred reštartom:", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Prehľad funkcií správ", + "intro": "Všetky sú definované v utils.sh. Používajte ich pre výstup viditeľný používateľovi, aby skripty mali jednotný vzhľad. Ak potrebujete novú funkciu, napríklad úroveň závažnosti alebo pomocníka rozloženia, navrhnite ju v pull requeste. Po kontrole sa pridá do utils.sh, ak má širšie využitie.", + "headerFunction": "Funkcia", + "headerWhen": "Kedy používať", + "headerSpinner": "Indikátor priebehu", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Prebieha operácia.", + "spinner": "Spustí" + }, + { + "function": "stop_spinner", + "whenRich": "Koniec tichej prípravnej práce vo fáze 1: zastaví indikátor a vyčistí riadok.", + "spinner": "Zastaví" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "Operácia bola úspešná. Použite aj pre zapnutú funkciu, hoci je ešte potrebný reštart.", + "spinner": "Zastaví" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Skutočné varovanie alebo zhoršený stav.", + "spinner": "Zastaví" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Závažná chyba.", + "spinner": "Zastaví" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Neblokujúce odporúčanie, tyrkysový informačný riadok.", + "spinner": "Zastaví" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Záverečná výzva „Stlačte Enter pre návrat“ na konci fázy 2.", + "spinner": "Zastaví" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Tučný nadpis so zabudovanými rozostupmi. Používa sa na začiatku fázy 2.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Vyčistí obrazovku a zobrazí logo. Na začiatku fázy 2 sa volá raz.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "Pravidlá používania dialog", + "bullets": [ + "Každému volaniu dialog a whiptail vždy odovzdajte --backtitle \"$BACKTITLE\". $BACKTITLE je vždy \"ProxMenux\", nastavené raz v hlavičke skriptu a nikdy neprepísané. Rámec rozhrania musí používateľovi ukazovať názov projektu, nie vlastný nadpis skriptu.", + "Vždy zabaľte titulky a správy s $(translate \"...\").", + "Vždy presmerujte dialog výstup pomocou 2>&1 >/dev/tty — zachytený stdout sa stane výberom používateľa, zatiaľ čo samotné dialógové okno sa kreslí na terminál.", + "Použite štandardné premenné rozmerov UI ($UI_MENU_H, $UI_MSG_W, atď.) pre konzistentnú veľkosť medzi skriptmi.", + "Vždy skontrolujte prázdne / zrušené výbery a zaobchádzajte s nimi elegantne." + ], + "exampleIntro": "Úplný príklad: vytvorenie ponuky na výber VM a spracovanie zrušenia.", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Politika prekladu", + "intro": "Všetky užívateľsky viditeľné reťazce musia byť zabalené s funkciou translate. ProxMenux ich automaticky prekladá do všetkých podporovaných jazykov, ktoré píšete anglicky, užívateľ číta ich rodný jazyk.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Reťazce píšte po anglicky; preklad sa spracuje automaticky pri zostavení.", + "Udržujte stručnosť. Ak je to možné, nepoužívajte premenné vo vnútri dlhých viet.", + "Neprekladajte názvy premenných, cesty ani technické identifikátory." + ] + }, + "variableStyle": { + "heading": "Pravidlá premenných a štýlu", + "bullets": [ + "Pre premenné úrovne skriptov použite UPPER_CASE.", + "Pre lokálne funkčné premenné (vyhlásiť s local) použite lower_case.", + "Všetky rozvinutia premenných uzavrite do úvodzoviek: \"$VAR\", nie $VAR.", + "Použite [[ ]] pre podmienky, nie [ ] (s výnimkou prípadov, keď sa POSIX vyžaduje).", + "show_proxmenux_logo je správny spôsob vyčistenia obrazovky: vyčistí ju a zobrazí logo projektu pre vizuálny kontext. Zavolajte ho raz na začiatku fázy 2 a voliteľne pred dlhou prípravnou kontrolou s indikátorom vo fáze 1." + ], + "standardNamesIntro": "Štandardné názvy premenných v rámci projektu:", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Presmerovanie výstupu nástroja počas fázy 2", + "redirectIntro": "Fáza 2 zobrazuje prehľadný záznam riadkov msg_info → msg_ok. Ak volaný nástroj, napríklad apt, mkfs, qm, pct alebo dd, zapisuje na stdout/stderr, posúva správy a naruší prehľad. Používateľ potom nerozozná priebeh skriptu od podrobného výstupu nástroja.", + "withoutRedirectIntro": "Bez presmerovania: čo používateľ vidí, ak neošetríte rušivý výstup.", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": "S presmerovaním — ako vyzerá tá istá operácia, keď je šum odoslaný inam:", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Dva vzory na výber:", + "discardLead": "Zahodiť výstup, ak ho nepotrebujete: najjednoduchšia a najrýchlejšia možnosť.", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Pošlite výstup do súboru denníka, keď ho budete chcieť neskôr skontrolovať (ladenie neúspešnej inštalácie, kontrola toho, čo dpkg skutočne urobil). Toto je preferovaný vzor pre akúkoľvek operáciu apt:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "scripts/global/update-pve9_2.sh je referenčná implementácia: každé volanie apt-get zapisuje do súboru záznamu. Používateľ vidí iba prehľadný tok msg_info → msg_ok, no pri probléme možno zo súboru presne zistiť, čo apt vykonal." + }, + "dosAndDonts": { + "heading": "Čo robiť a čomu sa vyhnúť", + "doHeading": "✅ Robiť", + "doBullets": [ + "stop_spinner pred dialog vo fáze 1 iba vtedy, keď msg_info spinner beží.", + "Fáza 2 začína s show_proxmenux_logo + msg_title + msg_ok zhrnutie výsledkov fázy 1.", + "Pre úspešne zapnuté funkcie použite msg_ok, aj keď je potrebný reštart.", + "Používajte whiptail (nie dialog) pre akýkoľvek postup po vykonaní, ktorý sa musí objaviť vo fáze 2.", + "V dialógových oknách reštartu vždy zahrňte vetvu „Nie“, ktorá varuje používateľa, aby nespúšťal dotknutý zdroj, kým nebude reštartovaný.", + "Logiku určenú iba pre VM chráňte kontrolou [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]]: radiče a NVMe PCIe nemožno priradiť LXC cez PCI passthrough.", + "Použite ensure_repositories z utils-install-functions.sh namiesto bezpodmienečného apt-get update.", + "Používajte paralelné polia vo fáze 1, ak každá položka potrebuje viac dialógov." + ], + "dontHeading": "❌ Nerobiť", + "dontBullets": [ + "Zavolajte dialog, kým je spinner aktívny.", + "Preskočiť zhrnutie fázy 1 na začiatku fázy 2.", + "Zavolať show_proxmenux_logo druhýkrát; vymaže všetko, čo fáza 2 vypísala.", + "Použite dialog vo fáze 2 (použite whiptail pre zriedkavý nevyhnutný prípad).", + "Použite holý clear.", + "Obaľovať msg_title prázdnymi riadkami echo \"\" — potrebné medzery už obsahuje.", + "Použiť msg_warn pre hlásenie úspešne zapnutej funkcie, ktorá je msg_ok.", + "Spustiť bezpodmienečné apt-get update; namiesto toho použite ensure_repositories." + ] + }, + "submitting": { + "heading": "Predloženie príspevku", + "steps": [ + "Vytvorte fork repozitára MacRimi/ProxMenux na GitHube a potom svoj fork naklonujte.", + "Vytvorte vetvu feature/* z develop (pozrite Model vetiev vyššie).", + "Postupujte podľa tejto príručky pre akékoľvek nové alebo zmenené skripty. Napíšte hlavičku s Vaším menom; pridajte voliteľný riadok sponzora, ak chcete.", + "Otestujte skript na skutočnej inštancii Proxmox VE: fázu 1 so všetkými vetvami dialógov a fázu 2 so všetkými operáciami aj čistým návratom pri chybe.", + "Otvorte pull request do vetvy develop s jasným opisom: čo skript robí, čo sa zmenilo a na ktorej verzii Proxmox VE bol otestovaný.", + "Uistite sa, že váš príspevok rešpektuje Kódex správania." + ], + "securityOutro": "Pokiaľ ide o otázky citlivé na bezpečnosť, postupujte podľa toku zverejnenia v SECURITY.md namiesto otvárania verejnej otázky." + }, + "whereNext": { + "heading": "Kam ďalej", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (úplný sprievodca)", + "tail": " — záväzný zdroj všetkých pravidiel vrátane pokročilých hardvérových postupov." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Prispievatelia", + "tail": " — ľudia, ktorých práca formovala vydania ProxMenux. Ide o inú stránku než túto." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Kódex správania", + "tail": " — Každý prispievateľ súhlasí s dodržiavaním noriem." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": " — Ak si nie ste istí, či idea sedí, alebo nájsť spolupracovníkov na väčšiu funkciu, spýtajte sa pred stavaním." + } + ] + } +} diff --git a/web/messages/sk/docs/about/contributors.json b/web/messages/sk/docs/about/contributors.json new file mode 100644 index 00000000..cff3f316 --- /dev/null +++ b/web/messages/sk/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Prispievatelia | Dokumentácia ProxMenux", + "description": "Poďakovanie ľuďom, vďaka ktorým ProxMenux vzniká: testerom, kontrolórom a vývojárom, ktorí sa podieľajú na každom vydaní.", + "ogTitle": "Prispievatelia | Dokumentácia ProxMenux", + "ogDescription": "Zoznámte sa s prispievateľmi ProxMenux." + }, + "header": { + "title": "Prispievatelia", + "description": "Projekt ProxMenux rastie vďaka svojim prispievateľom. Táto stránka oceňuje ich prácu: testovanie zostáv pred vydaním, odhaľovanie regresií, kontrolu PR a formovanie nových funkcií.", + "section": "O projekte" + }, + "beyond": { + "title": "Ďalší prispievatelia", + "body": "Mnohí ďalší ľudia prispievajú hlásením chýb, návrhmi funkcií v Discussions, beta testovaním a prekladmi. Úplný zoznam prispievateľov do kódu nájdete v grafe prispievateľov na GitHube. Táto stránka vyzdvihuje ľudí, ktorí sa najaktívnejšie zapájajú do testovania a kontroly vydaní ProxMenuxu." + }, + "testers": { + "heading": "Testeri a kontrolóri", + "intro": "Títo ľudia patrili medzi prvých prispievateľov do projektu. Testovaním, hlásením problémov a návrhmi zlepšení pomohli ProxMenuxu vyrásť do dnešnej podoby.", + "roles": { + "testing": "Testovanie", + "testingReviewer": "Testovanie a kontrola" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Chceš prispieť?", + "intro": "Akýkoľvek príspevok je vítaný – kód, testovanie, dizajn, dokumentácia alebo len zdieľanie nápadov:", + "tester": "Tester — vyskúšajte beta kanál a pred stabilným vydaním nahláste, čo funguje a čo nie.", + "developer": "Vývojár — Bash, Next.js (TypeScript) a trochu Pythonu pre virtuálne prostredie prekladov (venv). PR na GitHube sú vítané.", + "designer": "Dizajnér — vylepšenia používateľského rozhrania, ikony a snímky obrazovky pre dokumentáciu.", + "ideas": "Nápady — pred vytvorením požiadavky na novú funkciu otvorte tému v GitHub Discussions." + }, + "coc": { + "title": "Najprv si prečítajte kódex správania", + "body": "Pred odoslaním kódu alebo nahlásením problému si prečítajte Pravidlá správania a osvedčené postupy. Opisujú očakávania týkajúce sa zodpovednosti za kód, oznamovania bezpečnostných chýb a rozsahu PR." + } +} diff --git a/web/messages/sk/docs/about/faq.json b/web/messages/sk/docs/about/faq.json new file mode 100644 index 00000000..52fe8bd1 --- /dev/null +++ b/web/messages/sk/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Časté otázky | Dokumentácia ProxMenux", + "description": "Časté otázky o ProxMenuxe: funkcie, inštalácia, kompatibilita od PVE 8, prispôsobenie, aktualizácie, bezpečnosť, produkčné použitie, systémové súbory, prispievanie a odinštalovanie.", + "ogTitle": "Časté otázky | Dokumentácia ProxMenux", + "ogDescription": "Časté otázky o inštalácii ProxMenuxu, kompatibilite, bezpečnosti a možnostiach prispievania." + }, + "header": { + "title": "Často kladené otázky", + "description": "Bežné otázky o ProxMenuxe: čo to je, komu slúži, inštalácia, aktualizácia, odinštalovanie, kompatibilita, prispôsobenie, bezpečnosť a prispievanie. Ak tu svoju otázku nenájdete, pozrite si GitHub Discussions alebo vytvorte issue.", + "section": "O projekte" + }, + "quickLinks": { + "title": "Rýchle odkazy", + "installationLabel": "Inštalácia", + "installationSuffix": " — inštalačný príkaz na jednom riadku a potrebné závislosti.", + "introductionLabel": "Úvod", + "introductionSuffix": " — čo ProxMenux robí, za päť minút.", + "uninstallLabel": "Odinštalovať", + "uninstallSuffix": " — úplné odstránenie programu.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Diskusie" + }, + "q1": { + "question": "Čo je ProxMenux a na čo slúži?", + "p1Rich": "ProxMenux je interaktívny nástroj s ponukami, ktorý sprístupňuje Proxmox VE všetkým používateľom bez ohľadu na ich technické skúsenosti. Zjednodušuje vykonávanie príkazov a umožňuje spravovať systém bez pokročilých znalostí Linuxu.", + "p2": "Menej skúsení používatelia môžu spúšťať príkazy cez prehľadné ponuky a nemusia ich ručne zadávať do terminálu. Skúseným správcom ProxMenux šetrí kroky pri opakovaných úlohách: zabezpečení po inštalácii, GPU passthrough, importe VM vo formáte OVF a rotácii snímok ZFS.", + "p3": "Proxmox VE sa široko používa na:", + "items": [ + "Virtualizácia na podnikovej úrovni", + "HomeLab a osobné cloudové riešenia", + "Multimediálne servery, automatizácia a ďalšie" + ] + }, + "q2": { + "question": "Ako nainštalujem ProxMenux?", + "p1Rich": "Postupujte podľa inštalačnej príručky. V skratke:", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "Po nainštalovaní jednoducho spustite:", + "menuCmd": "menu" + }, + "q3": { + "question": "Je ProxMenux kompatibilný so všetkými verziami Proxmoxu?", + "bodyRich": "Nie — ProxMenux vyžaduje Proxmox VE 8 alebo novší. PVE 7 a staršie verzie nie sú podporované. Ak ešte používate PVE 7, najskôr prejdite na 8.x podľa oficiálneho postupu Proxmoxu. Následný prechod z 8 na 9 môžete vykonať v ProxMenuxe cez Nástroje → Aktualizovať PVE 8 na PVE 9." + }, + "q4": { + "question": "Môžem prispôsobiť ProxMenux?", + "p1Rich": "Aktualizácie môžu prepísať miestne zmeny hlavných skriptov, preto tieto súbory neupravujte priamo. Prispôsobiť si však môžete:", + "items": [ + "Banner konzoly prostredníctvom nástroja FastFetch v možnostiach po inštalácii.", + "Ktoré voliteľné súčasti sa nainštalujú (Monitor, Fail2Ban, Lynis a ďalšie): vyberiete ich počas inštalácie alebo neskôr v príslušných ponukách." + ] + }, + "q5": { + "question": "Ako aktualizujem ProxMenux?", + "p1Rich": "Keď je dostupná nová verzia, ProxMenux ju zistí pri ďalšom spustení menu a ponúkne aktualizáciu. Ak ju prijmete, nahradí súbory a konfiguráciu nástroja v existujúcej inštalácii. Nemusíte nič sťahovať ručne.", + "p2Rich": "Používatelia stabilnej verzie dostávajú stabilné aktualizácie a používatelia beta verzie beta aktualizácie. Po vydaní stabilnej verzie na to beta upozorní pri ďalšom otvorení ponuky a ponúkne automatický prechod. Pozrite si beta program." + }, + "q6": { + "question": "Kde môžem nahlásiť problémy?", + "p1Rich": "Chyby nahlasujte cez GitHub Issues. Uveďte kroky na ich zopakovanie, chybové hlásenie a podľa potreby výstup Monitora z príkazu journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "V prípade problémov s bezpečnosťou ich prosím nezverejňujte. Prečítajte si Kódex správania a osvedčené postupy o zodpovednom postupe zverejnenia." + }, + "q7": { + "question": "Môžem prispieť k ProxMenux?", + "p1Rich": "Samozrejme. ProxMenux je otvorený komunitný projekt. Prispieť môžete kódom (Bash, Next.js/TypeScript a prekladové nástroje v Pythone), testovaním, dokumentáciou, dizajnom alebo nápadmi.", + "item1Rich": "Pred otvorením žiadosti o funkciu prediskutujte nápady v GitHub diskusiách.", + "item2Rich": "Pred odoslaním pull requestu si prečítajte kódex správania a odporúčané postupy.", + "item3Rich": "Na stránke prispievateľov nájdete aktuálnych testerov a recenzentov." + }, + "q8": { + "question": "Upravuje ProxMenux kritické systémové súbory?", + "p1Rich": "Inštalácia ProxMenuxu pridá nasledujúce súčasti.", + "item1Rich": "Závislosti cez apt: dialog, curl, jq a git. Preklady používajú vopred vytvorené katalógy JSON; inštalátor na hostiteľa nepridáva virtuálne prostredie na preklad.", + "item2Rich": "Adresárový strom skriptov v /usr/local/share/proxmenux/.", + "item3Rich": "Spúšťač v /usr/local/bin/menu.", + "item4Rich": "Voliteľne: služba systemd pre Monitor, proxmenux-monitor.service.", + "p2Rich": "ProxMenux nemení základnú konfiguráciu Proxmoxu, pokiaľ o to výslovne nepožiadate v príslušnej ponuke. Oprava siete napríklad upravuje /etc/network/interfaces, ale až po vašom potvrdení a vytvorení zálohy." + }, + "q9": { + "question": "Je bezpečné používať ProxMenux v produkčnom prostredí?", + "bodyRich": "Áno. Keďže ProxMenux mení základné súbory Proxmoxu iba na výslovnú žiadosť, možno ho používať aj v produkčnom prostredí. Ako pri každej automatizácii ho najprv otestujte v kontrolovanom prostredí a pred deštruktívnymi operáciami majte zálohy. Týka sa to napríklad prechodu z PVE 8 na 9, formátovania diskov či odstránenia VM." + }, + "q10": { + "question": "Ako odinštalujem ProxMenux?", + "p1Rich": "V ProxMenuxe: Nastavenia → Odinštalovať ProxMenux. Odinštalovanie odstráni skripty, spúšťač a službu Monitor a voliteľne aj vybrané závislosti. Súbory /root/.bashrc a /etc/motd obnoví zo záloh vytvorených pri inštalácii, ak sú dostupné.", + "p2Rich": "Celý postup nájdete na stránke Odinštalovanie ProxMenuxu." + }, + "q11": { + "question": "VirusTotal označuje inštalačnú URL ako podozrivú — je bezpečná?", + "p1Rich": "Výsledok 1/95 od Chong Lua Dao, nahlásený v issue #162, správca projektu označil za falošný poplach. Inštalátor používa vzor curl | bash a môže stiahnuť jq z jeho oficiálneho vydania na GitHube. Takéto správanie môže spustiť heuristickú detekciu; historické hlásenie nie je hodnotením všetkých budúcich stiahnutí.", + "p2Rich": "ProxMenux má úplne otvorený zdrojový kód a inštalačný skript si môžete skontrolovať riadok po riadku: install_proxmenux.sh. Ďalšie súvislosti nájdete v issue #162." + } +} diff --git a/web/messages/sk/docs/about/index.json b/web/messages/sk/docs/about/index.json new file mode 100644 index 00000000..163b3c10 --- /dev/null +++ b/web/messages/sk/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "O projekte | Dokumentácia ProxMenux", + "description": "O projekte ProxMenux: ciele, prispievatelia, časté otázky a pravidlá správania. Nástroj s otvoreným zdrojovým kódom na správu Proxmox VE 8+ cez ponuky, ktorý vyvíja a udržiava MacRimi s malým tímom testerov a prispievateľov.", + "ogTitle": "O projekte | Dokumentácia ProxMenux", + "ogDescription": "O ProxMenuxe — nástroji s otvoreným zdrojovým kódom na správu Proxmox VE 8+ cez ponuky." + }, + "header": { + "title": "O projekte", + "description": "Informácie o projekte, časté otázky, prispievatelia a pravidlá správania. ProxMenux je nástroj s otvoreným zdrojovým kódom na správu Proxmox VE 8+ cez ponuky, ktorý vyvíja a udržiava MacRimi s pomocou malého tímu testerov a prispievateľov.", + "section": "O projekte" + }, + "callout": { + "title": "Otvorený zdrojový kód, vývoj vedený komunitou", + "body": "ProxMenux sa poskytuje pod licenciou GPL-3.0 a vyvíja sa verejne na GitHube. Každý skript možno skontrolovať a každú zmenu dohľadať. Hlásenia chýb, nápady, kód aj testovanie sú vítané." + }, + "section": { + "heading": "V tejto časti:", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Časté otázky: čo je ProxMenux, inštalácia, kompatibilita, prispôsobenie, aktualizácie, bezpečnosť, používanie v produkcii a odinštalovanie." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Prispievatelia", + "description": "Poďakovanie ľuďom, vďaka ktorým ProxMenux vzniká: testerom, kontrolórom a vývojárom, ktorí sa podieľajú na každom vydaní." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Kódex správania", + "description": "Hodnoty projektu, zodpovednosť za kód a pravidlá prispievania. Povinné čítanie pred odoslaním PR alebo hlásenia problému." + } + ] + }, + "involved": { + "heading": "Zapojte sa", + "intro": "Tri miesta na zapojenie sa do projektu:", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Nahlásiť chybu", + "description": "GitHub Issues — priložte záznamy a kroky na zopakovanie problému." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Navrhnúť funkciu", + "description": "GitHub Discussions — podeľte sa o nápady pred odoslaním PR." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Odoslať PR", + "description": "GitHub Pull Requests — najskôr si prečítajte pravidlá správania." + } + ] + }, + "support": { + "heading": "Podpora projektu", + "introRich": "Ak vám ProxMenux šetrí čas, najjednoduchšie mu pomôžete osloviť ďalších ľudí tak, že dáte repozitáru na GitHube ⭐. Ak chcete pomôcť viac:", + "kofiLabel": "Podpora pre Ko-fi", + "kofiOutro": " — prispejete správcovi na kávu a podporíte pokračovanie vývoja." + } +} diff --git a/web/messages/sv/docs/about/code-of-conduct.json b/web/messages/sv/docs/about/code-of-conduct.json new file mode 100644 index 00000000..7cddd218 --- /dev/null +++ b/web/messages/sv/docs/about/code-of-conduct.json @@ -0,0 +1,13 @@ +{ + "meta": { + "title": "Uppförandekod | ProxMenux-dokumentation", + "description": "ProxMenux uppförandekod och rekommenderade arbetssätt för bidragsgivare, testare och användare.", + "ogTitle": "Uppförandekod | ProxMenux-dokumentation", + "ogDescription": "Förväntningar på ansvar för koden, rapportering av säkerhetsbrister och omfattningen av PR:ar i ProxMenux-projektet." + }, + "errors": { + "notFound": "Fel: CODE_OF_CONDUCT.md-filen hittades inte.", + "loadFailed": "Fel: Det går inte att läsa in uppförandekodens innehåll." + }, + "contentMarkdown": "# 📝 Uppförandekod och rekommendationer\n\nDetta dokument fastställer **principer och riktlinjer** för användningen av **ProxMenux**, för att främja **respekt, samarbete och säkerhet** i utvecklingen.\n\n## 📌 1. Källangivelser och erkännande\n\n- Om **skript, kodavsnitt eller idéer** från andra kodarkiv eller utvecklare används ska den ursprungliga upphovspersonen alltid anges.\n- En kommentar i koden eller dokumentationen ska ange källan och om möjligt länka till det ursprungliga kodarkivet.\n- Vid användning av kod med en viss **licens** måste dess villkor följas.\n\n### ✅ Exempel på korrekt källangivelse i koden:\n\n```bash\n# Skript baserat på arbetet av [Upphovsperson] i [Länk till kodarkiv]\n```\n\n## 🔒 2. Säkerhet och ansvar för koden\n\n- All kod i detta arkiv följer rekommenderade arbetssätt för säker utveckling.\n- Bidrag som avsiktligt inför sårbarheter eller osäkra arbetssätt tillåts inte.\n- Skript får aldrig äventyra systemsäkerheten eller användarnas data.\n- Bidrag bör granskas med avseende på säkerhet och stabilitet före införandet.\n\n### ⚠️ Ansvarsfull rapportering av sårbarheter\n\nOm du upptäcker en säkerhetssårbarhet ska du INTE rapportera den i offentliga ärenden eller forum.\n\nRapportera den i stället privat via e-post:\n\n📧 proxmenux@macrimi.pro\n\nLäs vår [säkerhetspolicy](./SECURITY.md) för mer information om sårbarheter och hur de rapporteras.\n\n## 🤝 3. Gemenskapens riktlinjer\n\nAlla användare måste följa dessa riktlinjer för att upprätthålla en respektfull och professionell miljö:\n\n- ✅ Var respektfull och konstruktiv i diskussioner.\n- ❌ Trakasserier, hatretorik och stötande beteende tolereras inte.\n- ✅ Ge återkoppling professionellt och undvik personangrepp.\n- ❌ Överträdelser kan leda till varningar eller avstängningar beroende på allvarlighetsgrad.\n\n##\n\nDetta dokument främjar etisk kodanvändning, säkerhetsmedvetenhet och en respektfull gemenskap.\n\n📢 Låt oss bygga något fantastiskt tillsammans!\n" +} diff --git a/web/messages/sv/docs/about/contributing.json b/web/messages/sv/docs/about/contributing.json new file mode 100644 index 00000000..f951f4af --- /dev/null +++ b/web/messages/sv/docs/about/contributing.json @@ -0,0 +1,293 @@ +{ + "meta": { + "title": "Bidra till ProxMenux | ProxMenux Dokumentation", + "description": "Så bidrar du till ProxMenux: grenmodell (main / develop / feature/*), pull request-flöde, skripthuvud med författare och valfri stödlänk, gränssnitt i två faser, meddelandefunktioner och vägen till en stabil utgåva.", + "ogTitle": "Bidra till ProxMenux", + "ogDescription": "Grenmodell, arbetsflöde, författarangivelse i skripthuvudet och regler för gränssnitt i två faser som bidragsgivare ska följa." + }, + "header": { + "title": "Bidra till ProxMenux", + "description": "ProxMenux välkomnar bidrag från gemenskapen: nya skript, rättningar, förbättrade dialogrutor, översättningar och integrationer. Sidan beskriver arkivets struktur, grenmodellen, pull request-flödet, skripthuvudet med författare och valfri stödlänk samt de designregler varje bidrag ska följa.", + "section": "Om" + }, + "twoPagesCallout": { + "title": "Två relaterade sidor, ett projekt", + "body": "Förväxla inte sidan med Bidragsgivare, som uppmärksammar dem som redan bidrar: testare, granskare och utvecklare. Den här sidan är för dig som vill börja bidra och förklarar hur du förbereder en pull request för sammanslagning." + }, + "branching": { + "heading": "Grenmodell", + "intro": "ProxMenux använder en grenmodell med tre nivåer:", + "headerBranch": "Gren", + "headerPurpose": "syfte", + "rows": [ + { + "branch": "main", + "purposeRich": "Stabil kod som är klar för produktion. Bara ändringar som är klara för utgivning slås samman här. Det är den version slutanvändare får vid en vanlig installation av ProxMenux." + }, + { + "branch": "develop", + "purposeRich": "Aktiv utvecklingsgren. Allt nytt arbete tas först in här; det är betakanalen. När en stabil utgåva är klar förs den härifrån till main." + }, + { + "branch": "feature/*", + "purposeRich": "Kortlivade grenar per funktion, fix eller förbättring. Skapad från develop, fusioneras tillbaka till develop via en Pull begäran efter granskning." + } + ], + "calloutTitle": "Vad detta innebär för användare", + "calloutBody": "Slutanvändare som spårar main får testade releaser. Användare som vill ha de senaste funktionerna tidigt - och inte har något emot enstaka grova kanter - kan följa develop. Nya funktioner passerar alltid genom develop först; ingenting når main utan att gå igenom den cykeln." + }, + "workflow": { + "heading": "Arbetsflöde för pull requests", + "intro": "Från idé till fusion i fem steg:", + "step1Lead": "Skapa en funktionsgren från develop:", + "step1Code": "git clone https://github.com/MacRimi/ProxMenux.git\ncd ProxMenux\ngit checkout develop\ngit pull origin develop\ngit checkout -b feature/your-feature-name", + "step1Trail": "Använd ett beskrivande grennamn: feature/zfs-arc-tuning, feature/fix-iommu-detection, feature/add-tailscale-script.", + "step2Lead": "Arbeta med ändringarna och skicka upp grenen:", + "step2Code": "# Make your changes, then:\ngit add scripts/...\ngit commit -m \"Add ZFS ARC tuning script\"\ngit push -u origin feature/your-feature-name", + "step3": "Öppna en pull request mot develop, inte mot main. Kontrollera på GitHub att basgrenen är develop. Beskriv vad skriptet gör och ändrar och vilka Proxmox VE-versioner det testats på.", + "step4": "Efter granskning slås ändringarna samman i develop. En underhållare granskar kodkvalitet, skripthuvud och gränssnittets två faser. Om ändringar begärs skickar du fler incheckningar till samma gren; PR:n uppdateras automatiskt.", + "step5": "Stabila utgåvor förs från develop till main. Vid utgivning flyttas main fram till develop med fast-forward och taggas. Ditt bidrag ingår i nästa stabila version." + }, + "scriptHeader": { + "heading": "Skripthuvud: metadata och beskrivning", + "intro": "Varje ProxMenux-skript börjar med två intilliggande kommentarsblock som bildar skripthuvudet. Båda krävs: de anger författare och syfte utan att läsaren behöver gå igenom själva koden.", + "bullets": [ + "Övre blocket: metadata. Författare, valfria GitHub-/stödlänkar, underhållare, upphovsrätt, licens, version och uppdateringsdatum. Här ges också erkännande åt bidragsgivare: för ett nytt skript anger du ditt namn och valfritt GitHub-sida och stödprofil, till exempel Ko-fi, GitHub Sponsors eller Buy Me a Coffee.", + "Nedre blocket: beskrivning. Ett kort stycke på enkel engelska förklarar skriptet. Det läses före koden och ska på egen hand klargöra syfte, huvudåtgärder, berörda resurser och förutsättningar." + ], + "licenseCalloutTitle": "Licenslinjen är fast - GPL-3.0", + "licenseCalloutBody": "ProxMenux ges ut under GNU General Public License v3.0. Alla skript har samma licens. Raden License i skripthuvudet ska alltid ha GPL-3.0-hänvisningen i exemplet nedan; den väljs inte per skript. Genom att bidra godkänner du GPL-3.0: alla får läsa, ändra och sprida skriptet och ändringarna med samma licens. Fullständig text: MacRimi/ProxMenux/LICENSE.", + "templateCode": "#!/bin/bash\n\n# ==========================================================\n# ProxMenux - A menu-driven script for Proxmox VE management\n# ==========================================================\n# Author : Your Name\n# GitHub : github.com/yourhandle\n# Sponsor : ko-fi.com/yourhandle\n# Maintainer : MacRimi\n# Copyright : (c) 2026 MacRimi & contributors\n# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)\n# Version : 1.0\n# Last Updated: DD/MM/YYYY\n# ==========================================================\n# Description:\n# Short paragraph explaining what the script does.\n# Mention the main actions (e.g. \"creates a ZFS pool\",\n# \"configures IOMMU and reboots\", \"imports an ISO into a VM\"),\n# the resources it touches, and any prerequisites the user\n# should be aware of before running it.\n# ==========================================================", + "optionalNote": "Raderna GitHub och Sponsor är valfria. Utelämna dem om du inte vill publicera dem. Allt annat krävs.", + "whyCalloutTitle": "Varför detta är viktigt", + "whyCalloutBody": "Bidrag till öppen källkod är frivilligt arbete. Namn och eventuell stödlänk direkt i källkoden ger författaren synligt erkännande vid varje läsning, i stället för att döljas bakom projektet. Beskrivningen har ett annat syfte: den är det första en framtida underhållare eller nyfiken användare läser. En tydlig beskrivning avgör ofta om skriptet inger förtroende eller undviks." + }, + "structure": { + "heading": "Projektstruktur", + "intro": "Var olika typer av skript finns i arkivet:", + "treeCode": "scripts/\n├── menus/ # Top-level menu scripts (entry points)\n├── storage/ # Disk, storage and passthrough scripts\n├── share/ # NFS, Samba, local share scripts\n├── vm/ # VM creation and configuration scripts\n├── gpu_tpu/ # GPU / TPU passthrough scripts\n├── post_install/ # Post-install automation scripts\n├── backup_restore/ # Backup and restore scripts\n├── utilities/ # System utility scripts\n├── global/ # Shared helper libraries (sourced by other scripts)\n├── utils.sh # Shared utility functions and message helpers\n└── help_info_menu.sh # Interactive help and command reference", + "outro": "Varje skript läser in utils.sh vid start med source för att få meddelandefunktioner, väntindikator, färgvariabler och översättningssystem. Gemensamma hjälpbibliotek i scripts/global/ läses uttryckligen in av de skript som behöver dem." + }, + "twoPhase": { + "heading": "Regler för gränssnitt i två faser", + "intro": "Detta är den viktigaste regeln i ProxMenux och den som oftast leder till ändringskrav i PR:er. Varje skript delas in i exakt två faser:", + "headerPhase": "Fas", + "headerPurpose": "syfte", + "headerScreen": "Skärmtillstånd", + "rows": [ + { + "phaseRich": "Fas 1 — Urval", + "purposeRich": "Samla varje användarbeslut. Kör något förberedande arbete som behövs (sonder, skanningar, kontroller).", + "screenRich": "dialog-överlägg. Om en kontroll mellan två menyer tar mer än någon sekund, visa en msg_info-väntindikator så att användaren ser att skriptet inte hängt sig. Anropa sedan stop_spinner direkt före nästa dialogruta." + }, + { + "phaseRich": "Fas 2 - Utförande", + "purposeRich": "Utför varje operation. Visa hela framstegshistorien som ackumuleras på skärmen.", + "screenRich": "Synliga msg_info/msg_ok-meddelanden; aldrig dialog." + } + ], + "principle": "Principen är samla alla val först och kör sedan allt. Användaren ser först ordnade dialogmenyer (fas 1), sedan en loggliknande körningsvy (fas 2). Faserna blandas inte: inga dialogrutor mitt i körningen och inga förloppsmeddelanden under valet.", + "phase1Heading": "Fas 1 - typiskt mönster", + "phase1Code": "# Silent preparatory work between dialogs\nmsg_info \"$(translate \"Checking disk assignments...\")\"\nASSIGNED_TO=$(check_assignments \"$DISK\")\nstop_spinner # ← clears line silently, result saved in variable\n\n# Next dialog can now use ASSIGNED_TO\nif [ -n \"$ASSIGNED_TO\" ]; then\n dialog --yesno \"$(translate \"Disk already assigned. Continue?\")\" $UI_YESNO_H $UI_YESNO_W\nfi\n\n# Collect multiple decisions per item with parallel arrays\ndeclare -a DISK_LIST=()\ndeclare -a DISK_FORMAT_TYPES=()\ndeclare -a DISK_MOUNT_POINTS=()\nfor DISK in $SELECTED; do\n msg_info \"$(translate \"Analyzing disk...\")\"\n CURRENT_FS=$(lsblk -no FSTYPE \"$DISK\" | xargs)\n stop_spinner\n\n FORMAT=$(dialog --backtitle \"$BACKTITLE\" \\\n --title \"$(translate \"Select Filesystem\")\" \\\n --menu \"...\" $UI_SHORT_MENU_H $UI_SHORT_MENU_W $UI_SHORT_MENU_LIST_H \\\n \"ext4\" \"...\" \"xfs\" \"...\" \"btrfs\" \"...\" \\\n 2>&1 >/dev/tty)\n [ -z \"$FORMAT\" ] && continue\n\n DISK_LIST+=(\"$DISK\")\n DISK_FORMAT_TYPES+=(\"$FORMAT\")\ndone", + "phase1RulesIntro": "Regler för fas 1:", + "phase1Rules": [ + "Om en msg_info-väntindikator körs och du ska öppna en dialog- eller whiptail-meny, anropa först stop_spinner. Indikatorn kan inte samexistera med verktygens överlägg. Om ingen indikator är aktiv behövs inget anrop.", + "Använd show_proxmenux_logo + msg_title + msg_info för att ge visuellt sammanhang åt längre åtgärder i fas 1, exempelvis en kontroll som tar minst fem sekunder. Funktionen rensar redan skärmen; anropa inte clear före den.", + "Anropa inte show_proxmenux_logo mellan dialogmenyer när det saknas något att visa. Att rensa skärmen för en tom terminal ger bara visuellt brus.", + "Lagra alla val och kontrollresultat i variabler eller parallella arrayer. Sammanfattningen visas i början av fas 2, inte i fas 1." + ], + "phase2Heading": "Fas 2 - typiskt mönster", + "phase2Code": "# ── PHASE 2 — EXECUTION ─────────────────────────────\nshow_proxmenux_logo\nmsg_title \"$(translate \"My Script Title\")\"\n\n# Recap Phase 1 preparatory results — show what was already done\nmsg_ok \"$(translate \"CT $CTID selected.\")\"\nmsg_ok \"$(translate \"Repositories verified.\")\"\nmsg_ok \"$(translate \"Disks to process: ${#DISK_LIST[@]}\")\"\n\n# Now execute operations\nfor i in \"${!DISK_LIST[@]}\"; do\n DISK=\"${DISK_LIST[$i]}\"\n FORMAT=\"${DISK_FORMAT_TYPES[$i]}\"\n\n msg_info \"$(translate \"Formatting\") $DISK $(translate \"as\") $FORMAT...\"\n mkfs.\"$FORMAT\" \"$DISK\" >/dev/null 2>&1\n msg_ok \"$(translate \"Formatted.\")\"\ndone\n\nmsg_ok \"$(translate \"Completed. ${#DISK_LIST[@]} disk(s) processed.\")\"\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r", + "phase2Rules": "Regler för fas 2: börja med show_proxmenux_logo + msg_title och sammanfatta genast fas 1 som msg_ok-rader. Anropa aldrig show_proxmenux_logo igen, eftersom det raderar förloppshistoriken. Använd inte dialog; om ett val under körningen är oundvikligt, använd whiptail enligt nästa avsnitt." + }, + "dialogVsWhiptail": { + "headingRich": "När du ska använda dialog vs whiptail", + "intro": "Båda verktygen ritar textbaserade gränssnitt men beter sig olika på skärmen. ProxMenux använder dem därför i två skilda faser.", + "headerTool": "Verktyg", + "headerWhen": "När du ska använda den", + "headerEffect": "Effekt på skärmen", + "rows": [ + { + "toolRich": "dialog", + "whenRich": "Alltid i fas 1. Alla interaktiva val, som VM, disk, filsystem eller bekräftelse av en åtgärd, använder dialog. Det är ProxMenux standardverktyg för gränssnitt.", + "effectRich": "Tar över terminalen, rensar skärmen, ritar sitt överlägg och återställer föregående tillstånd när den stängs. Det fungerar i fas 1 eftersom inga användbara resultat visas ännu." + }, + { + "toolRich": "whiptail", + "whenRich": "Bara i fas 2, när det är oundvikligt. Ett typiskt fall är en omstartsfråga när körningen är klar. Använd inte whiptail för att fråga ”fortsätt?” mitt i körningen; det beslutet skulle ha fattats i fas 1.", + "effectRich": "Ett lättare överlägg som inte rensar terminalens innehåll. Historiken från msg_ok/msg_info i fas 2 syns bakom dialogrutan. Därför passar det när förlopp redan visas på skärmen." + } + ], + "calloutTitle": "Varför uppdelningen är viktig", + "calloutBody": "Om du anropar dialog mitt i fas 2 försvinner alla msg_ok-rader användaren har följt. Det synliga händelseförloppet raderas. whiptail undviker det. Regeln bevarar alltså användarens bild av vad skriptet hittills har gjort.", + "rebootHeading": "Omstartsfråga: det typiska whiptail-fallet i fas 2", + "rebootIntro": "Om ett skript kan kräva omstart efter exempelvis aktivering av IOMMU eller ändrade kärnparametrar använder slutfrågan i fas 2 whiptail. Ha alltid en Nej-gren som säger att den berörda resursen inte ska användas före omstart:", + "rebootCode": "if [[ \"$HOST_REBOOT_REQUIRED\" == \"yes\" ]]; then\n echo \"\"\n if whiptail --title \"$(translate \"Reboot Required\")\" --yesno \\\n \"\\n$(translate \"A host reboot is required before starting the VM. Reboot now?\")\" \\\n 13 78; then\n msg_warn \"$(translate \"Rebooting the system...\")\"\n reboot\n else\n echo \"\"\n msg_info2 \"$(translate \"Do not start the VM until the system has been rebooted.\")\"\n fi\nfi\n\nmsg_success \"$(translate \"Press Enter to return to menu...\")\"\nread -r" + }, + "messageFunctions": { + "heading": "Referens för meddelandefunktioner", + "intro": "Alla finns i utils.sh. Använd dem som standard för synlig utdata så att skripten får ett enhetligt utseende. Om en ny funktion behövs, till exempel en allvarlighetsnivå eller layouthjälpare, föreslå den i din pull request. Den granskas och läggs till i utils.sh om den är användbar för fler skript.", + "headerFunction": "Funktion", + "headerWhen": "När man ska använda", + "headerSpinner": "Förloppsindikator", + "rows": [ + { + "function": "msg_info \"text\"", + "whenRich": "Operation pågår.", + "spinner": "Börjar" + }, + { + "function": "stop_spinner", + "whenRich": "Slutet av tyst förberedande arbete i fas 1 - dödar spinner, rensar linjen.", + "spinner": "Stopp" + }, + { + "function": "msg_ok \"text\"", + "whenRich": "Operationen lyckades. Använd också för \"funktion aktiverad\" även när en omstart krävs.", + "spinner": "Stopp" + }, + { + "function": "msg_warn \"text\"", + "whenRich": "Faktisk varning eller nedbrutet tillstånd.", + "spinner": "Stopp" + }, + { + "function": "msg_error \"text\"", + "whenRich": "Allvarligt fel.", + "spinner": "Stopp" + }, + { + "function": "msg_info2 \"text\"", + "whenRich": "Icke blockerande upplysning på en cyan informationsrad.", + "spinner": "Stopp" + }, + { + "function": "msg_success \"text\"", + "whenRich": "Avslutande frågan ”Tryck Enter för att återgå” i slutet av fas 2.", + "spinner": "Stopp" + }, + { + "function": "msg_title \"text\"", + "whenRich": "Fet rubrik med inbyggda mellanrum. Används i början av fas 2.", + "spinner": "—" + }, + { + "function": "show_proxmenux_logo", + "whenRich": "Rensar skärmen och visar logotypen. Anropas en gång i början av fas 2.", + "spinner": "—" + } + ] + }, + "dialogConventions": { + "heading": "dialogkonventioner", + "bullets": [ + "Skicka alltid --backtitle \"$BACKTITLE\" till varje dialog- och whiptail-anrop. $BACKTITLE är alltid \"ProxMenux\", satt en gång i skripthuvudet och aldrig överskrivet. Användaren ska alltid se projektnamnet som ram, inte skriptets egen titel.", + "Omslut alltid rubriker och meddelanden med $(translate \"...\").", + "Omdirigera alltid dialog-utgången med 2>&1 >/dev/tty - den fångade stdouten blir användarens val, medan dialogrutan själv drar på terminalen.", + "Använd standard UI dimension variabler ($UI_MENU_H, $UI_MSG_W, etc.) för konsekvent storlek över skript.", + "Kontrollera alltid tomma eller avbrutna val och hantera dem korrekt." + ], + "exampleIntro": "Fullständigt exempel: skapa en VM-valmeny och hantera avbrott.", + "exampleCode": "# 1) Build the list of VMs as alternating \"ID NAME\" pairs.\n# dialog --menu expects this exact shape: tag1 description1 tag2 description2 ...\nVM_LIST=\"\"\nfor vmid in $(qm list | awk 'NR>1 {print $1}'); do\n vm_name=$(qm config \"$vmid\" | awk -F': ' '/^name:/ {print $2}')\n VM_LIST=\"$VM_LIST $vmid \\\"$vm_name\\\"\"\ndone\n\n# 2) Show the dialog. Captured stdout is the user's selection (the VMID).\nVMID=$(eval \"dialog --backtitle \\\"\\$BACKTITLE\\\" \\\n --title \\\"\\$(translate \\\"Select VM\\\")\\\" \\\n --menu \\\"\\$(translate \\\"Choose a VM from the list:\\\")\\\" \\\n \\$UI_MENU_H \\$UI_MENU_W \\$UI_MENU_LIST_H \\\n $VM_LIST \\\n 2>&1 >/dev/tty\")\n\n# 3) Empty result means the user pressed Cancel or Esc — exit silently.\nif [ -z \"$VMID\" ]; then\n exit 0\nfi\n\n# 4) Continue with the rest of Phase 1, knowing VMID is now set.\necho \"User selected VMID=$VMID\"" + }, + "translation": { + "heading": "Översättningspolicy", + "intro": "Alla användarsynliga strängar måste inslagas med funktionen translate. ProxMenux översätter dem automatiskt till alla språk - du skriver engelska, användaren läser sitt modersmål.", + "code": "msg_ok \"$(translate \"Operation completed successfully.\")\"\nmsg_error \"$(translate \"Failed to start container\") $CTID.\"\ndialog --title \"$(translate \"Select Storage\")\" ...", + "bullets": [ + "Skriv strängar på engelska - översättning hanteras automatiskt av bygget.", + "Håll strängar koncis. Undvik inbäddningsvariabler inom långa meningar där det är möjligt.", + "Översätt inte variabelnamn, sökvägar eller tekniska identifierare." + ] + }, + "variableStyle": { + "heading": "Regler för variabler och stil", + "bullets": [ + "Använd UPPER_CASE för script-nivå variabler.", + "Använd lower_case för lokala funktionsvariabler (deklarera med local).", + "Sätt alla variabelexpansioner inom citattecken: \"$VAR\", inte $VAR.", + "Använd [[ ]] för villkor, inte [ ] (förutom där POSIX krävs).", + "show_proxmenux_logo är det avsedda sättet att rensa skärmen: den rensar och visar projektlogotypen för att ge visuellt sammanhang. Anropa den en gång i början av fas 2, och valfritt före ett längre block med väntindikator i fas 1." + ], + "standardNamesIntro": "Standardvariabla namn över hela projektet:", + "standardNamesCode": "CTID # container ID\nVMID # virtual machine ID\nDISK # device path (e.g. /dev/sdb)\nPARTITION # partition path (e.g. /dev/sdb1)\nSTORAGE # Proxmox storage name\nMOUNT_POINT # filesystem mount path", + "redirectHeading": "Omdirigera verktygsutgång under fas 2", + "redirectIntro": "Fas 2 visar en logg med msg_info → msg_ok-rader. Om ett anropat verktyg, som apt, mkfs, qm, pct eller dd, skriver till stdout/stderr rullar dess utdata förbi meddelandena och stör överblicken. Användaren kan inte längre skilja skriptets förlopp från verktygets detaljer.", + "withoutRedirectIntro": " Utan omdirigering - vad användaren ser om du inte hanterar ljudet:", + "withoutRedirectCode": "ℹ Installing kernel package...\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nThe following NEW packages will be installed:\n proxmox-kernel-6.5\n0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.\nNeed to get 12.5 MB of archives.\nAfter this operation, 47.2 MB of additional disk space will be used.\nGet:1 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 ...\n... [200+ more lines of dpkg output] ...\n✓ Kernel installed.", + "withRedirectIntro": " Med omdirigering - hur samma operation ser ut när buller skickas någon annanstans:", + "withRedirectCode": "ℹ Installing kernel package...\n✓ Kernel installed.\nℹ Configuring kernel command line...\n✓ Configured.\nℹ Refreshing boot loader...\n✓ Boot loader updated.", + "twoPatternsIntro": "Två mönster att välja mellan:", + "discardLead": "Kasta utdata när de inte behövs: snabbast och enklast.", + "discardCode": "DEBIAN_FRONTEND=noninteractive apt-get install -y \"$package\" >/dev/null 2>&1", + "logLead": "Skicka utdata till en loggfil när de kan behöva granskas senare, exempelvis vid en misslyckad installation eller för att se vad dpkg gjorde. Det är det föredragna mönstret för apt-åtgärder:", + "logCode": "apt-get install -y \"$package\" >> \"$log_file\" 2>&1", + "referenceOutro": "Skriptet scripts/global/update-pve9_2.sh är ett referensimplementering - varje apt-get-samtal skickar ut till en loggfil så att användaren bara ser det rena msg_info → msg_ok-flödet, medan loggen på disken låter dig rekonstruera exakt vad apt gjorde om något går fel." + }, + "dosAndDonts": { + "heading": "Do's och Don'ts", + "doHeading": "✅ Gör", + "doBullets": [ + "stop_spinner före en dialog i fas 1 endast när en msg_info spinner körs för närvarande.", + "Fas 2 börjar med show_proxmenux_logo + msg_title + msg_ok för att sammanfatta resultaten från fas 1.", + "Använd msg_ok för framgångsrikt aktiverade funktioner - även om en omstart krävs.", + "Använd whiptail (inte dialog) för någon post-exekveringsprompt som måste visas i Fas 2.", + "Ha alltid en Nej-gren i omstartsfrågor som säger att den berörda resursen inte ska startas före omstart.", + "Bevakning VM-bara logik genom att kontrollera [[ -f \"/etc/pve/qemu-server/$'{'vmid'}'.conf\" ]] - controllers och NVMe PCIe kan inte läggas till LXC containrar.", + "Använd ensure_repositories från utils-install-functions.sh istället för ovillkorlig apt-get update.", + "Använd parallella arrayer i Fas 1 när varje objekt behöver flera dialoger." + ], + "dontHeading": "❌ Gör inte", + "dontBullets": [ + "Anropa dialog medan en väntindikator är aktiv.", + "Hoppa över fas 1 recap i början av fas 2.", + "Anropa show_proxmenux_logo en andra gång; det raderar allt fas 2 har visat.", + "Använd dialog i Fas 2 (använd whiptail för det sällsynta oundvikliga fallet).", + "Använda enbart clear.", + "Omge msg_title med tomma echo \"\"-rader; mellanrum ingår redan.", + "Använd msg_warn för att rapportera en framgångsrikt aktiverad funktion - det är en msg_ok.", + "Kör ovillkorlig apt-get update - använd ensure_repositories." + ] + }, + "submitting": { + "heading": "Skicka in ditt bidrag", + "steps": [ + "Skapa en fork av MacRimi/ProxMenux på GitHub och klona din fork.", + "Skapa en feature/*-gren från develop (se Grenmodell ovan).", + "Följ denna guide för nya eller modifierade skript. Skriv rubriken med ditt namn; lägg till den valfria sponsorlinjen om du vill.", + "Testa ditt manus på en riktig Proxmox VE-instans - både Fas 1 (varje dialogruta) och Fas 2 (varje operation lyckas och rullar tillbaka rent på fel).", + "Öppna en pull request mot develop med en tydlig beskrivning: skriptets syfte, ändringar och den Proxmox VE-version som användes för tester.", + "Se till att ditt bidrag respekterar Uppförandekod." + ], + "securityOutro": "För säkerhetskänsliga problem följer du rapporteringsprocessen i SECURITY.md i stället för att öppna ett offentligt ärende." + }, + "whereNext": { + "heading": "Nästa steg", + "items": [ + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/blob/main/CONTRIBUTING.md", + "label": "CONTRIBUTING.md (fullständig guide)", + "tail": " — källan till sanning för varje konvention, inklusive avancerade hårdvarumönster." + }, + { + "kind": "internal", + "href": "/docs/about/contributors", + "label": "Bidragsgivare", + "tail": " — personerna vars arbete har format ProxMenux-utgåvor. Detta är en annan sida." + }, + { + "kind": "internal", + "href": "/docs/about/code-of-conduct", + "label": "Uppförandekod", + "tail": " - standarderna varje bidragsgivare går med på att följa." + }, + { + "kind": "external", + "url": "https://github.com/MacRimi/ProxMenux/discussions", + "label": "GitHub Discussions", + "tail": " - fråga innan du bygger om du inte är säker på om en idé passar, eller att hitta medarbetare på en större funktion." + } + ] + } +} diff --git a/web/messages/sv/docs/about/contributors.json b/web/messages/sv/docs/about/contributors.json new file mode 100644 index 00000000..c8580f3e --- /dev/null +++ b/web/messages/sv/docs/about/contributors.json @@ -0,0 +1,38 @@ +{ + "meta": { + "title": "Bidragsgivare | ProxMenux-dokumentation", + "description": "Ett erkännande till dem som gör ProxMenux möjligt: testare, granskare och utvecklare som bidrar till varje version.", + "ogTitle": "Bidragsgivare | ProxMenux-dokumentation", + "ogDescription": "Möt bidragsgivarna bakom ProxMenux." + }, + "header": { + "title": "Bidragsgivare", + "description": "ProxMenux växer tack vare sina bidragsgivare. Den här sidan uppmärksammar deras arbete: att testa versioner före publicering, hitta regressioner, granska PR:ar och påverka utvecklingen av nya funktioner.", + "section": "Om" + }, + "beyond": { + "title": "Utöver denna lista", + "body": "Många fler bidrar genom felrapporter, funktionsförslag i Discussions, betatestning och översättningar. Den fullständiga listan över kodbidragsgivare finns i GitHubs bidragsgivargraf. Den här sidan lyfter fram dem som är mest aktiva med att testa och granska ProxMenux-versioner." + }, + "testers": { + "heading": "Testare och granskare", + "intro": "De här personerna var bland de första att bidra till projektet. Deras tester, felrapporter och förbättringsförslag hjälpte ProxMenux att bli vad det är i dag.", + "roles": { + "testing": "Testning", + "testingReviewer": "Testning och granskning" + }, + "youtube": "YouTube" + }, + "contribute": { + "heading": "Vill du bidra?", + "intro": "Alla bidrag är välkomna - kod, testning, design, dokumentation eller bara dela dina idéer:", + "tester": "Testare — prova betakanalen och rapportera vad som fungerar och vad som inte fungerar före de stabila versionerna.", + "developer": "Utvecklare — Bash, Next.js (TypeScript) och lite Python för översättningens virtuella miljö (venv). PR:ar välkomnas på GitHub.", + "designer": "Designer — UI förbättringar, ikoner, skärmdumpar för dokumentation.", + "ideas": "Idéer — öppna en tråd i GitHub Discussions innan du skapar ett ärende med ett funktionsförslag." + }, + "coc": { + "title": "Läs uppförandekoden först", + "body": "Läs uppförandekoden och rekommenderade arbetssätt innan du skickar in kod eller rapporterar problem. Där beskrivs förväntningar på kodansvar, rapportering av säkerhetsbrister och omfattningen av PR:ar." + } +} diff --git a/web/messages/sv/docs/about/faq.json b/web/messages/sv/docs/about/faq.json new file mode 100644 index 00000000..ce2459dc --- /dev/null +++ b/web/messages/sv/docs/about/faq.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Vanliga frågor | ProxMenux-dokumentation", + "description": "Vanliga frågor om ProxMenux: funktioner, installation, kompatibilitet från PVE 8, anpassning, uppdateringar, säkerhet, produktionsanvändning, systemfiler, bidrag och avinstallation.", + "ogTitle": "Vanliga frågor | ProxMenux-dokumentation", + "ogDescription": "Vanliga frågor om ProxMenux installation, kompatibilitet, säkerhet och hur du kan bidra." + }, + "header": { + "title": "Vanliga frågor", + "description": "Vanliga frågor om ProxMenux: vad det är, vem det passar, installation, uppdatering, avinstallation, kompatibilitet, anpassning, säkerhet och bidrag. Om din fråga saknas, titta i GitHub Discussions eller skapa ett ärende.", + "section": "Om" + }, + "quickLinks": { + "title": "Snabba länkar", + "installationLabel": "Installation", + "installationSuffix": " — installationskommandot på en rad och dess beroenden.", + "introductionLabel": "Introduktion", + "introductionSuffix": " — vad ProxMenux gör, på fem minuter.", + "uninstallLabel": "Avinstallera", + "uninstallSuffix": " — ren borttagning.", + "issuesLabel": "GitHub Issues", + "discussionsLabel": "Diskussioner" + }, + "q1": { + "question": "Vad är ProxMenux, och vad används det för?", + "p1Rich": "ProxMenux är ett interaktivt menystyrt verktyg som gör Proxmox VE tillgängligt oavsett teknisk erfarenhet. Det förenklar körningen av kommandon och låter dig hantera systemet utan avancerade Linux-kunskaper.", + "p2": "Mindre erfarna användare kan köra kommandon via strukturerade menyer och behöver skriva mindre i terminalen. Erfarna administratörer sparar steg i återkommande uppgifter som härdning efter installation, GPU-passthrough, import av OVF-VM:ar och rotation av ZFS-snapshots.", + "p3": "Proxmox VE används ofta för:", + "items": [ + "Virtualisering i företagsklass", + "HomeLab och personliga molnlösningar", + "Multimedieservrar, automatisering och mycket mer" + ] + }, + "q2": { + "question": "Hur installerar jag ProxMenux?", + "p1Rich": "Följ installationsguiden. Kortfattat:", + "stableInstall": "bash -c \"$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)\"", + "p2": "När installationen är klar startar du verktyget med:", + "menuCmd": "menu" + }, + "q3": { + "question": "Är ProxMenux kompatibel med alla Proxmox-versioner?", + "bodyRich": "Nej — ProxMenux kräver Proxmox VE 8 eller senare. PVE 7 och äldre stöds inte. Uppgradera först PVE 7 till 8.x enligt Proxmox officiella anvisningar. Därefter kan ProxMenux hantera steget från 8 till 9 via Verktyg → Uppgradera PVE 8 till PVE 9." + }, + "q4": { + "question": "Kan jag anpassa ProxMenux?", + "p1Rich": "Uppdateringar kan skriva över lokala ändringar i huvudskripten, så undvik att redigera dessa filer direkt. Det här kan du anpassa:", + "items": [ + "Konsolens välkomstbanner med verktyget FastFetch i menyn för efterinstallation.", + "Vilka valfria komponenter installeras (Monitor, Fail2Ban, Lynis ...) - välj under installation eller via den relevanta menyn senare." + ] + }, + "q5": { + "question": "Hur uppdaterar jag ProxMenux?", + "p1Rich": "När en ny version finns upptäcker ProxMenux den nästa gång du kör menu och erbjuder en uppdatering. Om du accepterar ersätts verktygets filer och konfiguration i den befintliga installationen. Du behöver inte ladda ned något manuellt.", + "p2Rich": "Användare av den stabila versionen får stabila uppdateringar, och betaanvändare får betauppdateringar. När en stabil version släpps meddelar betan det nästa gång menyn öppnas och erbjuder automatiskt byte. Läs mer om betaprogrammet." + }, + "q6": { + "question": "Var kan jag rapportera problem eller föreslå funktioner?", + "p1Rich": "Rapportera buggar i GitHub Issues. Beskriv stegen för att återskapa felet, felmeddelandet och vid behov Monitor-utdata från journalctl -u proxmenux-monitor -n 50.", + "p2Rich": "För säkerhetsproblem: publicera inte detaljerna. Följ rutinen för ansvarsfull rapportering i uppförandekoden och rekommendationerna." + }, + "q7": { + "question": "Kan jag bidra till ProxMenux?", + "p1Rich": "Absolut. ProxMenux är ett samarbetsprojekt med öppen källkod. Du kan bidra med kod (Bash, Next.js/TypeScript och översättningsverktyg i Python), tester, dokumentation, design eller idéer.", + "item1Rich": "Diskutera idéer i GitHub Discussions innan du skapar en funktionsförfrågan.", + "item2Rich": "Läs uppförandekoden och rekommendationerna innan du skickar en pull request.", + "item3Rich": "På sidan för medverkande ser du vilka som för närvarande testar och granskar projektet." + }, + "q8": { + "question": "Ändrar ProxMenux kritiska systemfiler?", + "p1Rich": "Installationen av ProxMenux lägger till följande komponenter.", + "item1Rich": "Beroenden via apt: dialog, curl, jq och git. Översättningarna använder färdiga JSON-kataloger. Installationsprogrammet lägger inte till någon virtuell översättningsmiljö på värden.", + "item2Rich": "Skriptträdet på /usr/local/share/proxmenux/.", + "item3Rich": "Startkommandot i /usr/local/bin/menu.", + "item4Rich": "Valfritt: Monitors systemd-tjänst proxmenux-monitor.service.", + "p2Rich": "ProxMenux ändrar inte Proxmox centrala konfiguration om du inte uttryckligen begär det via en särskild meny. Nätverksreparationen redigerar till exempel /etc/network/interfaces, men först efter din bekräftelse och efter att en säkerhetskopia har skapats." + }, + "q9": { + "question": "Är det säkert att använda ProxMenux i produktion?", + "bodyRich": "Ja. Eftersom ProxMenux bara ändrar Proxmox centrala filer på uttrycklig begäran kan det användas i produktionsmiljöer. Som med all automatisering bör du först testa i en kontrollerad miljö och ha säkerhetskopior innan du kör destruktiva åtgärder, till exempel uppgradering från PVE 8 till 9, diskformatering eller borttagning av en VM." + }, + "q10": { + "question": "Hur avinstallerar jag ProxMenux?", + "p1Rich": "I ProxMenux: Inställningar → Avinstallera ProxMenux. Avinstallationen tar bort skriptträdet, startkommandot och Monitor-tjänsten samt, om du väljer det, utvalda beroenden. Den återställer /root/.bashrc och /etc/motd från säkerhetskopiorna som skapades vid installationen, om de finns.", + "p2Rich": "Fullständiga instruktioner: Avinstallera ProxMenux." + }, + "q11": { + "question": "VirusTotal flaggar installationsadressen — är den säker?", + "p1Rich": "Resultatet 1/95 från Chong Lua Dao, som rapporterades i ärende #162, bedömdes av projektansvarig som ett falsklarm. Installationsprogrammet använder mönstret curl | bash och kan ladda ned jq från dess officiella GitHub-utgåva. Detta beteende kan utlösa heuristiska skannrar. Den historiska rapporten säger inget avgörande om alla framtida nedladdningar.", + "p2Rich": "ProxMenux har helt öppen källkod och du kan granska installationsskriptet rad för rad: install_proxmenux.sh. Mer bakgrund finns i ärende #162." + } +} diff --git a/web/messages/sv/docs/about/index.json b/web/messages/sv/docs/about/index.json new file mode 100644 index 00000000..437a1b7e --- /dev/null +++ b/web/messages/sv/docs/about/index.json @@ -0,0 +1,67 @@ +{ + "meta": { + "title": "Om projektet | ProxMenux-dokumentation", + "description": "Om ProxMenux: projektmål, bidragsgivare, vanliga frågor och uppförandekod. Ett menystyrt hanteringsverktyg med öppen källkod för Proxmox VE 8+, utvecklat och underhållet av MacRimi tillsammans med en liten grupp testare och bidragsgivare.", + "ogTitle": "Om projektet | ProxMenux-dokumentation", + "ogDescription": "Om ProxMenux — ett menystyrt hanteringsverktyg med öppen källkod för Proxmox VE 8+." + }, + "header": { + "title": "Om", + "description": "Projektinformation, vanliga frågor, bidragsgivare och uppförandekod. ProxMenux är ett menystyrt hanteringsverktyg med öppen källkod för Proxmox VE 8+, utvecklat och underhållet av MacRimi med hjälp av en liten grupp testare och bidragsgivare.", + "section": "Om" + }, + "callout": { + "title": "Öppen källkod, utvecklad av gemenskapen", + "body": "ProxMenux har GPL-3.0-licens och utvecklas öppet på GitHub. Varje skript kan granskas och varje ändring spåras. Felrapporter, idéer, kod och testning välkomnas." + }, + "section": { + "heading": "I detta avsnitt", + "options": [ + { + "icon": "HelpCircle", + "href": "/docs/about/faq", + "title": "FAQ", + "description": "Vanliga frågor: vad ProxMenux är, installation, kompatibilitet, anpassning, uppdateringar, säkerhet, användning i produktion och avinstallation." + }, + { + "icon": "Users", + "href": "/docs/about/contributors", + "title": "Bidragsgivare", + "description": "Ett erkännande till dem som gör ProxMenux möjligt: testare, granskare och utvecklare som bidrar till varje version." + }, + { + "icon": "ScrollText", + "href": "/docs/about/code-of-conduct", + "title": "Uppförandekod", + "description": "Projektvärden, kodansvarsförväntningar och bidragsriktlinjer. Obligatorisk läsning innan du öppnar en PR eller rapport." + } + ] + }, + "involved": { + "heading": "Bli involverad", + "intro": "Tre ställen att engagera sig i projektet:", + "cards": [ + { + "href": "https://github.com/MacRimi/ProxMenux/issues/new", + "title": "🐛 Rapportera en bugg", + "description": "GitHub Issues — bifoga loggar och steg för att återskapa problemet." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/discussions", + "title": "💡 Föreslå en funktion", + "description": "GitHub Discussions — dela idéer innan du öppnar en PR." + }, + { + "href": "https://github.com/MacRimi/ProxMenux/pulls", + "title": "🔀 Skicka en PR", + "description": "GitHub Pull Requests - läs uppförandekoden först." + } + ] + }, + "support": { + "heading": "Stöd projektet", + "introRich": "Om ProxMenux sparar tid åt dig är det enklaste sättet att hjälpa andra hitta det att ge förrådet en ⭐ på GitHub. Om du vill göra mer:", + "kofiLabel": "Stöd på Ko-fi", + "kofiOutro": " — Köper underhållaren en kaffe och håller utvecklingen på gång." + } +} From a9241d7a6cdb477f49e38139b03a2171225c829f Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:08:54 +0200 Subject: [PATCH 17/53] docs: review security documentation in all seven translations --- .github/workflows/test-i18n.yml | 10 ++ web/messages/de/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/de/docs/security/index.json | 70 ++++++++ web/messages/de/docs/security/lynis.json | 170 ++++++++++++++++++ .../de/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ web/messages/en/docs/security/fail2ban.json | 4 +- .../en/docs/security/ssl-letsencrypt.json | 4 +- web/messages/es/docs/security/fail2ban.json | 34 ++-- web/messages/es/docs/security/index.json | 36 ++-- web/messages/es/docs/security/lynis.json | 40 ++--- .../es/docs/security/ssl-letsencrypt.json | 52 +++--- web/messages/fr/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/fr/docs/security/index.json | 70 ++++++++ web/messages/fr/docs/security/lynis.json | 170 ++++++++++++++++++ .../fr/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ web/messages/it/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/it/docs/security/index.json | 70 ++++++++ web/messages/it/docs/security/lynis.json | 170 ++++++++++++++++++ .../it/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ web/messages/pt/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/pt/docs/security/index.json | 70 ++++++++ web/messages/pt/docs/security/lynis.json | 170 ++++++++++++++++++ .../pt/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ web/messages/sk/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/sk/docs/security/index.json | 70 ++++++++ web/messages/sk/docs/security/lynis.json | 170 ++++++++++++++++++ .../sk/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ web/messages/sv/docs/security/fail2ban.json | 152 ++++++++++++++++ web/messages/sv/docs/security/index.json | 70 ++++++++ web/messages/sv/docs/security/lynis.json | 170 ++++++++++++++++++ .../sv/docs/security/ssl-letsencrypt.json | 169 +++++++++++++++++ 31 files changed, 3461 insertions(+), 85 deletions(-) create mode 100644 web/messages/de/docs/security/fail2ban.json create mode 100644 web/messages/de/docs/security/index.json create mode 100644 web/messages/de/docs/security/lynis.json create mode 100644 web/messages/de/docs/security/ssl-letsencrypt.json create mode 100644 web/messages/fr/docs/security/fail2ban.json create mode 100644 web/messages/fr/docs/security/index.json create mode 100644 web/messages/fr/docs/security/lynis.json create mode 100644 web/messages/fr/docs/security/ssl-letsencrypt.json create mode 100644 web/messages/it/docs/security/fail2ban.json create mode 100644 web/messages/it/docs/security/index.json create mode 100644 web/messages/it/docs/security/lynis.json create mode 100644 web/messages/it/docs/security/ssl-letsencrypt.json create mode 100644 web/messages/pt/docs/security/fail2ban.json create mode 100644 web/messages/pt/docs/security/index.json create mode 100644 web/messages/pt/docs/security/lynis.json create mode 100644 web/messages/pt/docs/security/ssl-letsencrypt.json create mode 100644 web/messages/sk/docs/security/fail2ban.json create mode 100644 web/messages/sk/docs/security/index.json create mode 100644 web/messages/sk/docs/security/lynis.json create mode 100644 web/messages/sk/docs/security/ssl-letsencrypt.json create mode 100644 web/messages/sv/docs/security/fail2ban.json create mode 100644 web/messages/sv/docs/security/index.json create mode 100644 web/messages/sv/docs/security/lynis.json create mode 100644 web/messages/sv/docs/security/ssl-letsencrypt.json diff --git a/.github/workflows/test-i18n.yml b/.github/workflows/test-i18n.yml index 2b3e4685..7f4dfb51 100644 --- a/.github/workflows/test-i18n.yml +++ b/.github/workflows/test-i18n.yml @@ -9,15 +9,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' push: branches: [main, develop] paths: @@ -27,15 +32,20 @@ on: - 'AppImage/messages/**' - 'AppImage/lib/i18n/**' - 'AppImage/components/terminal-panel.tsx' + - 'web/**' - 'CONTRIBUTING.md' # Scheduler consumers and the offline extractor's .sh/.func inputs. - 'scripts/**/*.sh' - 'scripts/**/*.func' - 'scripts/**/*.py' - 'lang/*.json' + - 'menu' + - 'install_proxmenux.sh' + - 'install_proxmenux_beta.sh' - 'oci/src/**/*.py' - 'oci/catalog/**/*.json' - 'oci/remote/**/*.sh' + - 'oci/remote/**/*.py' workflow_dispatch: permissions: diff --git a/web/messages/de/docs/security/fail2ban.json b/web/messages/de/docs/security/fail2ban.json new file mode 100644 index 00000000..e65a6918 --- /dev/null +++ b/web/messages/de/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | ProxMenux-Dokumentation", + "description": "Installieren Sie Fail2Ban mit drei jails abgestimmt auf Proxmox (SSH aggressiv, Proxmox UI 8006, ProxMenux Monitor 8008). Beinhaltet den Fix journald MaxLevelStore, benutzerdefinierte Protokolldienste für Zuverlässigkeit, automatisch erkanntes nftables/iptables-Backend und SSH MaxAuthTries Härtung.", + "ogTitle": "Fail2Ban | ProxMenux-Dokumentation", + "ogDescription": "Brute-Force-Schutz für SSH und Proxmox-Weboberflächen mit Proxmox-spezifischen journald- und Backend-Korrekturen." + }, + "header": { + "title": "Fail2Ban", + "description": "Installiert Fail2Ban für Proxmox: drei Jails (SSH aggressiv, Proxmox-Weboberfläche auf 8006, ProxMenux Monitor auf 8008 und Reverse Proxy), journald-Korrektur zum Speichern von SSH-Anmeldeereignissen, zwei Journal-zu-Datei-Dienste als Umgehung eines bekannten systemd-Backend-Problems, automatische Firewall-Backend-Erkennung und SSH-Härtung mit MaxAuthTries.", + "section": "Sicherheit" + }, + "intro": { + "title": "Was das macht", + "body": "Installiert Fail2Ban aus Debian-Paketquellen und schreibt eine Proxmox-Konfiguration, die SSH, Proxmox-Weboberfläche und ProxMenux Monitor sofort schützt. Bei vorhandener Installation erscheint stattdessen ein Verwaltungsmenü zum Neuinstallieren oder Entfernen." + }, + "firstLaunch": { + "heading": "Erststart-Dialog", + "body": "Ohne vorhandenes Fail2Ban zeigt das Skript einen Bestätigungsdialog mit den geplanten Installations- und Konfigurationsschritten. Abbrechen beendet ohne Änderungen; Bestätigen startet die Installation.", + "imageAlt": "Fail2Ban-Installationsbestätigung mit drei Jails, journald-Korrektur und SSH-Härtung" + }, + "jails": { + "heading": "Die drei jails", + "headerJail": "Jail", + "headerProtects": "Schützt", + "headerRetries": "Fehlversuche / Zeitfenster", + "headerBan": "Sperrdauer", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (aggressiver Modus — umfasst ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 Stunden" + }, + { + "jail": "[proxmox]", + "protects": "Proxmox web UI (Port 8006)", + "retries": "3 / 10 min", + "ban": "1 Stunde" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (Port 8008 + http/https Reverse Proxy)", + "retries": "3 / 10 min", + "ban": "1 Stunde" + } + ], + "outro": "Globale Standardwerte von jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (24-Stunden-Fallback für jails, die es nicht überschreiben), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Warum die journald-Korrektur wichtig ist", + "intro": "Proxmox liefert /etc/systemd/journald.conf mit MaxLevelStore=warning. journald verwirft Meldungen unterhalb von warning vor dem Speichern. SSH und PAM melden Anmeldefehler auf den Stufen info / notice, daher :", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "Anmeldefehler\n(Stufe info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ Ereignis ohne Hinweis verworfen", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "Nichts sehen\nNiemals etwas verbieten", + "arrowLabel": "Standard Proxmox" + }, + "afterDiagram": "Der Installateur erkennt dies und schreibt ein Drop-in bei /etc/systemd/journald.conf.d/proxmenux-loglevel.conf und erhöht sowohl MaxLevelStore als auch MaxLevelSyslog auf info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Dann startet systemd-journald neu. Das Drop-In wird bei der Deinstallation entfernt, wodurch der ursprüngliche Proxmox-Standard wiederhergestellt wird." + }, + "loggers": { + "heading": "Warum zwei Custom Logger Services", + "intro1": "Fail2Ban kann direkt aus dem systemd-Journal lesen (backend = systemd). Auf Proxmox gibt es jedoch bekannte Zuverlässigkeitsprobleme mit Worker-Prozessen von pvedaemon: Anmeldeereignisse erscheinen im Journal, werden aber nicht immer erkannt. Zeitweise betrifft dies auch sshd.", + "intro2": "ProxMenux erstellt zwei kleine systemd-Dienste, die die betreffenden Units mit journalctl -f verfolgen und jede Zeile an eine Datei anhängen. Fail2Ban liest diese Dateien mit dem zuverlässigen backend = auto (Dateimodus) :", + "headerService": "Dienst", + "headerSource": "Quelleneinheit", + "headerOutput": "Ausgabedatei", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Beide Dienste sind als PartOf=fail2ban.service deklariert, sodass sie mit Fail2Ban neu starten und damit stoppen. Modus 640 im Besitz von root:adm in den Protokolldateien. Das dritte Protokoll, das von [proxmenux] jail (/var/log/proxmenux-auth.log) verwendet wird, wird direkt von der App ProxMenux Monitor Flask geschrieben – dafür ist kein Logger-Dienst erforderlich." + }, + "backend": { + "heading": "Firewall-Backend-Autoerkennung", + "intro": "Der Installer prüft den Host: Wenn nft list ruleset funktioniert, wählt er nftables als Sperraktion. Ansonsten fällt es auf iptables-multiport / iptables-allports zurück. Die Auswahl ist in jail.local geschrieben:", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "SSH-Härtung: MaxAuthTries", + "intro": "Die Lynis-Prüfung SSH-7408 empfiehlt MaxAuthTries 3 in sshd_config. Die SSH-Jail verwendet maxretry = 2 für Sperren nach wiederholten Fehlversuchen; MaxAuthTries begrenzt die Versuche innerhalb einer Verbindung. Diese SSH-Einstellung bietet zusätzliche Abwehr, auch bei wegen Wartung gestopptem Fail2Ban.", + "installerIntro": "Der Installer:", + "items": [ + "Lesen Sie den aktuellen MaxAuthTries-Wert (oder standardmäßig 6, falls nicht vorhanden).", + "Speichert es in /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Bearbeitet sshd_config direkt: ersetzt die vorhandene Zeile, entfernt das Kommentarzeichen oder fügt eine neue Zeile hinzu.", + "Lädt sshd neu, ohne den Dienst neu zu starten, damit bestehende Sitzungen aktiv bleiben." + ], + "outro": "Bei der Deinstallation wird der gespeicherte Originalwert wiederhergestellt und sshd erneut geladen." + }, + "manage": { + "heading": "Verwalten einer vorhandenen Installation", + "intro": "Wenn Fail2Ban beim Öffnen des Menüs bereits installiert ist, erkennt das Skript es und zeigt ein Verwaltungsmenü an, anstatt das Installationsprogramm erneut auszuführen:", + "headerAction": "Aktion", + "headerWhat": "Was es tut", + "rows": [ + { + "action": "Neuinstallieren", + "what": "Führt den vollständigen Installer erneut aus und schreibt alle Jails mit den aktuellen ProxMenux-Vorgaben neu. Verwenden Sie dies, wenn ein Update die empfohlenen Werte ändert." + }, + { + "action": "Entfernen", + "what": "Stoppt fail2ban und die beiden Logger-Dienste, löscht das apt-Paket, entfernt alle jail/Filterdateien, löscht das journald-Drop-In (stellt die Proxmox-Standardeinstellung wieder her) und stellt das ursprüngliche SSH wieder her. MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Überprüfen Sie, ob es funktioniert", + "intro": "Nach der Installation nützliche Befehle vom Host:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "neverBansTitle": "Fail2Ban läuft, verbietet aber niemals etwas", + "neverBansBody": "Prüfen Sie, ob tail -f /var/log/ssh-auth.log nach einer SSH-Anmeldung mit falschem Passwort von einem anderen Rechner einen Eintrag erhält. Bleibt die Datei leer, prüfen Sie systemctl status ssh-auth-logger.service. Ist der Logger aktiv, zeigen Sie aktuelle SSH-Ereignisse mit journalctl -u ssh.service -n 50 --no-pager -p info an und prüfen, ob der journald-Zusatz die Stufe info erlaubt.", + "monitorEmptyTitle": "ProxMenux Monitor jail hat keine Einträge, auch nach fehlgeschlagenen Anmeldungen", + "monitorEmptyBody": "Der [proxmenux] jail liest /var/log/proxmenux-auth.log, der von der ProxMenux Monitor Flask-App geschrieben wurde – nicht von einem journald-Logger. Wenn Sie den Monitor nicht ausführen, bleibt die Datei leer und jail wird nie ausgelöst. Das wird erwartet; Die jail-Konfiguration ist harmlos. Wenn Sie den Monitor ausführen und das Protokoll leer ist, überprüfen Sie die Protokollierungskonfiguration Flask.", + "selfBanTitle": "Ich habe meinen eigenen Zugriff gesperrt", + "selfBanIntro": "Von einer Konsole / IPMI / iKVM (oder einer anderen Whitelist-IP):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "apt-get schlägt fehl: ''Unable to locate package fail2ban''", + "aptFailBody": "Dem Host fehlen die Debian-Repos (üblich bei Barebones, die Proxmox installiert). Der Installateur erkennt dies und schreibt /etc/apt/sources.list.d/debian.sources mit dem richtigen Codenamen (bookworm / trixie), bevor er es erneut versucht. Wenn es immer noch fehlschlägt, überprüfen Sie /etc/os-release für VERSION_CODENAME und bestätigen Sie, dass die Repo-URL erreichbar ist.", + "lockoutTitle": "SSH sperrt mich nach der Installation aus", + "lockoutBody": "Das Installationsprogramm setzt MaxAuthTries=3. Wenn Ihr Passwort-Manager / Agent mehrere Schlüssel wiederholt, können Sie diese bei einem einzigen Verbindungsversuch überschreiten. Begrenzen Sie die angebotenen Schlüssel: ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. Oder erhöhen Sie MaxAuthTries vorübergehend in sshd_config, während Sie debuggen." + }, + "files": { + "heading": "Dateien geschrieben", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Verwandte Themen", + "monitorLabel": "ProxMenux Monitor - Sicherheits-Tab", + "monitorTail": " — Installation über das Dashboard, aktueller Jail-Status, gesperrte IP-Adressen und Einstellung von Fehlversuchen und Sperrdauer je Jail im Browser.", + "lynisLabel": "Lynis", + "lynisTail": " — führen Sie vorher und nachher eine Sicherheitsprüfung aus, um die Erfüllung von SSH-7408 zu bestätigen.", + "securityLabel": "Sicherheitsübersicht", + "securityTail": " — zurück zur Abschnittsübersicht." + } +} diff --git a/web/messages/de/docs/security/index.json b/web/messages/de/docs/security/index.json new file mode 100644 index 00000000..17af6319 --- /dev/null +++ b/web/messages/de/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Proxmox-Sicherheit — Fail2Ban, Lynis-Härtungsprüfung | ProxMenux", + "description": "Zwei installierbare Sicherheitswerkzeuge für Proxmox VE: Fail2Ban (Einbruchsschutz für SSH, Proxmox-Weboberfläche und ProxMenux Monitor) und Lynis (Sicherheits- und Härtungsprüfung aus dem offiziellen GitHub-Repository).", + "ogTitle": "Proxmox-Sicherheit — Fail2Ban und Lynis-Härtungsprüfung", + "ogDescription": "Fail2Ban und Lynis für Proxmox VE: Einbruchsschutz und Härtungsprüfung, installiert und verwaltet durch ProxMenux.", + "twitterTitle": "Proxmox Sicherheit | ProxMenux", + "twitterDescription": "Fail2Ban und Lynis für Proxmox VE: Einbruchsschutz und Härtungsprüfung." + }, + "header": { + "title": "Sicherheit", + "description": "Zwei ergänzende Sicherheitswerkzeuge für Proxmox VE: aktive Abwehr (Fail2Ban sperrt IP-Adressen, die SSH oder Weboberflächen angreifen) und Offline-Prüfung (Lynis sucht Härtungslücken und vergibt 0–100 Punkte). Beide haben ein eigenes Installations- und Verwaltungsmenü, das vorhandene Installationen vor jeder Aktion erkennt.", + "section": "Sicherheit" + }, + "intro": { + "title": "Aktive Abwehr und Offline-Prüfung", + "body": "Sie beantworten unterschiedliche Fragen. Fail2Ban beantwortet „Werden wir gerade angegriffen?“, indem es Anmeldeprotokolle überwacht und angreifende IP-Adressen sperrt. Lynis beantwortet „Wie gut ist dieser Host gehärtet?“ mit einer einmaligen Prüfung und konkreten Empfehlungen. Nutzen Sie beide: Lynis zeigt Verbesserungen auf, Fail2Ban reagiert auf laufende Angriffe." + }, + "opening": { + "heading": "Öffnen des Menüs", + "body": "Wählen Sie im ProxMenux-Hauptmenü Sicherheit. Folgendes wird angezeigt :", + "imageAlt": "Sicherheitsmenü mit Fail2Ban und Lynis Optionen" + }, + "pick": { + "heading": "Wählen Sie Ihr Werkzeug", + "body": "Die beiden Werkzeuge sind unabhängig: Installieren Sie eines, beide oder keines. Jede Karte unten führt zum Abschnitt mit der ausführlichen Erklärung." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Aktiver Einbruchsschutz. Überwacht Anmeldeprotokolle von SSH und Weboberflächen und sperrt IP-Adressen nach wiederholten Fehlversuchen.", + "bullets": [ + "Drei jails: SSH, Proxmox UI (8006), ProxMenux Monitor (8008)", + "Korrigiert Proxmox-Standardeinstellungen von journald, die Anmeldeprotokolle blockieren", + "Erkennt nftables / iptables automatisch", + "SSH-Härtung: MaxAuthTries=3 (Empfehlung von Lynis)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Offline-Sicherheitsprüfer. Prüft den Host und zeigt eine Härtungsbewertung sowie konkrete Hinweise zur Behebung von Problemen.", + "bullets": [ + "Aus dem offiziellen CISOfy-GitHub-Repository installiert (neueste Version)", + "Härtungsbewertung 0–100 und Liste mit Warnungen und Vorschlägen", + "Prüfung und Aktualisierung über das Menü", + "Nur lesend konzipiert: verändert das System nicht" + ] + } + }, + "workflowTip": { + "title": "Workflow, der beides nutzt", + "body": "Führen Sie zuerst Lynis aus, um Ausgangsbewertung und wichtigste Empfehlungen zu sehen. Setzen Sie gewünschte Korrekturen um. Installieren Sie dann Fail2Ban: Es setzt automatisch den SSH-Brute-Force-Schutz um und passt MaxAuthTries in sshd_config an die Prüfung SSH-7408 an. Prüfen Sie anschließend mit Lynis die Verbesserung." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "ProxMenux installiert Fail2Ban mit einer speziell für Proxmox optimierten Konfiguration. Über den Standard SSH jail hinaus bietet es Schutz für das Proxmox-Web UI und das ProxMenux Monitor und umgeht zwei Proxmox-spezifische Macken: eine journald-Standardeinstellung, die Authentifizierungsereignisse verwirft, und die systemd-Backend-Problem, das verhindert, dass Fail2Ban bestimmte Journalquellen zuverlässig liest. Die Detailseite behandelt den vollständigen Installationsablauf, die drei jails, den journald-Fix, die SSH-Härtungsänderung und den Fehlerbehebungs-Cheatsheet.", + "optionTitle": "Fail2Ban — installieren und verwalten", + "optionDescription": "Drei vorkonfigurierte Jails (sshd aggressiv, Proxmox-Weboberfläche auf 8006, ProxMenux Monitor auf 8008 und Reverse Proxy), automatische nftables/iptables-Erkennung, journald-Korrektur und SSH-Härtung mit MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "ProxMenux klont Lynis von github.com/CISOfy/lynis nach /opt/lynis und macht es als /usr/local/bin/lynis verfügbar. Das Debian-Paket wird bewusst vermieden, da es mehrere Hauptversionen hinter dem Originalprojekt liegt. Die Detailseite erklärt Installation, Prüfung, Aktualisierung, Deinstallation, Auswertung des Berichts und Umsetzung der Ergebnisse.", + "optionTitle": "Lynis — installieren, auditieren und verwalten", + "optionDescription": "Installiert das neueste Lynis aus dem offiziellen CISOfy GitHub-Repo (nicht das ältere Debian-Paket), führt Systemaudits mit Hardening-Score und Updates per Git Pull aus." + }, + "componentStatus": { + "heading": "Komponentenstatus", + "body": "Beide Installateure registrieren ihren Status in /usr/local/share/proxmenux/components_status.json unter der Kategorie security. ProxMenux verwendet diese Datei, um zu entscheiden, ob die Installation oder das Menü bei nachfolgenden Durchläufen angezeigt werden soll, und die gleichen Daten speisen die Dashboards im ProxMenux Monitor, wenn vorhanden." + } +} diff --git a/web/messages/de/docs/security/lynis.json b/web/messages/de/docs/security/lynis.json new file mode 100644 index 00000000..42c5b8bd --- /dev/null +++ b/web/messages/de/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | ProxMenux-Dokumentation", + "description": "Installieren Sie die neueste Lynis-Version aus dem offiziellen CISOfy-GitHub-Repository, führen Sie eine Sicherheitsprüfung mit Härtungsbewertung 0–100 aus und aktualisieren Sie per git pull. Rein lesende Prüfung ohne Systemänderungen.", + "ogTitle": "Lynis | ProxMenux-Dokumentation", + "ogDescription": "Lynis-Sicherheitsprüfung auf Proxmox VE installieren und ausführen. Installation aus dem Original-GitHub-Repository, Prüfung und Aktualisierung." + }, + "header": { + "title": "Lynis", + "description": "Klont die neueste Lynis-Version aus dem offiziellen CISOfy-GitHub-Repository, stellt sie als /usr/local/bin/lynis bereit und bietet Prüfung, Aktualisierung, Neuinstallation und Deinstallation im Menü. Der Auditor liest nur und meldet Ergebnisse, ohne das System zu ändern.", + "section": "Sicherheit" + }, + "intro": { + "title": "Was das macht", + "body": "Installiert Lynis, den Open-Source-Sicherheitsprüfer von CISOfy, aus dessen GitHub-Repository nach /opt/lynis und erstellt einen Starter unter /usr/local/bin/lynis für den Aufruf über PATH. Eine vorhandene Installation öffnet stattdessen ein Verwaltungsmenü für Prüfung, Aktualisierung, Neuinstallation oder Entfernung." + }, + "manageMenu": { + "heading": "Verwaltungsmenü (nach der Installation)", + "intro": "Nach der Installation öffnet jeder weitere Aufruf das Verwaltungsmenü. Dort können Sie eine Prüfung starten, per git pull aktualisieren, neu installieren oder deinstallieren.", + "imageAlt": "Lynis Management Menü mit Audit, Update, Neuinstallation und Entfernen von Optionen" + }, + "whyUpstream": { + "heading": "Warum das Original-GitHub-Repository statt apt", + "intro": "Debian bietet Lynis über apt an, doch das Paket liegt meist mehrere Hauptversionen hinter dem Originalprojekt. Neue Prüfungen, Korrekturen für neue Angriffsvektoren und überarbeitete Empfehlungen erscheinen zuerst auf GitHub. Lynis ist ein eigenständiges Shell-Skript ohne Kompilierung und ohne weitere Installationsabhängigkeiten außer git; CISOfy empfiehlt daher das Klonen des Repositorys.", + "headerSource": "Quelle", + "headerPath": "Install path", + "headerUpdate": "Aktualisierungsmethode", + "headerFresh": "Aktualität der Version", + "rows": [ + { + "sourceRich": "ProxMenux (dieses Skript)", + "path": "/opt/lynis/", + "update": "git pull (im Menü)", + "fresh": "Neueste Version des Originalprojekts" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Oft Monate / Jahre zurück" + } + ] + }, + "install": { + "heading": "Wie die Installation funktioniert", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(git zuerst installieren, falls nicht vorhanden)", + "node2Label": "/opt/lynis/", + "node2Detail": "Vollständiges Repo\neinschließlich ./lynis Skript", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "Umschlag:\ncd /opt/lynis && ./lynis $@", + "outro": "Der Wrapper ist obligatorisch - Lynis besteht darauf, aus seinem eigenen Verzeichnis ausgeführt zu werden, da er relative Pfade für Plugins und Profildaten lädt. Der Wrapper verbirgt dieses Detail, so dass lynis audit system von überall aus funktioniert." + }, + "detection": { + "heading": "Detektionspfade", + "intro": "Bevor Sie das Menü anzeigen, überprüft das Skript drei Standorte, um zu entscheiden, ob Lynis bereits vorhanden ist:", + "items": [ + "/usr/local/bin/lynis — Wrapper installiert von ProxMenux", + "/opt/lynis/lynis — direkter Pfad (falls der Wrapper entfernt wurde)", + "/usr/bin/lynis — apt-installierte Version, wenn der Benutzer es zuvor so installiert hat" + ], + "outro": "Wird einer dieser Pfade gefunden, öffnet sich das Verwaltungsmenü. Das Skript deinstalliert kein über apt installiertes Lynis, sondern nur die selbst verwaltete Installation in /opt/lynis samt Starter." + }, + "audit": { + "heading": "Durchführung eines Audits", + "intro": "Wählen Sie im Verwaltungsmenü Sicherheitsprüfung jetzt ausführen. Das entspricht folgendem Aufruf :", + "code": "lynis audit system --no-colors", + "outro": "Die Prüfung dauert je nach Hostgröße 30 Sekunden bis einige Minuten. Die Ausgabe erscheint direkt im Terminal, ohne Warteanimation. Lynis gliedert die Prüfungen nach Kategorien und kennzeichnet Tests mit [ OK ], [ WARNING ] oder [ SUGGESTION ]. Die abschließende Zusammenfassung enthält die wichtigsten Zahlen :", + "summary": "================================================================================\n\n Details der Lynis-Sicherheitsprüfung:\n\n Härtungsindex : 76 [############ ]\n Durchgeführte Tests : 247\n Aktivierte Plugins : 0\n\n Komponenten:\n - Firewall [V]\n - Malware-Scanner [X]\n\n Lynis-Module:\n - Konformitätsstatus [?]\n - Sicherheitsprüfung [V]\n - Schwachstellenprüfung [V]\n\n================================================================================" + }, + "report": { + "heading": "Lesen Sie den Bericht", + "intro": "Die beiden wichtigen Abschnitte sind Warnings (Dinge, die Sie bald beheben sollten) und Suggestions (Empfehlungen zur Verbesserung der Härtung). Jedes Element trägt eine Kontroll-ID wie SSH-7408 - nützlich bei der Suche nach den Lynis-Dokumenten für die Gründe und die Korrektur.", + "headerMarker": "Markierung", + "headerMeaning": "Bedeutung", + "headerAction": "Aktion", + "rows": [ + { + "markerRich": "OK", + "meaning": "Test bestanden", + "action": "Nichts" + }, + { + "markerRich": "WARNING", + "meaning": "Echtes Problem, Fix empfohlen", + "action": "Lesen Sie die Prüfungsbeschreibung und planen Sie eine Korrektur." + }, + { + "markerRich": "SUGGESTION", + "meaning": "Härtungsverbesserung verfügbar", + "action": "Umsetzen, wenn es zu Ihrem Bedrohungsmodell passt" + } + ], + "outro": "Der vollständige Bericht und die maschinenlesbaren Daten werden von Lynis selbst in /var/log/lynis.log und /var/log/lynis-report.dat geschrieben." + }, + "pairFail2ban": { + "title": "Mit Fail2Ban kombinieren", + "body": "SSH-7408 (MaxAuthTries) ist eine der häufigsten Warnungen. Die Installation von Fail2Ban aus ProxMenux löscht es automatisch, da der Installer MaxAuthTries=3 als Teil seines SSH-Härtungsschritts einstellt. Führen Sie Lynis danach erneut aus, um zu bestätigen, dass die Warnung verschwunden ist." + }, + "update": { + "heading": "Lynis aktualisieren", + "body": "Im Verwaltungsmenü führt Lynis auf die neueste Version aktualisieren den Befehl git pull --quiet in /opt/lynis aus. Ist das Verzeichnis keine Git-Arbeitskopie, etwa nach manuellem Kopieren der Dateien, wird vollständig neu installiert." + }, + "reinstall": { + "heading": "Reinstallieren / Deinstallieren", + "headerAction": "Aktion", + "headerWhat": "Was es tut", + "rows": [ + { + "actionRich": "Neu installieren", + "whatRich": "Entfernt /opt/lynis, klont erneut von GitHub und erstellt den Starter neu. Für eine beschädigte lokale Arbeitskopie." + }, + { + "actionRich": "Entfernen", + "whatRich": "Löscht /opt/lynis und /usr/local/bin/lynis. Protokolle bei /var/log/lynis* werden aufbewahrt (sie sind Audit-Historie). Ein apt-installiertes Lynis bei /usr/bin/lynis bleibt unberührt." + } + ] + }, + "cli": { + "heading": "Nützliche CLI Optionen", + "intro": "Einmal installiert, kann Lynis direkt von einer Shell aus aufgerufen werden, wobei zusätzliche Optionen im Menü nicht angezeigt werden:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Fehlerbehebung", + "cloneTitle": "git clone schlägt bei der Installation fehl", + "cloneBody": "Der Host benötigt Netzwerkzugang und DNS, um github.com zu erreichen. Von einer Konsole: curl -sI https://github.com und git ls-remote https://github.com/CISOfy/lynis.git wird der tatsächliche Fehler (DNS, TLS, Proxy, Repo-URL) enthüllt. Wenn ein Proxy erforderlich ist, legen Sie https_proxy in der Umgebung fest, bevor Sie das Menü erneut ausführen.", + "notFoundTitle": "lynis: Befehl nach der Installation nicht gefunden", + "notFoundIntro": "Der Wrapper bei /usr/local/bin/lynis wurde entweder nicht erstellt oder entfernt. Schnellste Lösung: Neuinstallation aus dem Menü. Manuelle Fixierung:", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "Die Prüfung zeigt \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "Der SSH-Daemon erlaubt zu viele Auth-Versuche pro Verbindung. Installieren Sie entweder Fail2Ban von ProxMenux (was MaxAuthTries=3 automatisch einstellt) oder bearbeiten Sie /etc/ssh/sshd_config von Hand:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "Score ging nach einem Proxmox-Upgrade zurück", + "scoreBody": "Führen Sie lynis update info zuerst aus - ein Lynis-Update hat möglicherweise neue Steuerelemente hinzugefügt, die bestehende Konfiguration kennzeichnen. Aktualisieren Sie Lynis aus dem Menü, überprüfen Sie es erneut und sprechen Sie die neuen Erkenntnisse an." + }, + "files": { + "heading": "Dateien geschrieben", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Beispielbericht", + "intro": "ProxMenux Monitor erstellt zu jedem Lynis-Durchlauf eine mehrseitige PDF-Datei im Reiter Sicherheit. Die erste Seite fasst Härtungsbewertung, Systeminformationen und Sicherheitslage zusammen. Die weiteren Seiten erläutern jede Warnung, ordnen Vorschläge nach Auswirkung und enthalten das bei der Prüfung verwendete Paketinventar.", + "imageAlt": "Erste Seite des Lynis Security Audit Report PDF – Zusammenfassung mit Hardening Index 71/100, Systeminformationsblock und Sicherheitsübersicht mit Firewall, Malware-Scanner und installierten Paketen", + "captionPrefix": "Erste Seite eines Beispielberichts. Die vollständige PDF-Datei (", + "captionLink": "Probe", + "captionSuffix": ") fährt mit detaillierten Warnungen, Vorschlägen und der Liste der installierten Pakete fort.", + "cli": "Auf der CLI-Seite befinden sich die gleichen Daten in /var/log/lynis-report.dat (maschinenlesbare Flat-Datei) und /var/log/lynis.log (das vom Menschen lesbare Run-Log). Die PDF-Datei wird auf Anfrage von ProxMenux Monitor generiert – die Ausführung von lynis aus der Befehlszeile erzeugt keine." + }, + "related": { + "heading": "Verwandte Themen", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor - Sicherheits-Tab", + "tail": " — Prüfung ausführen, frühere Berichte ansehen und die PDF-Datei direkt aus dem Dashboard herunterladen." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": "- implementiert den von Lynis empfohlenen Brute-Force-Schutz SSH." + }, + { + "href": "/docs/security", + "label": "Sicherheitsübersicht", + "tail": " — zurück zur Abschnittsübersicht." + } + ] + } +} diff --git a/web/messages/de/docs/security/ssl-letsencrypt.json b/web/messages/de/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..fecde538 --- /dev/null +++ b/web/messages/de/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS für ProxMenux Monitor — Proxmox-Hostzertifikat oder eigene Pfade | ProxMenux", + "description": "Stellen Sie ProxMenux Monitor per HTTPS bereit, mit dem automatisch erkannten Proxmox-Hostzertifikat, einschließlich eines über Proxmox ACME installierten Let’s-Encrypt-Zertifikats, oder einem eigenen Zertifikat an beliebiger Stelle auf dem Datenträger.", + "ogTitle": "HTTPS für ProxMenux Monitor — Proxmox-Host oder benutzerdefiniertes Zertifikat", + "ogDescription": "Aktivieren Sie HTTPS am Port 8008 mit dem Proxmox-Hostzertifikat oder einem benutzerdefinierten .pem / .key-Paar." + }, + "header": { + "title": "HTTPS für ProxMenux Monitor", + "description": "Aktivieren Sie HTTPS für Monitor auf Port 8008 mit dem automatisch erkannten Proxmox-Hostzertifikat oder einem eigenen Zertifikat auf dem Datenträger. Die Proxmox-Option bevorzugt das ACME-Zertifikat, falls vorhanden, und nutzt das von Proxmox verwaltete Let’s-Encrypt-Zertifikat ohne eigene Erneuerungsaufgabe.", + "section": "Sicherheit" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Zwei Möglichkeiten, HTTPS zu aktivieren", + "proxmox": { + "title": "Das Proxmox-Zertifikat verwenden", + "summary": "Ein Klick: Monitor nutzt das Zertifikat, das Proxmox VE auf Port 8006 bereitstellt.", + "items": [ + "Automatisch erkannt, keine Pfadeingabe erforderlich", + "Bevorzugt das ACME-Zertifikat, falls vorhanden; andernfalls das selbstsignierte Standardzertifikat", + "Proxmox erneuert; Monitor prüft und lädt das Zertifikat bei der nächsten neuen TLS-Verbindung" + ] + }, + "custom": { + "title": "Benutzerdefiniertes Zertifikat verwenden", + "summaryRich": "Richten Sie den Monitor auf ein .pem / .key-Paar überall auf der Festplatte.", + "items": [ + "Nur absolute Pfade (der Monitor-Prozess muss beide lesen können)", + "Nützlich, wenn Zertifikate von certbot, acme.sh oder einer Wildcard stammen", + "Sie verwalten die Erneuerung: Starten Sie Monitor nach Dateiänderungen neu" + ] + } + }, + "proxmoxCert": { + "heading": "Proxmox-Zertifikat - was es tatsächlich zeigt", + "intro": "Proxmox VE führt zwei Zertifikatspaare in /etc/pve/local/:", + "table": { + "headers": { + "file": "Datei", + "origin": "Ursprung", + "when": "Wenn der Monitor es verwendet" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Benutzerdefiniertes Zertifikat, das über die Proxmox-Benutzeroberfläche hochgeladen wird - einschließlich des Let's Encrypt-Zertifikats, das unter Datacenter → Node → Certificates → ACME ausgestellt wurde.", + "when": "Bevorzugt, wenn beide Dateien existieren; Monitor nutzt das von Proxmox bereitgestellte Zertifikat." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Selbstsigniertes Zertifikat, das zum Zeitpunkt der Installation vom Proxmox PKI generiert wurde.", + "whenRich": "Fallback, wenn kein pveproxy-ssl.pem vorhanden ist." + } + ] + }, + "callout": { + "title": "Gleiche Priorität wie Proxmox selbst", + "bodyRich": "pveproxy nutzt dieselbe Auswahlregel. Mit der Proxmox-Option verwendet Monitor das Zertifikat der Proxmox-Weboberfläche auf Port 8006, ohne Kopieren oder getrennten Erneuerungsablauf." + } + }, + "letsencrypt": { + "heading": "Erhalten eines Let's Encrypt Zertifikats über Proxmox ACME", + "intro": "Proxmox VE enthält einen eigenen ACME-Client. „Order Certificate Now“ speichert das Ergebnis in /etc/pve/local/pveproxy-ssl.pem, das Monitor liest. Die folgenden fünf Schritte fassen die offizielle Proxmox-Anleitung in den benötigten Befehlen zusammen.", + "prereqs": { + "title": "Voraussetzungen", + "items": [ + "Der Knoten hat einen echten DNS-Namen (nicht nur eine IP), der darauf zeigt.", + "Entweder Port 80 aus dem Internet erreichbar (HTTP-01-Prüfung, am einfachsten) oder ein DNS-Anbieter mit API-Zugangsdaten (DNS-01, funktioniert hinter NAT und unterstützt Wildcards).", + "Eine E-Mail-Adresse für das ACME-Konto." + ] + }, + "step1": { + "heading": "Schritt 1 — Registrieren Sie das ACME-Konto", + "introRich": "Ersetzen Sie die E-Mail durch Ihre. Der Kontoname default ist nur ein Label - Sie können eine beliebige Zeichenfolge verwenden.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "Proxmox fordert Sie auf, die Nutzungsbedingungen von Let's Encrypt zu akzeptieren. Danach wird das Konto unter /etc/pve/priv/acme/ fortgesetzt und die Registerkarte ACME in der GUI zeigt es an." + }, + "step2": { + "heading": "Schritt 2 - Wählen Sie einen Herausforderungstyp", + "http01Rich": "HTTP-01 ist das einfachste: es ist eingebaut, kein zusätzliches Plugin. Let's Encrypt verbindet sich mit http://<your-host>/.well-known/acme-challenge/... an Port 80, so dass Port 80 vom öffentlichen Internet zu Ihrem Knoten geöffnet sein muss - normalerweise ein Port vorwärts auf Ihrem Router.", + "dns01Rich": "DNS-01 funktioniert ohne Port 80 (gut für Homelabs hinter CG-NAT) und ist die einzige Option für Wildcard-Zertifikate. Sie fügen ein DNS-Plugin einmal mit dem API-Token Ihres Providers hinzu. Cloudflare Beispiel:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "Für andere Anbieter (Gandi, OVH, AWS Route 53 usw.) nutzen Sie pvenode acme plugin add dns --help. Proxmox enthält alle DNS-Plugins von acme.sh." + }, + "step3": { + "heading": "Schritt 3 - Binden Sie die Domain an Ihren Knoten", + "http01Rich": "Für HTTP-01 (verwendet das eingebaute standalone-Plugin):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "Für DNS-01 mit dem Cloudflare-Plugin aus Schritt 2:", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Wildcard? Fügen Sie domain=*.example.com auf einem separaten --acmedomainN-Eintrag hinzu. Wildcards erfordern DNS-01." + }, + "step4": { + "heading": "Schritt 4 — Bestellen Sie das Zertifikat", + "code": "pvenode acme cert order", + "afterRich": "Der erste Lauf dauert normalerweise 30–60 Sekunden, während sich die Herausforderung ausbreitet. Beim Erfolg schreibt Proxmox /etc/pve/local/pveproxy-ssl.pem + .key, startet pveproxy neu und ein Erneuerungs-Cron wird automatisch geplant (Let's Encrypt Zertifikate sind gültig 90 Tage; Proxmox erneuert bei 60)." + }, + "step5": { + "heading": "Schritt 5 — Überprüfung des Emittenten", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "Sollte einen Emittenten ab O = Let's Encrypt drucken. Wenn stattdessen CN = Proxmox Virtual Environment gelesen wird, ist die Bestellung fehlgeschlagen und die Datei ist immer noch das selbstsignierte PVE-Zertifikat - führen Sie pvenode acme cert order --debug 1 erneut aus, um zu sehen, warum." + }, + "gui": { + "title": "Bevorzugen Sie die GUI? Gleiche fünf Schritte", + "bodyRich": "Datacenter → ACME registriert das Konto; Datacenter → ACME → Plugins fügt das DNS-Plugin hinzu; Node → Certificates → ACME verknüpft die Domain; Order Certificates Now führt Schritt 4 aus. Die obigen CLI-Befehle eignen sich für Skripte und Systeme ohne grafische Oberfläche." + } + }, + "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." + }, + "custom": { + "heading": "Benutzerdefiniertes Zertifikat — wann es sinnvoll ist", + "intro": "Klicken Sie auf Benutzerdefiniertes Zertifikat verwenden und geben Sie zwei absolute Pfade ein :", + "items": [ + "Zertifikat: vollständige Kette im PEM-Format (.pem oder .crt).", + "Privater Schlüssel: passender Schlüssel im PEM-Format (.key)." + ], + "outro": "Beide Dateien werden beim Start vom Monitor-Prozess gelesen. Sie müssen für den Benutzer lesbar bleiben, unter dem die systemd-Unit läuft (standardmäßig root). Übliche Orte bei ACME-Verwaltung außerhalb von Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Symlink-Ziele vs. Dateien", + "bodyRich": "certbot speichert die eigentlichen Dateien in /etc/letsencrypt/archive/ und der live/-Pfad ist ein Symlink. Richten Sie den Monitor auf den live/-Symlink, nicht auf die archive/-Datei - so schreibt jeder Erneuerungszyklus einfach das Symlink-Ziel um und der Monitor nimmt das neue Zertifikat beim nächsten Servicestart entgegen." + } + }, + "afterHttps": { + "heading": "Nach Aktivierung von HTTPS", + "bodyRich": "Das Speichern der Änderung schreibt /etc/proxmenux/ssl_config.json und löst einen Neustart der proxmenux-monitor systemd Einheit aus. Die Browser-Registerkarte verliert ihre aktuelle Verbindung - laden Sie sie auf https://<host>:8008/ neu. Die erste Ladung mit einem Let's Encrypt-Zertifikat sollte keine Warnung zeigen; mit dem selbstsignierten Proxmox-Zertifikat benötigt der Browser eine Ausnahme, die gleiche wie für Port 8006.", + "reverse": { + "heading": "Reverse Proxys und Webhooks", + "bodyRich": "Wenn der Monitor zu HTTPS wechselt, schalten auch die für Benachrichtigungen registrierten Proxmox VE-webhook-URLs automatisch von http:// zu https:// um. Bestehende webhook-Einträge werden beim nächsten Speichern oder erneuten Erkennen einer Benachrichtigungsregel neu geschrieben, so dass keine manuelle Bearbeitung der Proxmox-Benachrichtigungskonfiguration erforderlich ist." + } + }, + "trustCa": { + "heading": "Der selbstsignierten Proxmox-CA vertrauen", + "intro1Rich": "Um das Proxmox-Standardzertifikat ohne ACME zu behalten, importieren Sie die Cluster-Stamm-CA in den Zertifikatsspeicher von Betriebssystem oder Browser. Monitor erkennt /etc/pve/local/pve-ssl.pem als Ersatzzertifikat. Der Client vertraut dadurch dem Aussteller; die URL muss weiterhin zu einem Namen im Zertifikat passen.", + "intro2Rich": "Die CA-Datei lebt bei /etc/pve/pve-root-ca.pem auf jedem Knoten. In einem Cluster befindet sich der Pfad auf dem gemeinsamen pmxcfs-Dateisystem, so dass alle Knoten die gleiche Wurzel teilen und ein einziger Import das gesamte Rechenzentrum abdeckt. Kopieren Sie es auf Ihre Maschine:", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Importieren Sie die Datei anschließend auf dem Client:", + "items": [ + "Linux (systemweit): Führen Sie sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt und anschließend sudo update-ca-certificates aus. Browser mit System-Zertifikatsspeicher (Chromium, Edge) erkennen die CA nach dem Neustart; Firefox nutzt seinen eigenen Speicher und benötigt den folgenden manuellen Import.", + "macOS: Doppelklicken Sie auf die .pem-Datei. Fügen Sie sie in der Schlüsselbundverwaltung zum Schlüsselbund System hinzu und wählen Sie Vertrauen → Immer vertrauen.", + "Windows: Rechtsklick auf die Datei → Zertifikat installieren → unter Vertrauenswürdige Stammzertifizierungsstellen ablegen.", + "Firefox (jedes Betriebssystem): Einstellungen → Datenschutz und Sicherheit → Zertifikate → Zertifikate anzeigen → Zertifizierungsstellen → Importieren; aktivieren Sie Dieser CA vertrauen, um Websites zu identifizieren." + ], + "standalone": { + "title": "Standalone Knoten außerhalb eines Clusters", + "bodyRich": "Jeder nicht geclusterte Proxmox-Host hat seinen eigenen pve-root-ca.pem. Wenn Sie mehrere eigenständige Hosts verwalten, wiederholen Sie die Kopie / den Import für jeden oder verbinden Sie sie zu einem Cluster, so dass sie sich eine einzelne Wurzel teilen." + } + }, + "disable": { + "heading": "Deaktivierung von HTTPS", + "bodyRich": "Wählen Sie im selben Bereich HTTPS deaktivieren. Monitor startet mit unverschlüsseltem HTTP neu, etwa wenn eine eigene Zertifikatsdatei unlesbar ist und der Dienst nicht startet. Es gibt keinen weiteren Deinstallationsschritt; Zertifikatsdateien bleiben unverändert.", + "stateCallout": { + "title": "Wo der Zustand gespeichert wird", + "bodyRich": "/etc/proxmenux/ssl_config.json speichert die aktiven Zertifikatpfade und die Quelle (none, proxmox, custom). Das Löschen der Datei entspricht dem Deaktivieren von HTTPS - beim nächsten Start fällt der Monitor auf HTTP zurück." + } + } +} diff --git a/web/messages/en/docs/security/fail2ban.json b/web/messages/en/docs/security/fail2ban.json index f7d431d8..18b0ad34 100644 --- a/web/messages/en/docs/security/fail2ban.json +++ b/web/messages/en/docs/security/fail2ban.json @@ -91,7 +91,7 @@ }, "hardening": { "heading": "SSH hardening: MaxAuthTries", - "intro": "Lynis control SSH-7408 recommends MaxAuthTries 3 in sshd_config. With Fail2Ban's maxretry = 2 on the SSH jail, a malicious client never reaches three attempts anyway — but the explicit setting satisfies the audit and adds defence in depth (e.g. if Fail2Ban is stopped for maintenance).", + "intro": "Lynis control SSH-7408 recommends MaxAuthTries 3 in sshd_config. The SSH jail uses maxretry = 2 to trigger a ban after repeated failures, while MaxAuthTries limits attempts within a connection. The explicit SSH setting adds defence in depth, including when Fail2Ban is stopped for maintenance.", "installerIntro": "The installer:", "items": [ "Reads the current MaxAuthTries value (or defaults to 6 if absent).", @@ -125,7 +125,7 @@ "troubleshoot": { "heading": "Troubleshooting", "neverBansTitle": "Fail2Ban runs but never bans anything", - "neverBansBody": "Check the auth log files actually receive entries: tail -f /var/log/ssh-auth.log and try a wrong-password SSH attempt from a different machine. If the log stays empty, the logger service is not running: systemctl status ssh-auth-logger.service. If it's active but the log is empty, check that the journald drop-in took effect: journalctl --dump-catalog | head — events at info level should be visible.", + "neverBansBody": "Check that tail -f /var/log/ssh-auth.log receives an entry after a wrong-password SSH attempt from another machine. If it stays empty, inspect systemctl status ssh-auth-logger.service. If the logger is active, inspect recent SSH events with journalctl -u ssh.service -n 50 --no-pager -p info and check whether the journald drop-in permits the info level.", "monitorEmptyTitle": "ProxMenux Monitor jail has no entries even after failed logins", "monitorEmptyBody": "The [proxmenux] jail reads /var/log/proxmenux-auth.log, which is written by the ProxMenux Monitor Flask app — not by a journald logger. If you don't run the Monitor, the file stays empty and the jail never fires. That's expected; the jail config is harmless. If you do run the Monitor and the log is empty, check the Flask logging config.", "selfBanTitle": "I banned myself", diff --git a/web/messages/en/docs/security/ssl-letsencrypt.json b/web/messages/en/docs/security/ssl-letsencrypt.json index 19f4ea50..b5a65563 100644 --- a/web/messages/en/docs/security/ssl-letsencrypt.json +++ b/web/messages/en/docs/security/ssl-letsencrypt.json @@ -28,7 +28,7 @@ "items": [ "Auto-detected — no path to type", "Picks the ACME-uploaded cert if present, falls back to the self-signed default", - "Renewal happens through Proxmox; the Monitor follows automatically on next start" + "Proxmox handles renewal; the Monitor validates and loads it on the next new TLS connection" ] }, "custom": { @@ -143,7 +143,7 @@ }, "trustCa": { "heading": "Trust the Proxmox self-signed CA", - "intro1Rich": "If you prefer to keep the default Proxmox-generated certificate (no ACME), you can stop the browser warning by importing the cluster's root CA into the OS or browser trust store. The Monitor will keep auto-detecting the same /etc/pve/local/pveproxy-ssl.pem; the only thing that changes is that the device viewing the dashboard now recognises the issuer.", + "intro1Rich": "To keep the default Proxmox-generated certificate without ACME, import the cluster root CA into the OS or browser trust store. The Monitor detects /etc/pve/local/pve-ssl.pem as the fallback certificate. This makes the client trust its issuer; the URL must still match a name in the certificate.", "intro2Rich": "The CA file lives at /etc/pve/pve-root-ca.pem on every node. In a cluster the path is on the shared pmxcfs filesystem so all nodes share the same root, and a single import covers the whole datacenter. Copy it to your machine:", "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", "thenImport": "Then import it on the client side:", diff --git a/web/messages/es/docs/security/fail2ban.json b/web/messages/es/docs/security/fail2ban.json index d1fdd044..d72c9441 100644 --- a/web/messages/es/docs/security/fail2ban.json +++ b/web/messages/es/docs/security/fail2ban.json @@ -1,30 +1,30 @@ { "meta": { - "title": "Fail2Ban | ProxMenux Documentation", + "title": "Fail2Ban | Documentación de ProxMenux", "description": "Instala Fail2Ban con tres jails afinados para Proxmox (SSH agresivo, Proxmox UI 8006, ProxMenux Monitor 8008). Incluye el arreglo de journald MaxLevelStore, servicios de log propios para fiabilidad, backend nftables/iptables autodetectado y hardening de SSH MaxAuthTries.", - "ogTitle": "Fail2Ban | ProxMenux Documentation", - "ogDescription": "Protección contra fuerza bruta para SSH y las UIs web de Proxmox, con arreglos específicos de journald y backend para Proxmox." + "ogTitle": "Fail2Ban | Documentación de ProxMenux", + "ogDescription": "Protección contra fuerza bruta para SSH y las interfaces web de Proxmox, con correcciones de journald y del backend específicas para Proxmox." }, "header": { "title": "Fail2Ban", - "description": "Instala Fail2Ban con una configuración específica para Proxmox: tres jails (SSH agresivo, Proxmox UI en puerto 8006, ProxMenux Monitor en puerto 8008 + proxy inverso), un arreglo del nivel de log de journald para que los eventos de autenticación SSH se almacenen de verdad, dos servicios logger journal-a-archivo que rodean un problema conocido del backend systemd de Fail2Ban, backend de firewall autodetectado y hardening de SSH MaxAuthTries.", + "description": "Instala Fail2Ban con configuración para Proxmox: tres jails (SSH agresivo, Proxmox en 8006 y ProxMenux Monitor en 8008 y proxy inverso), corrección del nivel de journald para guardar fallos SSH, dos servicios que copian el journal a archivos para evitar un problema del backend systemd de Fail2Ban, detección del backend de firewall y refuerzo SSH mediante MaxAuthTries.", "section": "Seguridad" }, "intro": { "title": "Qué hace", - "body": "Instala Fail2Ban desde los repos de Debian y escribe una configuración completa afinada para Proxmox que protege tres superficies de ataque (SSH, Proxmox UI, ProxMenux Monitor) por defecto. Detecta una instalación existente y ofrece un menú de gestión (reinstalar / eliminar) en lugar de volver a ejecutar el instalador." + "body": "Instala Fail2Ban desde los repositorios Debian y escribe una configuración para Proxmox que protege SSH, Proxmox UI y ProxMenux Monitor desde el primer momento. Si detecta una instalación existente, ofrece un menú para reinstalar o eliminar." }, "firstLaunch": { "heading": "Diálogo de primer lanzamiento", - "body": "En un host sin Fail2Ban, el script muestra un diálogo de confirmación que resume todo lo que va a instalar y configurar. Cancelar sale sin cambios; confirmar arranca el flujo de instalación.", - "imageAlt": "Diálogo de confirmación de instalación de Fail2Ban listando los tres jails, el arreglo de journald y el hardening de SSH" + "body": "Sin Fail2Ban instalado, el script muestra un diálogo con lo que instalará y configurará. Cancelar termina sin cambios; confirmar inicia la instalación.", + "imageAlt": "Confirmación de instalación de Fail2Ban con las tres jails, corrección de journald y refuerzo SSH" }, "jails": { "heading": "Los tres jails", "headerJail": "Jail", "headerProtects": "Protege", "headerRetries": "Reintentos / Ventana", - "headerBan": "Tiempo de ban", + "headerBan": "Tiempo de bloqueo", "rows": [ { "jail": "[sshd]", @@ -49,12 +49,12 @@ }, "journald": { "heading": "Por qué importa el arreglo de journald", - "intro": "Proxmox entrega /etc/systemd/journald.conf con MaxLevelStore=warning. journald descarta cada mensaje de log por debajo de warning antes de almacenarlo. SSH y PAM emiten fallos de autenticación a niveles info / notice, así que:", + "intro": "Proxmox incluye /etc/systemd/journald.conf con MaxLevelStore=warning. journald descarta los mensajes de nivel inferior a warning antes de guardarlos. SSH y PAM emiten fallos de autenticación en los niveles info / notice, por lo que:", "diagram": { "sshLabel": "SSH / PAM", "sshDetail": "fallo de autenticación\n(nivel info / notice)", "journaldLabel": "journald", - "journaldDetail": "MaxLevelStore=warning\n→ evento descartado en silencio", + "journaldDetail": "MaxLevelStore=warning\n→ evento descartado sin aviso", "fail2banLabel": "Fail2Ban", "fail2banDetail": "no ve nada\n→ nunca banea nada", "arrowLabel": "Proxmox por defecto" @@ -65,8 +65,8 @@ }, "loggers": { "heading": "Por qué dos servicios logger propios", - "intro1": "Fail2Ban puede leer directamente del journal de systemd (backend = systemd), pero en Proxmox este backend tiene problemas conocidos de fiabilidad con los procesos worker de pvedaemon (los eventos de autenticación aparecen en el journal pero Fail2Ban no siempre los recoge) e intermitentemente con sshd.", - "intro2": "La solución alternativa: ProxMenux crea dos pequeños servicios systemd que hacen journalctl -f de las unidades relevantes y añaden cada línea a un archivo. Fail2Ban entonces lee esos archivos con el sólido como una roca backend = auto (modo archivo):", + "intro1": "Fail2Ban puede leer directamente el journal de systemd (backend = systemd), pero en Proxmox este backend presenta problemas de fiabilidad con procesos worker de pvedaemon (los eventos aparecen en el journal pero Fail2Ban no siempre los recoge) y ocasionalmente con sshd.", + "intro2": "ProxMenux crea dos pequeños servicios systemd que siguen las unidades correspondientes con journalctl -f y añaden cada línea a un archivo. Fail2Ban lee esos archivos con el fiable backend = auto (modo archivo):", "headerService": "Servicio", "headerSource": "Unidad origen", "headerOutput": "Archivo de salida", @@ -91,12 +91,12 @@ }, "hardening": { "heading": "Hardening de SSH: MaxAuthTries", - "intro": "El control de Lynis SSH-7408 recomienda MaxAuthTries 3 en sshd_config. Con el maxretry = 2 de Fail2Ban en el jail SSH, un cliente malicioso nunca llega a tres intentos de todas formas — pero el ajuste explícito satisface la auditoría y añade defensa en profundidad (p. ej. si Fail2Ban está parado por mantenimiento).", + "intro": "El control Lynis SSH-7408 recomienda MaxAuthTries 3 en sshd_config. La jail SSH usa maxretry = 2 para bloquear tras fallos repetidos; MaxAuthTries limita los intentos dentro de una conexión. Este ajuste SSH añade defensa en profundidad, también cuando Fail2Ban está detenido por mantenimiento.", "installerIntro": "El instalador:", "items": [ "Lee el valor actual de MaxAuthTries (o por defecto 6 si está ausente).", "Lo guarda en /usr/local/share/proxmenux/sshd_maxauthtries_backup.", - "Edita sshd_config en sitio — reemplaza la línea existente, descomenta la línea comentada o la añade.", + "Edita sshd_config directamente: sustituye la línea existente, descomenta la comentada o añade una nueva.", "Recarga sshd (reload, no restart, para mantener vivas las sesiones existentes)." ], "outro": "Al desinstalar, el valor original guardado se restaura y se recarga sshd otra vez." @@ -109,7 +109,7 @@ "rows": [ { "action": "Reinstalar", - "what": "Vuelve a ejecutar el instalador completo — reescribe todos los jails con los valores por defecto actuales de ProxMenux. Úsalo cuando una actualización de ProxMenux suba los valores recomendados." + "what": "Repite la instalación completa y reescribe todas las jails con los valores actuales de ProxMenux. Úsalo cuando una actualización cambie las recomendaciones." }, { "action": "Eliminar", @@ -125,7 +125,7 @@ "troubleshoot": { "heading": "Solución de problemas", "neverBansTitle": "Fail2Ban corre pero nunca banea nada", - "neverBansBody": "Comprueba que los archivos de log de autenticación reciben entradas de verdad: tail -f /var/log/ssh-auth.log y prueba un intento SSH con contraseña incorrecta desde otra máquina. Si el log se queda vacío, el servicio logger no está corriendo: systemctl status ssh-auth-logger.service. Si está activo pero el log está vacío, comprueba que el drop-in de journald surtió efecto: journalctl --dump-catalog | head — eventos a nivel info deberían ser visibles.", + "neverBansBody": "Comprueba que tail -f /var/log/ssh-auth.log recibe una entrada tras intentar acceder por SSH con contraseña incorrecta desde otra máquina. Si sigue vacío, consulta systemctl status ssh-auth-logger.service. Si el logger está activo, mira los eventos SSH recientes con journalctl -u ssh.service -n 50 --no-pager -p info y comprueba que el drop-in de journald admite el nivel info.", "monitorEmptyTitle": "El jail del Monitor de ProxMenux no tiene entradas ni siquiera tras logins fallidos", "monitorEmptyBody": "El jail [proxmenux] lee /var/log/proxmenux-auth.log, que lo escribe la app Flask del Monitor de ProxMenux — no un logger de journald. Si no usas el Monitor, el archivo se queda vacío y el jail nunca dispara. Eso es lo esperado; la configuración del jail es inocua. Si sí usas el Monitor y el log está vacío, comprueba la configuración de logging de Flask.", "selfBanTitle": "Me he baneado a mí mismo", @@ -143,7 +143,7 @@ "related": { "heading": "Relacionado", "monitorLabel": "ProxMenux Monitor → pestaña de Seguridad", - "monitorTail": " — misma instalación accesible desde el panel, más estado en vivo de los jails, IPs baneadas y ajuste por jail de reintentos / tiempo de ban desde el navegador.", + "monitorTail": " — instalación desde el panel, estado de las jails en directo, IP bloqueadas y ajuste de reintentos y duración de bloqueo por jail desde el navegador.", "lynisLabel": "Lynis", "lynisTail": " — ejecuta una auditoría de seguridad antes/después para confirmar que el control SSH-7408 se satisface.", "securityLabel": "Resumen de Seguridad", diff --git a/web/messages/es/docs/security/index.json b/web/messages/es/docs/security/index.json index 7e89921b..09522610 100644 --- a/web/messages/es/docs/security/index.json +++ b/web/messages/es/docs/security/index.json @@ -1,24 +1,24 @@ { "meta": { - "title": "Seguridad de Proxmox — Fail2Ban, auditoría de hardening con Lynis | ProxMenux", - "description": "Dos herramientas de seguridad instalables para Proxmox VE: Fail2Ban (prevención de intrusiones para SSH, la UI web de Proxmox y el Monitor de ProxMenux) y Lynis (escáner de auditoría y hardening de seguridad instalado desde GitHub upstream).", - "ogTitle": "Seguridad de Proxmox — Fail2Ban, auditoría de hardening con Lynis", - "ogDescription": "Fail2Ban y Lynis para Proxmox VE — prevención de intrusiones más una auditoría de hardening instaladas y gestionadas por ProxMenux.", + "title": "Seguridad de Proxmox — Fail2Ban y auditoría de refuerzo con Lynis | ProxMenux", + "description": "Dos herramientas de seguridad para Proxmox VE: Fail2Ban (prevención de intrusiones para SSH, la interfaz web de Proxmox y ProxMenux Monitor) y Lynis (auditor de seguridad y refuerzo del sistema instalado desde el repositorio original en GitHub).", + "ogTitle": "Seguridad de Proxmox — Fail2Ban y auditoría de refuerzo con Lynis", + "ogDescription": "Fail2Ban y Lynis para Proxmox VE: prevención de intrusiones y auditoría de refuerzo, instaladas y gestionadas por ProxMenux.", "twitterTitle": "Seguridad de Proxmox | ProxMenux", - "twitterDescription": "Fail2Ban y Lynis para Proxmox VE — prevención de intrusiones y auditoría de hardening." + "twitterDescription": "Fail2Ban y Lynis para Proxmox VE: prevención de intrusiones y auditoría de refuerzo." }, "header": { "title": "Seguridad", - "description": "Dos herramientas de seguridad complementarias para Proxmox VE: una defensa activa (Fail2Ban — banea IPs que atacan SSH o las UIs web) y una auditoría offline (Lynis — escanea el sistema en busca de huecos de hardening y da una puntuación 0–100). Ambas se instalan y gestionan mediante su propio menú dedicado, con detección de una instalación existente antes de cualquier acción.", + "description": "Dos herramientas complementarias para Proxmox VE: defensa activa (Fail2Ban bloquea IP que atacan SSH o las interfaces web) y auditoría local (Lynis busca carencias de seguridad y da una puntuación de 0 a 100). Cada una tiene su propio menú de instalación y gestión, que detecta instalaciones existentes antes de actuar.", "section": "Seguridad" }, "intro": { - "title": "Defensa activa vs. auditoría offline", - "body": "Estas dos herramientas responden a preguntas distintas. Fail2Ban responde a \"¿hay algo atacándonos ahora mismo?\" vigilando los logs de autenticación y baneando las IPs ofensoras. Lynis responde a \"¿cómo de hardened está este host?\" ejecutando una auditoría de un tirón e imprimiendo recomendaciones concretas. Úsalas juntas: Lynis te dice qué arreglar, Fail2Ban gestiona el abuso en vivo." + "title": "Defensa activa y auditoría local", + "body": "Responden a preguntas distintas. Fail2Ban contesta «¿nos están atacando ahora?» vigilando registros de autenticación y bloqueando las IP responsables. Lynis contesta «¿qué nivel de refuerzo tiene este host?» mediante una auditoría puntual con recomendaciones concretas. Usa ambas: Lynis indica qué mejorar y Fail2Ban responde a los ataques en curso." }, "opening": { "heading": "Abrir el menú", - "body": "Desde el menú principal de ProxMenux, selecciona Seguridad. Verás esto:", + "body": "Desde el menú principal de ProxMenux, selecciona Seguridad. Verás lo siguiente:", "imageAlt": "Menú de Seguridad con opciones de Fail2Ban y Lynis" }, "pick": { @@ -28,7 +28,7 @@ "cards": { "fail2ban": { "title": "Fail2Ban", - "body": "Prevención de intrusiones activa. Vigila los logs de autenticación de SSH y las UIs web y banea IPs tras fallos repetidos.", + "body": "Prevención activa de intrusiones. Vigila los registros de autenticación de SSH y las interfaces web y bloquea IP tras fallos repetidos.", "bullets": [ "Tres jails: SSH, Proxmox UI (8006), ProxMenux Monitor (8008)", "Arregla los valores por defecto de journald de Proxmox que bloquean los logs de autenticación", @@ -38,28 +38,28 @@ }, "lynis": { "title": "Lynis", - "body": "Auditor de seguridad offline. Escanea el host e imprime una puntuación de hardening más pistas concretas de remediación.", + "body": "Auditor local de seguridad. Analiza el host y muestra una puntuación de refuerzo y recomendaciones concretas para corregir problemas.", "bullets": [ - "Instalado desde el GitHub upstream de CISOfy (siempre el último)", - "Puntuación de hardening 0–100 + lista de warnings y sugerencias", - "Acciones de ejecutar auditoría y actualizar dentro del menú", - "De solo lectura por diseño — nunca cambia el sistema" + "Instalado desde el repositorio original de CISOfy en GitHub (última versión)", + "Puntuación de refuerzo 0–100 y lista de advertencias y sugerencias", + "Acciones de auditoría y actualización desde el menú", + "De solo lectura: nunca modifica el sistema" ] } }, "workflowTip": { "title": "Flujo de trabajo que usa ambos", - "body": "Ejecuta primero una auditoría de Lynis para ver tu puntuación de partida y las recomendaciones principales. Aplica los arreglos que quieras. Después instala Fail2Ban — implementa una de las recomendaciones más comunes de Lynis (protección contra fuerza bruta SSH) automáticamente y ajusta MaxAuthTries en sshd_config para satisfacer el control SSH-7408. Vuelve a ejecutar Lynis después para confirmar que la puntuación mejoró." + "body": "Ejecuta primero una auditoría Lynis para ver la puntuación inicial y las recomendaciones principales. Aplica las correcciones que elijas. Instala Fail2Ban: implementa automáticamente la protección contra fuerza bruta SSH y ajusta MaxAuthTries en sshd_config para cumplir el control SSH-7408. Vuelve a ejecutar Lynis para comprobar la mejora." }, "fail2banSection": { "heading": "Fail2Ban", "body": "ProxMenux instala Fail2Ban con una configuración afinada específicamente para Proxmox. Más allá del jail estándar de SSH, añade protección para la UI web de Proxmox y el Monitor de ProxMenux, y rodea dos rarezas específicas de Proxmox: un valor por defecto de journald que descarta eventos de autenticación, y el problema del backend systemd que impide a Fail2Ban leer de forma fiable ciertas fuentes del journal. La página de detalle cubre el flujo de instalación completo, los tres jails, el arreglo de journald, el cambio de hardening SSH y la chuleta de solución de problemas.", "optionTitle": "Fail2Ban — instalar y gestionar", - "optionDescription": "Tres jails preconfigurados (sshd agresivo, Proxmox UI puerto 8006, ProxMenux Monitor puerto 8008 + proxy inverso), backend nftables/iptables autodetectado, arreglo de journald y hardening de SSH MaxAuthTries." + "optionDescription": "Tres jails preconfiguradas (sshd agresivo, Proxmox en 8006, ProxMenux Monitor en 8008 y proxy inverso), detección de nftables/iptables, corrección de journald y refuerzo SSH mediante MaxAuthTries." }, "lynisSection": { "heading": "Lynis", - "body": "ProxMenux clona Lynis desde github.com/CISOfy/lynis en /opt/lynis y lo expone como /usr/local/bin/lynis. El paquete de Debian se evita intencionadamente porque va varias versiones mayores por detrás de upstream. La página de detalle cubre el flujo de instalar / auditar / actualizar / desinstalar, cómo leer el informe y cómo actuar sobre los hallazgos.", + "body": "ProxMenux clona Lynis de github.com/CISOfy/lynis en /opt/lynis y lo expone como /usr/local/bin/lynis. Evita el paquete Debian porque va varias versiones principales por detrás del proyecto original. La página detallada explica instalación, auditoría, actualización, desinstalación, lectura del informe y aplicación de sus recomendaciones.", "optionTitle": "Lynis — instalar, auditar y gestionar", "optionDescription": "Instala el último Lynis desde el repo oficial de CISOfy en GitHub (no el paquete más antiguo de Debian), ejecuta auditorías del sistema con puntuación de hardening y actualiza vía git pull." }, diff --git a/web/messages/es/docs/security/lynis.json b/web/messages/es/docs/security/lynis.json index 46cddaa9..452dad31 100644 --- a/web/messages/es/docs/security/lynis.json +++ b/web/messages/es/docs/security/lynis.json @@ -1,37 +1,37 @@ { "meta": { - "title": "Lynis | ProxMenux Documentation", - "description": "Instala Lynis desde el repositorio oficial de CISOfy en GitHub (siempre el último), ejecuta una auditoría del sistema con puntuación de hardening 0–100, actualiza vía git pull. De solo lectura por diseño — nunca modifica el sistema.", - "ogTitle": "Lynis | ProxMenux Documentation", - "ogDescription": "Instala y ejecuta el auditor de seguridad Lynis en Proxmox VE. Instalación desde GitHub upstream, auditoría, actualización." + "title": "Lynis | Documentación de ProxMenux", + "description": "Instala Lynis desde el repositorio oficial CISOfy de GitHub, ejecuta una auditoría con puntuación de refuerzo de 0 a 100 y actualiza mediante git pull. Auditor de solo lectura: no modifica el sistema.", + "ogTitle": "Lynis | Documentación de ProxMenux", + "ogDescription": "Instala y ejecuta el auditor de seguridad Lynis en Proxmox VE. Instalación desde el repositorio original GitHub, auditoría y actualización." }, "header": { "title": "Lynis", - "description": "Clona el último Lynis desde el repositorio oficial de CISOfy en GitHub, lo expone como /usr/local/bin/lynis y ofrece acciones de ejecutar auditoría / actualizar / reinstalar / desinstalar desde el menú. Auditor de solo lectura por diseño — nunca modifica el sistema, solo reporta.", + "description": "Clona el último Lynis del repositorio oficial CISOfy en GitHub, lo expone como /usr/local/bin/lynis y ofrece auditoría, actualización, reinstalación y desinstalación desde el menú. El auditor es de solo lectura: informa sin modificar el sistema.", "section": "Seguridad" }, "intro": { "title": "Qué hace", - "body": "Instala Lynis (el auditor de seguridad de código abierto de CISOfy) desde GitHub upstream en /opt/lynis y crea un wrapper en /usr/local/bin/lynis para que esté en tu PATH. Detecta una instalación existente al arrancar y muestra un menú de gestión (auditoría / actualizar / reinstalar / eliminar) en su lugar." + "body": "Instala Lynis, el auditor de código abierto de CISOfy, desde su repositorio GitHub en /opt/lynis y crea un script lanzador en /usr/local/bin/lynis para incluirlo en PATH. Si detecta una instalación existente al abrirlo, muestra un menú de auditoría, actualización, reinstalación o eliminación." }, "manageMenu": { - "heading": "Menú de gestión (tras la instalación)", + "heading": "Menú de gestión (tras instalar)", "intro": "Una vez instalado Lynis, cada invocación posterior abre el menú de gestión en lugar de volver a ejecutar el instalador. Desde aquí lanzas una auditoría, actualizas vía git pull, reinstalas o desinstalas:", "imageAlt": "Menú de gestión de Lynis con opciones de auditoría, actualizar, reinstalar y eliminar" }, "whyUpstream": { - "heading": "Por qué GitHub upstream, no apt", - "intro": "Debian entrega Lynis vía apt, pero el paquete suele ir varias versiones mayores por detrás de upstream. Los controles más nuevos, los arreglos para nuevos vectores de ataque y las recomendaciones refinadas solo aterrizan en el repo de GitHub. El propio Lynis es un script de shell autocontenido — sin compilación, sin dependencias más allá de git para la instalación — así que clonar el repo es el método de instalación canónico recomendado por la propia CISOfy.", + "heading": "Por qué el repositorio oficial de GitHub y no apt", + "intro": "Debian distribuye Lynis mediante apt, pero el paquete suele ir varias versiones principales por detrás del proyecto original. GitHub recibe antes los controles, correcciones de nuevos vectores de ataque y mejoras de las recomendaciones. Lynis es un script de shell autocontenido, sin compilación ni dependencias de instalación aparte de git; por eso clonar el repositorio es el método recomendado por CISOfy.", "headerSource": "Origen", - "headerPath": "Ruta de instalación", + "headerPath": "Install path", "headerUpdate": "Método de actualización", - "headerFresh": "Frescura de versión", + "headerFresh": "Actualidad de la versión", "rows": [ { "sourceRich": "ProxMenux (este script)", "path": "/opt/lynis/", "update": "git pull (desde el menú)", - "fresh": "Última versión upstream" + "fresh": "Última versión del proyecto original" }, { "sourceRich": "Debian apt", @@ -59,14 +59,14 @@ "/opt/lynis/lynis — ruta directa (por si el wrapper se eliminó)", "/usr/bin/lynis — versión instalada por apt, si el usuario la instaló así anteriormente" ], - "outro": "Si se encuentra cualquiera de estas, se abre el menú de gestión. El script no desinstala un Lynis instalado por apt — solo el que él mismo gestiona (/opt/lynis + el wrapper)." + "outro": "Si encuentra cualquiera de estas rutas, abre el menú de gestión. El script no desinstala Lynis instalado por apt; solo el que gestiona en /opt/lynis y su lanzador." }, "audit": { "heading": "Ejecutar una auditoría", - "intro": "Desde el menú de gestión, elige Ejecutar auditoría de seguridad ahora. Esto equivale a ejecutar:", + "intro": "En el menú de gestión, elige Ejecutar auditoría de seguridad ahora. Equivale a ejecutar:", "code": "lynis audit system --no-colors", - "outro": "La auditoría tarda de 30 segundos a unos minutos según el tamaño del host. La salida fluye directamente al terminal — no hay spinner. Lynis imprime secciones por cada categoría de control, marcando cada test como [ OK ], [ WARNING ] o [ SUGGESTION ]. El resumen al final tiene los números titulares:", - "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" + "outro": "La auditoría tarda de 30 segundos a varios minutos según el tamaño del host. La salida se muestra directamente en el terminal, sin indicador giratorio. Lynis organiza los controles por categoría y marca cada prueba como [ OK ], [ WARNING ] o [ SUGGESTION ]. El resumen final muestra las cifras principales:", + "summary": "================================================================================\n\n Detalles del análisis de seguridad de Lynis:\n\n Índice de refuerzo : 76 [############ ]\n Pruebas realizadas : 247\n Plugins habilitados : 0\n\n Componentes:\n - Cortafuegos [V]\n - Escáner de malware [X]\n\n Módulos de Lynis:\n - Estado de cumplimiento [?]\n - Auditoría de seguridad [V]\n - Análisis de vulnerabilidades [V]\n\n================================================================================" }, "report": { "heading": "Leer el informe", @@ -99,7 +99,7 @@ }, "update": { "heading": "Actualizar Lynis", - "body": "Desde el menú de gestión, Actualizar Lynis a la última versión ejecuta git pull --quiet dentro de /opt/lynis. Si el directorio existe pero no es un checkout de Git (p. ej. alguien copió los archivos a mano), el script recae en una reinstalación completa." + "body": "En el menú, Actualizar Lynis a la última versión ejecuta git pull --quiet en /opt/lynis. Si el directorio existe pero no es una copia Git (por ejemplo, se copiaron archivos manualmente), el script reinstala por completo." }, "reinstall": { "heading": "Reinstalar / desinstalar", @@ -108,7 +108,7 @@ "rows": [ { "actionRich": "Reinstalar", - "whatRich": "Elimina /opt/lynis, vuelve a clonar desde GitHub, recrea el wrapper. Úsalo si el checkout local está corrupto." + "whatRich": "Elimina /opt/lynis, clona de nuevo desde GitHub y recrea el lanzador. Úsalo si la copia local está dañada." }, { "actionRich": "Eliminar", @@ -128,7 +128,7 @@ "notFoundTitle": "lynis: command not found tras la instalación", "notFoundIntro": "El wrapper en /usr/local/bin/lynis no se creó o se eliminó. Arreglo más rápido: reinstalar desde el menú. Arreglo manual:", "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", - "sshTitle": "La auditoría imprime \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshTitle": "La auditoría muestra \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", "sshIntro": "El demonio SSH permite demasiados intentos de autenticación por conexión. O bien instala Fail2Ban desde ProxMenux (que establece MaxAuthTries=3 automáticamente) o edita /etc/ssh/sshd_config a mano:", "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", "scoreTitle": "La puntuación bajó tras una actualización de Proxmox", @@ -140,7 +140,7 @@ }, "sample": { "heading": "Informe de ejemplo", - "intro": "ProxMenux Monitor empaqueta cada ejecución de Lynis en un PDF de varias páginas disponible desde la pestaña de Seguridad en el panel. La primera página es el resumen ejecutivo — puntuación de hardening, info del sistema, vista general de la postura de seguridad. Las páginas siguientes listan cada warning con explicación y cada sugerencia rankeada por impacto, más el inventario de paquetes usado durante la auditoría.", + "intro": "ProxMenux Monitor genera un PDF de varias páginas para cada ejecución de Lynis desde la pestaña Seguridad. La primera página resume puntuación de refuerzo, información del sistema y estado de seguridad. Las siguientes detallan cada advertencia, las sugerencias ordenadas por impacto y el inventario de paquetes utilizado en la auditoría.", "imageAlt": "Primera página del PDF del Lynis Security Audit Report — resumen ejecutivo con Hardening Index 71/100, bloque de información del sistema y vista general de la postura de seguridad con firewall, escáner de malware y recuento de paquetes instalados", "captionPrefix": "Primera página de un informe de ejemplo. El PDF completo (", "captionLink": "ejemplo", diff --git a/web/messages/es/docs/security/ssl-letsencrypt.json b/web/messages/es/docs/security/ssl-letsencrypt.json index 9fb83863..0e97809e 100644 --- a/web/messages/es/docs/security/ssl-letsencrypt.json +++ b/web/messages/es/docs/security/ssl-letsencrypt.json @@ -1,13 +1,13 @@ { "meta": { "title": "HTTPS para ProxMenux Monitor — certificado del host Proxmox o rutas personalizadas | ProxMenux", - "description": "Sirve ProxMenux Monitor sobre HTTPS usando o bien el certificado del host Proxmox (autodetectado, incluyendo el certificado Let's Encrypt subido vía la pestaña ACME de Proxmox) o un certificado personalizado en cualquier ruta del disco.", + "description": "Sirve ProxMenux Monitor por HTTPS con el certificado del host Proxmox (detectado automáticamente, incluido el de Let’s Encrypt instalado mediante ACME de Proxmox) o un certificado personalizado en cualquier ruta del disco.", "ogTitle": "HTTPS para ProxMenux Monitor — certificado del host Proxmox o personalizado", "ogDescription": "Habilita HTTPS en el puerto 8008 con el certificado del host Proxmox o cualquier par .pem / .key personalizado." }, "header": { "title": "HTTPS para ProxMenux Monitor", - "description": "Activa HTTPS para el Monitor (puerto 8008) usando o bien el certificado del host Proxmox que el Monitor autodetecta, o un certificado personalizado ubicado en cualquier sitio del disco. La opción de Proxmox elige de forma transparente el certificado subido por ACME cuando está presente, así que un certificado Let's Encrypt gestionado por la UI de Proxmox se reutiliza sin un trabajo de renovación extra.", + "description": "Activa HTTPS para Monitor en 8008 con el certificado del host Proxmox detectado automáticamente o uno personalizado en disco. La opción Proxmox prefiere el certificado instalado mediante ACME, si existe, y reutiliza el Let’s Encrypt gestionado por Proxmox sin otra tarea de renovación.", "section": "Seguridad" }, "intro": { @@ -15,20 +15,20 @@ "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." }, "wheresetting": { - "heading": "Dónde vive el ajuste", - "body": "Abre el Monitor y ve a Settings → Security → HTTPS / SSL. El panel muestra el estado actual (HTTP o HTTPS), y en un host sin HTTPS aún lista las fuentes de certificado que el Monitor ha podido detectar.", + "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 Settings → Security → HTTPS / SSL antes de la activación. El Monitor saca a la luz el certificado que el propio Proxmox está usando y lo ofrece como una fuente de un solo clic." + "caption": "Panel Ajustes → Seguridad → HTTPS / SSL antes de activar HTTPS. Monitor muestra el certificado que usa Proxmox y permite elegirlo con un clic." }, "twoways": { "heading": "Dos formas de habilitar HTTPS", "proxmox": { "title": "Usar el certificado de Proxmox", - "summary": "Un clic. El Monitor reutiliza el certificado que el propio Proxmox VE sirve en el puerto 8006.", + "summary": "Un clic: Monitor reutiliza el certificado que Proxmox VE presenta en el puerto 8006.", "items": [ - "Autodetectado — no hay ruta que teclear", - "Elige el certificado subido por ACME si está presente, recae en el self-signed por defecto", - "La renovación pasa por Proxmox; el Monitor sigue automáticamente en el siguiente arranque" + "Detectado automáticamente, sin introducir rutas", + "Prefiere el certificado ACME si existe; si no, usa el autofirmado predeterminado", + "Proxmox renueva; Monitor valida y carga el certificado en la siguiente conexión TLS nueva" ] }, "custom": { @@ -37,7 +37,7 @@ "items": [ "Solo rutas absolutas (el proceso del Monitor debe poder leer ambos)", "Útil cuando los certificados vienen de certbot, acme.sh o un wildcard", - "La renovación es tuya — reinicia el servicio del Monitor tras que el archivo cambie" + "La renovación queda a tu cargo: reinicia Monitor cuando cambien los archivos" ] } }, @@ -54,7 +54,7 @@ { "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", "originRich": "Certificado personalizado subido vía la UI de Proxmox — incluyendo el certificado Let's Encrypt emitido en Datacenter → Node → Certificates → ACME.", - "when": "Preferido siempre que existan ambos archivos. El Monitor seguirá el certificado que el propio Proxmox está sirviendo." + "when": "Se prefiere cuando existen ambos archivos; Monitor usa el certificado presentado por Proxmox." }, { "fileRich": "pve-ssl.pem
pve-ssl.key", @@ -65,17 +65,17 @@ }, "callout": { "title": "Misma prioridad que el propio Proxmox", - "bodyRich": "pveproxy usa la misma regla de selección, así que elegir la opción de Proxmox en el Monitor siempre sirve el certificado que la UI web de Proxmox ya está sirviendo en el puerto 8006. Sin copias, sin pipeline de renovación aparte." + "bodyRich": "pveproxy aplica la misma selección: al elegir Proxmox, Monitor usa el certificado que la interfaz de Proxmox presenta en 8006, sin copiarlo ni mantener otro proceso de renovación." } }, "letsencrypt": { "heading": "Obtener un certificado Let's Encrypt vía Proxmox ACME", - "intro": "Proxmox VE entrega su propio cliente ACME. Pulsar \"Order Certificate Now\" escribe el resultado en /etc/pve/local/pveproxy-ssl.pem, que es exactamente lo que el Monitor lee. La receta de cinco pasos de abajo es el mismo procedimiento que la guía oficial de Proxmox — destilada a los comandos que realmente necesitas ejecutar.", + "intro": "Proxmox VE incluye su propio cliente ACME. «Order Certificate Now» guarda el resultado en /etc/pve/local/pveproxy-ssl.pem, que Monitor lee. Los cinco pasos siguientes condensan el procedimiento de la guía oficial de Proxmox en los comandos necesarios.", "prereqs": { "title": "Prerrequisitos", "items": [ "El nodo tiene un nombre DNS real (no solo una IP) apuntándole.", - "O bien puerto 80 alcanzable desde la internet pública (challenge HTTP-01, el más simple), o bien un proveedor de DNS con credenciales API (challenge DNS-01 — funciona detrás de NAT, soporta wildcards).", + "O bien puerto 80 accesible desde Internet (verificación HTTP-01, la más sencilla), o bien proveedor DNS con credenciales API (DNS-01, funciona tras NAT y admite comodines).", "Una dirección de email para la cuenta ACME." ] }, @@ -90,7 +90,7 @@ "http01Rich": "HTTP-01 es el más fácil: está integrado, sin plugin extra. Let's Encrypt se conecta a http://<your-host>/.well-known/acme-challenge/... en el puerto 80, así que el puerto 80 debe estar abierto desde la internet pública a tu nodo — típicamente un port forward en tu router.", "dns01Rich": "DNS-01 funciona sin exponer el puerto 80 (bueno para homelabs detrás de CG-NAT) y es la única opción para certificados wildcard. Añades un plugin DNS una vez con el token API de tu proveedor. Ejemplo con Cloudflare:", "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", - "outroRich": "Para otros proveedores (Gandi, OVH, AWS Route 53, etc.) consulta pvenode acme plugin add dns --help — Proxmox entrega todos los plugins DNS de acme.sh." + "outroRich": "Para otros proveedores (Gandi, OVH, AWS Route 53, etc.), consulta pvenode acme plugin add dns --help. Proxmox incluye todos los plugins DNS de acme.sh." }, "step3": { "heading": "Paso 3 — Asocia el dominio a tu nodo", @@ -112,19 +112,19 @@ }, "gui": { "title": "¿Prefieres la GUI? Los mismos cinco pasos", - "bodyRich": "Datacenter → ACME registra la cuenta, Datacenter → ACME → Plugins añade el plugin DNS, Node → Certificates → ACME asocia el dominio y Order Certificates Now ejecuta el paso 4. Los comandos CLI de arriba son útiles para scriptar y para setups headless / solo CLI." + "bodyRich": "Datacenter → ACME registra la cuenta; Datacenter → ACME → Plugins añade DNS; Node → Certificates → ACME vincula el dominio; Order Certificates Now ejecuta el paso 4. Los comandos anteriores sirven para automatizar y trabajar sin interfaz gráfica." } }, "switchToHttps": { "heading": "Cambiar el Monitor a HTTPS", - "bodyRich": "Una vez que /etc/pve/local/pveproxy-ssl.pem está firmado por Let's Encrypt, en el Monitor solo hace falta un clic: abre Ajustes → Seguridad → HTTPS / SSL, confirma que el emisor mostrado en el panel del certificado detectado sea Let's Encrypt (y no la CA local de Proxmox) y pulsa Usar certificado de Proxmox. El servicio del Monitor se reinicia y la siguiente carga del navegador usa HTTPS en el puerto 8008, sin advertencias porque la cadena es de confianza pública. Las renovaciones posteriores de Proxmox ACME se validan y seleccionan durante la siguiente conexión TLS nueva; no existe un comprobador periódico del certificado ni se reinicia el servicio durante la renovación. Actualizar certificado permanece 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 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." }, "custom": { "heading": "Certificado personalizado — cuándo usarlo", - "intro": "Pulsa Use Custom Certificate para introducir dos rutas absolutas:", + "intro": "Pulsa Usar certificado personalizado e introduce dos rutas absolutas:", "items": [ "Certificado — cadena completa en PEM (.pem o .crt).", - "Clave privada — clave correspondiente en PEM (.key)." + "Clave privada: clave correspondiente en PEM (.key)." ], "outro": "Ambos archivos los lee el proceso del Monitor al arrancar. Asegúrate de que se mantengan legibles por el usuario con el que corre la unidad systemd (root por defecto). Ubicaciones comunes cuando ACME se gestiona fuera de Proxmox:", "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", @@ -143,15 +143,15 @@ }, "trustCa": { "heading": "Confiar en la CA self-signed de Proxmox", - "intro1Rich": "Si prefieres mantener el certificado por defecto generado por Proxmox (sin ACME), puedes parar el warning del navegador importando la CA raíz del clúster en el almacén de confianza del SO o navegador. El Monitor seguirá autodetectando el mismo /etc/pve/local/pveproxy-ssl.pem; lo único que cambia es que el dispositivo que ve el panel ahora reconoce el issuer.", + "intro1Rich": "Para conservar el certificado predeterminado de Proxmox sin ACME, importa la CA raíz del clúster en el almacén de confianza del sistema o navegador. Monitor detecta /etc/pve/local/pve-ssl.pem como certificado alternativo. El cliente confiará en su emisor; la URL aún debe coincidir con un nombre del certificado.", "intro2Rich": "El archivo de la CA vive en /etc/pve/pve-root-ca.pem en cada nodo. En un clúster la ruta está en el sistema de archivos compartido pmxcfs, así que todos los nodos comparten la misma raíz, y una sola importación cubre el datacenter entero. Cópialo a tu máquina:", "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", "thenImport": "Después impórtalo en el lado del cliente:", "items": [ - "Linux (todo el sistema): sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt seguido de sudo update-ca-certificates. Los navegadores que usan el almacén del sistema (Chromium, Edge) lo toman tras reiniciar; Firefox usa su propio almacén y necesita la importación manual de abajo.", - "macOS: doble clic en el archivo .pem, se abre Keychain Access → añádelo al keychain del Sistema → establece Trust → Always Trust.", - "Windows: clic derecho en el archivo → Install Certificate → colócalo en Trusted Root Certification Authorities.", - "Firefox (cualquier SO): Settings → Privacy y Security → Certificates → View Certificates → Authorities → Import, marca Trust this CA to identify websites." + "Linux (todo el sistema): ejecuta sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt y después sudo update-ca-certificates. Los navegadores que usan el almacén del sistema (Chromium, Edge) lo reconocen tras reiniciar; Firefox usa su propio almacén y requiere la importación manual siguiente.", + "macOS: abre el archivo .pem con doble clic en Acceso a Llaveros, añádelo al llavero Sistema y configura Confiar → Confiar siempre.", + "Windows: clic derecho en el archivo → Instalar certificado → guárdalo en Entidades de certificación raíz de confianza.", + "Firefox (cualquier SO): Ajustes → Privacidad y seguridad → Certificados → Ver certificados → Autoridades → Importar; marca Confiar en esta CA para identificar sitios web." ], "standalone": { "title": "Nodos standalone fuera de un clúster", @@ -160,9 +160,9 @@ }, "disable": { "heading": "Deshabilitar HTTPS", - "bodyRich": "Pulsa Disable HTTPS en el mismo panel. El servicio del Monitor reinicia de vuelta en HTTP plano — útil si un archivo de certificado personalizado se vuelve no legible y el servicio no vuelve a levantarse. No hay paso de desinstalación aparte; los propios archivos de certificado no se tocan.", + "bodyRich": "Pulsa Desactivar HTTPS en el mismo panel. Monitor reinicia con HTTP sin cifrar, útil si un certificado personalizado no se puede leer y el servicio no arranca. No hay otra desinstalación y los archivos de certificado no se modifican.", "stateCallout": { - "title": "Dónde vive el estado", + "title": "Dónde se guarda el estado", "bodyRich": "/etc/proxmenux/ssl_config.json almacena las rutas de certificado activas y la fuente (none, proxmox, custom). Borrar el archivo equivale a deshabilitar HTTPS — en el siguiente arranque el Monitor recae en HTTP." } } diff --git a/web/messages/fr/docs/security/fail2ban.json b/web/messages/fr/docs/security/fail2ban.json new file mode 100644 index 00000000..8c313a04 --- /dev/null +++ b/web/messages/fr/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | Documentation ProxMenux", + "description": "Installez Fail2Ban avec trois jails optimisés pour Proxmox (SSH agressif, Proxmox UI 8006, ProxMenux Monitor 8008). Inclut le correctif journald MaxLevelStore, des services de journalisation personnalisés pour plus de fiabilité, un backend nftables/iptables détecté automatiquement et le renforcement SSH MaxAuthTries.", + "ogTitle": "Fail2Ban | Documentation ProxMenux", + "ogDescription": "Protection contre la force brute pour SSH et les interfaces web Proxmox, avec des correctifs journald et backend propres à Proxmox." + }, + "header": { + "title": "Fail2Ban", + "description": "Installe Fail2Ban pour Proxmox : trois jails (SSH agressif, interface Proxmox sur 8006, ProxMenux Monitor sur 8008 et proxy inverse), correction du niveau journald pour conserver les événements SSH, deux services copiant le journal vers des fichiers afin de contourner un problème connu du backend systemd, détection du backend de pare-feu et durcissement SSH avec MaxAuthTries.", + "section": "Sécurité" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Installe Fail2Ban depuis les dépôts Debian et écrit une configuration Proxmox protégeant immédiatement SSH, l'interface web Proxmox et ProxMenux Monitor. Une installation existante est détectée et un menu de gestion propose de réinstaller ou de supprimer." + }, + "firstLaunch": { + "heading": "Boîte de dialogue Premier lancement", + "body": "Sans Fail2Ban installé, le script résume dans un dialogue ce qu'il va installer et configurer. Annuler quitte sans modification ; confirmer lance l'installation.", + "imageAlt": "Confirmation de Fail2Ban avec les trois jails, correction de journald et durcissement SSH" + }, + "jails": { + "heading": "Les trois jails", + "headerJail": "Jail", + "headerProtects": "Protège", + "headerRetries": "Tentatives / Fenêtre", + "headerBan": "Durée de blocage", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (mode agressif — couvre ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 heures" + }, + { + "jail": "[proxmox]", + "protects": "UI web Proxmox (port 8006)", + "retries": "3 / 10 min", + "ban": "1 heure" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (port 8008 + http/https proxy inverse)", + "retries": "3 / 10 min", + "ban": "1 heure" + } + ], + "outro": "Par défaut mondial à partir de jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (24h pour les prisons qui ne le remplacent pas), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Pourquoi la correction de journald est importante", + "intro": "Proxmox fournit /etc/systemd/journald.conf avec MaxLevelStore=warning. journald supprime les messages de niveau inférieur à warning avant de les stocker. SSH et PAM signalent les échecs d'authentification aux niveaux info / notice, donc :", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "échec d'authentification\n(niveau info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ événement ignoré sans avertissement", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "ne voit rien\n→ n'interdit jamais rien", + "arrowLabel": "Proxmox par défaut" + }, + "afterDiagram": "L'installateur détecte cela et écrit un drop-in à /etc/systemd/journald.conf.d/proxmenux-loglevel.conf élevant à la fois MaxLevelStore et MaxLevelSyslog à info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Puis redémarre systemd-journald. Le drop-in est supprimé lors de la désinstallation, ce qui restaure le Proxmox original par défaut." + }, + "loggers": { + "heading": "Pourquoi deux services d'enregistreur personnalisé", + "intro1": "Fail2Ban peut lire directement le journal systemd (backend = systemd), mais ce backend présente sur Proxmox des problèmes connus avec les processus de travail de pvedaemon : les événements d'authentification apparaissent dans le journal sans être toujours détectés. Le problème concerne aussi parfois sshd.", + "intro2": "ProxMenux crée deux petits services systemd qui suivent les unités concernées avec journalctl -f et ajoutent chaque ligne à un fichier. Fail2Ban lit ces fichiers avec le fiable backend = auto (mode fichier) :", + "headerService": "Services", + "headerSource": "Unité source", + "headerOutput": "Fichier de sortie", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Les deux services sont déclarés PartOf=fail2ban.service donc ils redémarrent avec Fail2Ban et s'arrêtent avec elle. Mode 640 appartenant à root:adm dans les fichiers journaux. Le troisième journal utilisé par la prison [proxmenux] (/var/log/proxmenux-auth.log) est écrit directement par l'application ProxMenux Monitor Flask — aucun service d'enregistreur nécessaire pour celui-ci." + }, + "backend": { + "heading": "Détection du backend de pare-feu", + "intro": "L'installateur sonde l'hôte : si nft list ruleset fonctionne, il choisit nftables comme mesure d'interdiction. Sinon, il revient à iptables-multiport / iptables-allports. Le choix est écrit dans jail.local :", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "Durcissement SSH : MaxAuthTries", + "intro": "Le contrôle Lynis SSH-7408 recommande MaxAuthTries 3 dans sshd_config. La jail SSH utilise maxretry = 2 pour bloquer après des échecs répétés ; MaxAuthTries limite les tentatives dans une connexion. Ce réglage SSH ajoute une défense en profondeur, y compris lorsque Fail2Ban est arrêté pour maintenance.", + "installerIntro": "L'installateur:", + "items": [ + "Lit la valeur actuelle MaxAuthTries (ou par défaut 6 si elle est absente).", + "Enregistre-le dans /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Modifie sshd_config sur place : remplace la ligne existante, décommente une ligne commentée ou ajoute une nouvelle ligne.", + "Recharge sshd (recharger, pas redémarrer, pour garder les sessions existantes en vie)." + ], + "outro": "Lors de la désinstallation, la valeur originale enregistrée est restaurée et sshd est rechargée." + }, + "manage": { + "heading": "Gérer une installation existante", + "intro": "Si Fail2Ban est déjà installé lorsque vous ouvrez le menu, le script le détecte et affiche un menu de gestion au lieu de ré-exécuter l'installateur:", + "headerAction": "Décision", + "headerWhat": "Ce qu'il fait", + "rows": [ + { + "action": "Réinstaller", + "what": "Relance l'installation complète et réécrit toutes les jails avec les valeurs actuelles de ProxMenux. À utiliser lorsqu'une mise à jour modifie les recommandations." + }, + { + "action": "Supprimer", + "what": "Arrête fail2ban et les deux services d'enregistrement, purge le package apt, supprime tous les fichiers jail/filtre, supprime le drop-in journald (restauration de la valeur par défaut de Proxmox) et restaure l'original SSH MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Vérifiez que ça marche.", + "intro": "Après l'installation, commandes utiles de l'hôte:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Dépannage", + "neverBansTitle": "Fail2Ban fonctionne mais n'interdit jamais rien", + "neverBansBody": "Vérifiez que tail -f /var/log/ssh-auth.log reçoit une entrée après une tentative SSH avec un mauvais mot de passe depuis une autre machine. Sinon, examinez systemctl status ssh-auth-logger.service. Si le service est actif, consultez les événements récents avec journalctl -u ssh.service -n 50 --no-pager -p info et vérifiez que le complément journald autorise le niveau info.", + "monitorEmptyTitle": "La jail ProxMenux Monitor ne contient aucune entrée malgré des connexions échouées", + "monitorEmptyBody": "Le [proxmenux] jail lit /var/log/proxmenux-auth.log, qui est écrit par l'application ProxMenux Monitor Flask — et non par un enregistreur journald. Si vous n'exécutez pas le moniteur, le fichier reste vide et le jail ne se déclenche jamais. C'est prévu ; la configuration jail est inoffensive. Si vous exécutez le moniteur et que le journal est vide, vérifiez la configuration de journalisation Flask.", + "selfBanTitle": "Je me suis bloqué moi-même", + "selfBanIntro": "À partir d'une console / IPMI / iKVM (ou d'une autre IP sur liste blanche):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "Échec d'apt-get : ''Unable to locate package fail2ban''", + "aptFailBody": "Les dépôts Debian manquent sur l'hôte (fréquent sur les installations Proxmox minimales). L'installateur crée /etc/apt/sources.list.d/debian.sources avec le bon nom de code (bookworm / trixie), puis réessaie. Si cela échoue encore, vérifiez VERSION_CODENAME dans /etc/os-release et l'accessibilité du dépôt.", + "lockoutTitle": "SSH me verrouille après l'installation", + "lockoutBody": "L'installateur définit MaxAuthTries=3. Si votre gestionnaire de mot de passe / agent récupère plusieurs clés, vous pouvez dépasser cela sur une seule tentative de connexion. Limiter les clés offertes : ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. Ou augmenter temporairement MaxAuthTries en sshd_config pendant le débogage." + }, + "files": { + "heading": "Fichiers écrits", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Autres", + "monitorLabel": "ProxMenux Monitor → Onglet Sécurité", + "monitorTail": " — installation depuis le tableau de bord, état des jails en direct, IP bloquées et réglage des tentatives et de la durée de blocage par jail dans le navigateur.", + "lynisLabel": "Lynis", + "lynisTail": " — lancer un audit avant et après pour vérifier que le contrôle SSH-7408 est satisfait.", + "securityLabel": "Aperçu de la sécurité", + "securityTail": "— retour à l'aperçu de la section." + } +} diff --git a/web/messages/fr/docs/security/index.json b/web/messages/fr/docs/security/index.json new file mode 100644 index 00000000..a1d70fe2 --- /dev/null +++ b/web/messages/fr/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Sécurité Proxmox — Fail2Ban et audit de durcissement Lynis | ProxMenux", + "description": "Deux outils de sécurité installables pour Proxmox VE : Fail2Ban (prévention des intrusions pour SSH, l'interface web Proxmox et ProxMenux Monitor) et Lynis (audit de sécurité et de durcissement installé depuis le dépôt officiel GitHub).", + "ogTitle": "Sécurité Proxmox — Fail2Ban et audit de durcissement Lynis", + "ogDescription": "Fail2Ban et Lynis pour Proxmox VE : prévention des intrusions et audit de durcissement, installés et gérés par ProxMenux.", + "twitterTitle": "Sécurité Proxmox | ProxMenux", + "twitterDescription": "Fail2Ban et Lynis pour Proxmox VE : prévention des intrusions et audit de durcissement." + }, + "header": { + "title": "Sécurité", + "description": "Deux outils complémentaires pour Proxmox VE : défense active (Fail2Ban bloque les IP attaquant SSH ou les interfaces web) et audit local (Lynis cherche les lacunes de durcissement et donne un score de 0 à 100). Chaque outil possède son menu d'installation et de gestion, qui détecte une installation existante avant toute action.", + "section": "Sécurité" + }, + "intro": { + "title": "Défense active et audit local", + "body": "Ils répondent à des questions différentes. Fail2Ban répond à « Sommes-nous attaqués en ce moment ? » en surveillant les journaux d'authentification et en bloquant les IP responsables. Lynis répond à « Quel est le niveau de durcissement de cet hôte ? » par un audit ponctuel avec des recommandations concrètes. Utilisez les deux : Lynis indique les améliorations, Fail2Ban réagit aux attaques en cours." + }, + "opening": { + "heading": "Ouverture du menu", + "body": "Dans le menu principal ProxMenux, sélectionnez Sécurité. Voici ce qui apparaît :", + "imageAlt": "Menu de sécurité avec options Fail2Ban et Lynis" + }, + "pick": { + "heading": "Choisissez votre outil", + "body": "Les deux outils sont indépendants : installez-en un, les deux ou aucun. Chaque carte ci-dessous mène à la section détaillant cet outil." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Prévention active des intrusions. Surveille les journaux d'authentification SSH et web et bloque les IP après des échecs répétés.", + "bullets": [ + "Trois jails : SSH, interface Proxmox (8006), ProxMenux Monitor (8008)", + "Corrige les paramètres journald de Proxmox qui bloquent les journaux d'authentification", + "Auto-détecte nftables / iptables", + "SSH durcissement: MaxAuthTries=3 (recommandation Lynis)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Auditeur de sécurité local. Analyse l'hôte et affiche un score de durcissement et des conseils concrets pour corriger les problèmes.", + "bullets": [ + "Installé depuis le dépôt officiel CISOfy sur GitHub (dernière version)", + "Score de durcissement 0–100 et liste des avertissements et suggestions", + "Audit et mise à jour depuis le menu", + "Conçu en lecture seule : ne modifie jamais le système" + ] + } + }, + "workflowTip": { + "title": "Flux de travail qui utilise les deux", + "body": "Lancez d'abord Lynis pour connaître le score initial et les recommandations principales. Appliquez les corrections souhaitées. Installez ensuite Fail2Ban : il met automatiquement en place la protection SSH contre la force brute et ajuste MaxAuthTries dans sshd_config pour satisfaire le contrôle SSH-7408. Relancez Lynis pour vérifier l'amélioration." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "ProxMenux installe Fail2Ban avec une configuration adaptée à Proxmox. En plus de la jail SSH standard, il protège l'interface web Proxmox et ProxMenux Monitor. Il contourne deux problèmes spécifiques : un paramètre journald supprimant les événements d'authentification et un problème du backend systemd empêchant la lecture fiable de certaines sources du journal. La page détaillée explique l'installation, les trois jails, la correction de journald, le durcissement SSH et le dépannage.", + "optionTitle": "Fail2Ban — installer et gérer", + "optionDescription": "Trois jails préconfigurées (sshd agressif, interface Proxmox sur 8006, ProxMenux Monitor sur 8008 et proxy inverse), détection de nftables/iptables, correction de journald et durcissement SSH avec MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "ProxMenux clone Lynis depuis github.com/CISOfy/lynis dans /opt/lynis et le rend accessible par /usr/local/bin/lynis. Le paquet Debian est volontairement évité car il accuse plusieurs versions majeures de retard sur le projet original. La page détaillée explique installation, audit, mise à jour, désinstallation, lecture du rapport et traitement des résultats.", + "optionTitle": "Lynis — installation, audit et gestion", + "optionDescription": "Installe le dernier Lynis de la repo officielle CISOfy GitHub (pas l'ancien paquet Debian), exécute des audits système avec score de durcissement, et des mises à jour via git pull." + }, + "componentStatus": { + "heading": "État des composantes", + "body": "Les deux installateurs enregistrent leur état en /usr/local/share/proxmenux/components_status.json dans la catégorie security. ProxMenux utilise ce fichier pour décider s'il faut afficher l'installation ou le menu de gestion lors d'exécutions ultérieures, et les mêmes données alimentent les tableaux de bord dans le ProxMenux Monitor lorsqu'elles sont présentes." + } +} diff --git a/web/messages/fr/docs/security/lynis.json b/web/messages/fr/docs/security/lynis.json new file mode 100644 index 00000000..0444e96d --- /dev/null +++ b/web/messages/fr/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | Documentation ProxMenux", + "description": "Installez la dernière version de Lynis depuis le dépôt officiel CISOfy sur GitHub, lancez un audit avec score de durcissement de 0 à 100 et mettez à jour par git pull. Auditeur en lecture seule, sans modification du système.", + "ogTitle": "Lynis | Documentation ProxMenux", + "ogDescription": "Installez et exécutez Lynis sur Proxmox VE. Installation depuis le dépôt GitHub officiel, audit et mise à jour." + }, + "header": { + "title": "Lynis", + "description": "Clone la dernière version de Lynis depuis le dépôt officiel CISOfy sur GitHub, la rend disponible par /usr/local/bin/lynis et propose audit, mise à jour, réinstallation et désinstallation dans le menu. L'auditeur est en lecture seule : il rapporte sans modifier le système.", + "section": "Sécurité" + }, + "intro": { + "title": "Ce que ça fait", + "body": "Installe Lynis, l'auditeur de sécurité libre de CISOfy, depuis son dépôt GitHub dans /opt/lynis et crée un script de lancement dans /usr/local/bin/lynis pour le rendre accessible dans PATH. Une installation existante ouvre un menu d'audit, mise à jour, réinstallation ou suppression." + }, + "manageMenu": { + "heading": "Menu de gestion (après installation)", + "intro": "Une fois que Lynis est installé, chaque invocation subséquente ouvre le menu de gestion au lieu de relancer l'installateur. De là vous lancez un audit, mise à jour via git pull, réinstaller ou désinstaller:", + "imageAlt": "Lynis menu de gestion avec audit, mise à jour, réinstaller et supprimer les options" + }, + "whyUpstream": { + "heading": "Pourquoi le dépôt officiel GitHub plutôt qu'apt", + "intro": "Debian fournit Lynis via apt, mais le paquet accuse généralement plusieurs versions majeures de retard. Les nouveaux contrôles, correctifs pour de nouveaux vecteurs d'attaque et recommandations améliorées arrivent d'abord sur GitHub. Lynis est un script shell autonome, sans compilation ni dépendance d'installation au-delà de git ; cloner le dépôt est donc la méthode recommandée par CISOfy.", + "headerSource": "Source", + "headerPath": "Install path", + "headerUpdate": "Méthode de mise à jour", + "headerFresh": "Actualité de la version", + "rows": [ + { + "sourceRich": "ProxMenux (ce script)", + "path": "/opt/lynis/", + "update": "git pull (depuis le menu)", + "fresh": "Dernière version du projet original" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Souvent mois / années de retard" + } + ] + }, + "install": { + "heading": "Comment fonctionne l'installation", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(installer git d'abord si nécessaire)", + "node2Label": "/opt/lynis/", + "node2Detail": "dépôt complet\ny compris le script ./lynis", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "script de lancement :\ncd /opt/lynis && ./lynis $@", + "outro": "Le lanceur est nécessaire : Lynis doit être exécuté depuis son propre répertoire pour charger les chemins relatifs des plugins et des profils. Le lanceur masque ce détail et permet à lynis audit system de fonctionner depuis n'importe quel répertoire." + }, + "detection": { + "heading": "Voies de détection", + "intro": "Avant d'afficher le menu, le script vérifie trois emplacements pour déterminer si Lynis est déjà présent :", + "items": [ + "/usr/local/bin/lynis — enveloppe installée par ProxMenux", + "/opt/lynis/lynis — chemin direct (au cas où l'emballage serait enlevé)", + "/usr/bin/lynis — version apt-installée, si l'utilisateur l'a installé de cette façon précédemment" + ], + "outro": "Si un de ces chemins existe, le menu de gestion s'ouvre. Le script ne désinstalle pas un Lynis installé par apt, uniquement celui qu'il gère dans /opt/lynis et son lanceur." + }, + "audit": { + "heading": "Exécuter un audit", + "intro": "Dans le menu de gestion, choisissez Lancer l'audit de sécurité maintenant. Cela revient à exécuter :", + "code": "lynis audit system --no-colors", + "outro": "L'audit prend de 30 secondes à quelques minutes selon la taille de l'hôte. La sortie apparaît directement dans le terminal, sans animation d'attente. Lynis classe les contrôles par catégorie et marque chaque test [ OK ], [ WARNING ] ou [ SUGGESTION ]. Le résumé final affiche les chiffres principaux :", + "summary": "================================================================================\n\n Détails de l’audit de sécurité Lynis:\n\n Indice de durcissement : 76 [############ ]\n Tests effectués : 247\n Plugins activés : 0\n\n Composants:\n - Pare-feu [V]\n - Analyseur de logiciels malveillants [X]\n\n Modules Lynis:\n - État de conformité [?]\n - Audit de sécurité [V]\n - Analyse de vulnérabilités [V]\n\n================================================================================" + }, + "report": { + "heading": "Lire le rapport", + "intro": "Les deux sections importantes sont Avertissements (les choses que vous devriez fixer bientôt) et Suggestions (recommandations pour améliorer le durcissement). Chaque élément porte un ID de contrôle comme SSH-7408 — utile lors de la recherche des documents Lynis pour la justification et la correction.", + "headerMarker": "Marqueur", + "headerMeaning": "Signification", + "headerAction": "Décision", + "rows": [ + { + "markerRich": "OK", + "meaning": "Essai réussi", + "action": "Rien" + }, + { + "markerRich": "WARNING", + "meaning": "Problème réel, correction recommandée", + "action": "Lire la description du contrôle, planifier une correction" + }, + { + "markerRich": "SUGGESTION", + "meaning": "Amélioration du durcissement disponible", + "action": "Appliquer si elle correspond à votre modèle de menace" + } + ], + "outro": "Le rapport complet et les données lisibles par machine sont écrits à /var/log/lynis.log et /var/log/lynis-report.dat par Lynis lui-même." + }, + "pairFail2ban": { + "title": "À combiner avec Fail2Ban", + "body": "SSH-7408 (MaxAuthTries) est l'un des avertissements les plus courants. L'installation de Fail2Ban à partir de ProxMenux l'élimine automatiquement parce que l'installateur définit MaxAuthTries=3 dans le cadre de son processus de durcissement SSH. Lancez Lynis pour confirmer que l'avertissement a disparu." + }, + "update": { + "heading": "Mettre à jour Lynis", + "body": "Dans le menu, Mettre Lynis à jour vers la dernière version exécute git pull --quiet dans /opt/lynis. Si le répertoire n'est pas une copie de travail Git, par exemple après une copie manuelle des fichiers, le script réinstalle entièrement." + }, + "reinstall": { + "heading": "Réinstaller / désinstaller", + "headerAction": "Décision", + "headerWhat": "Ce qu'il fait", + "rows": [ + { + "actionRich": "Réinstaller", + "whatRich": "Supprime /opt/lynis, clone à nouveau depuis GitHub et recrée le lanceur. À utiliser si la copie locale est endommagée." + }, + { + "actionRich": "Supprimer", + "whatRich": "Supprime /opt/lynis et /usr/local/bin/lynis. Conserve les journaux /var/log/lynis* comme historique des audits. Un Lynis installé par apt dans /usr/bin/lynis reste intact." + } + ] + }, + "cli": { + "heading": "Options utiles CLI", + "intro": "Une fois installé, Lynis peut être invoqué directement à partir d'un shell avec des options supplémentaires non exposées par le menu:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Dépannage", + "cloneTitle": "git clone échoue lors de l'installation", + "cloneBody": "L'hôte a besoin d'un accès réseau et DNS pour atteindre github.com. Depuis une console : curl -sI https://github.com et git ls-remote https://github.com/CISOfy/lynis.git révèlent l'erreur réelle (DNS, TLS, proxy, repo URL). Si un proxy est requis, définissez https_proxy dans l'environnement avant de ré-exécuter le menu.", + "notFoundTitle": "lynis: commande introuvable après installation", + "notFoundIntro": "Le lanceur /usr/local/bin/lynis n'a pas été créé ou a été supprimé. La solution la plus rapide est de réinstaller depuis le menu. Correction manuelle :", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "L'audit affiche \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "Le démon SSH permet trop de tentatives d'auth par connexion. Soit installer Fail2Ban à partir de ProxMenux (qui définit MaxAuthTries=3 automatiquement) soit éditer /etc/ssh/sshd_config à la main:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "Le score a baissé après une mise à niveau Proxmox", + "scoreBody": "Exécutez lynis update info d'abord — une mise à jour de Lynis peut avoir ajouté de nouvelles commandes qui annoncent la configuration existante. Mettre à jour Lynis dans le menu, revérifier et traiter des nouvelles constatations." + }, + "files": { + "heading": "Fichiers écrits", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Exemple de rapport", + "intro": "ProxMenux Monitor produit pour chaque exécution de Lynis un PDF de plusieurs pages depuis l'onglet Sécurité. La première page résume score de durcissement, informations système et état de sécurité. Les suivantes détaillent les avertissements, les suggestions classées par impact et l'inventaire des paquets utilisé lors de l'audit.", + "imageAlt": "Première page du rapport d'audit de sécurité de Lynis PDF — résumé avec l'index de durcissement 71/100, bloc d'information système, et vue d'ensemble de la posture de sécurité avec pare-feu, scanner de malware et les paquets installés", + "captionPrefix": "Première page d'un exemple de rapport. Le PDF complet (", + "captionLink": "échantillon", + "captionSuffix": ") continue avec des avertissements détaillés, des suggestions et la liste des paquets installés.", + "cli": "Sur le côté CLI les mêmes données sont dans /var/log/lynis-report.dat (fichier plat lisible par machine) et /var/log/lynis.log (le journal d'exécution lisible par l'homme). Le PDF est généré sur demande par ProxMenux Monitor — l'exécution de lynis à partir de la ligne de commande n'en produit pas." + }, + "related": { + "heading": "Autres", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor → Onglet Sécurité", + "tail": "— exécuter l'audit, parcourir les rapports historiques et télécharger le PDF directement à partir du tableau de bord." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": "— met en œuvre la protection de la force brute SSH que recommande Lynis." + }, + { + "href": "/docs/security", + "label": "Aperçu de la sécurité", + "tail": "— retour à l'aperçu de la section." + } + ] + } +} diff --git a/web/messages/fr/docs/security/ssl-letsencrypt.json b/web/messages/fr/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..7ee2fe14 --- /dev/null +++ b/web/messages/fr/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS pour ProxMenux Monitor — certificat de l'hôte Proxmox ou chemins personnalisés | ProxMenux", + "description": "Servez ProxMenux Monitor en HTTPS avec le certificat de l'hôte Proxmox (détecté automatiquement, y compris le certificat Let’s Encrypt installé via ACME de Proxmox) ou un certificat personnalisé à un emplacement quelconque sur disque.", + "ogTitle": "HTTPS pour ProxMenux Monitor — Hôte Proxmox ou certificat personnalisé", + "ogDescription": "Activer HTTPS sur le port 8008 avec le certificat hôte Proxmox ou toute paire de .pem / .key personnalisée." + }, + "header": { + "title": "HTTPS pour ProxMenux Monitor", + "description": "Activez HTTPS pour Monitor sur le port 8008 avec le certificat de l'hôte Proxmox détecté automatiquement ou un certificat personnalisé sur disque. L'option Proxmox privilégie le certificat ACME, s'il existe, et réutilise Let’s Encrypt géré par Proxmox sans tâche de renouvellement supplémentaire.", + "section": "Sécurité" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Deux façons d'activer HTTPS", + "proxmox": { + "title": "Utiliser le certificat Proxmox", + "summary": "Un clic : Monitor réutilise le certificat présenté par Proxmox VE sur le port 8006.", + "items": [ + "Détecté automatiquement, aucun chemin à saisir", + "Privilégie le certificat ACME s'il existe, sinon le certificat autosigné par défaut", + "Proxmox renouvelle ; Monitor valide et charge le certificat à la prochaine nouvelle connexion TLS" + ] + }, + "custom": { + "title": "Utiliser un certificat personnalisé", + "summaryRich": "Pointez le moniteur sur une paire .pem / .key n'importe où sur le disque.", + "items": [ + "Chemins absolus seulement (le processus Monitor doit pouvoir lire les deux)", + "Utile lorsque les certificats proviennent de certbot, acme.sh ou d'un wildcard", + "Vous gérez le renouvellement : redémarrez Monitor après modification des fichiers" + ] + } + }, + "proxmoxCert": { + "heading": "Certificat Proxmox — ce qu'il indique en réalité à", + "intro": "Proxmox VE conserve deux paires de certificats dans /etc/pve/local/ :", + "table": { + "headers": { + "file": "Fichier", + "origin": "Origine", + "when": "Lorsque le moniteur l'utilise" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Certificat personnalisé téléchargé via l'interface utilisateur Proxmox — y compris le certificat Let's Encrypt émis sous Datacenter → Node → Certificats → ACME.", + "when": "Préféré si les deux fichiers existent ; Monitor utilise le certificat présenté par Proxmox." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Certificat autosigné généré au moment de l'installation par l'ICP Proxmox.", + "whenRich": "Certificat de repli en l'absence de pveproxy-ssl.pem." + } + ] + }, + "callout": { + "title": "Même priorité que Proxmox", + "bodyRich": "pveproxy applique la même sélection. Avec l'option Proxmox, Monitor utilise le certificat présenté par l'interface Proxmox sur 8006, sans copie ni processus de renouvellement séparé." + } + }, + "letsencrypt": { + "heading": "Obtenir un certificat Let's Encrypt via Proxmox ACME", + "intro": "Proxmox VE inclut son client ACME. « Order Certificate Now » enregistre le résultat dans /etc/pve/local/pveproxy-ssl.pem, lu par Monitor. Les cinq étapes suivantes résument le guide officiel Proxmox avec les commandes nécessaires.", + "prereqs": { + "title": "Préalables", + "items": [ + "Le noeud a un vrai nom DNS (pas seulement un IP) pointant dessus.", + "Soit le port 80 accessible depuis Internet (validation HTTP-01, la plus simple), soit un fournisseur DNS avec identifiants API (DNS-01, fonctionne derrière NAT et accepte les certificats génériques).", + "Une adresse email pour le compte ACME." + ] + }, + "step1": { + "heading": "Étape 1 — Enregistrer le compte ACME", + "introRich": "Remplacer le courriel par le vôtre. Le nom de compte default n'est qu'une étiquette — vous pouvez utiliser n'importe quelle chaîne.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "Proxmox vous invite à accepter les termes de service Let's Encrypt. Après cela, le compte est maintenu sous /etc/pve/priv/acme/ et l'onglet ACME dans l'interface graphique l'affichera." + }, + "step2": { + "heading": "Étape 2 — Choisir un type de défi", + "http01Rich": "HTTP-01 est le plus facile : il est intégré, pas de plugin supplémentaire. Let's Encrypt se connecte à http://<your-host>/.well-known/acme-challenge/... sur le port 80, de sorte que le port 80 doit être ouvert de l'Internet public à votre noeud — généralement un port vers l'avant sur votre routeur.", + "dns01Rich": "DNS-01 fonctionne sans exposer le port 80 (bon pour les homelabs derrière CG-NAT) et est la seule option pour les certificats wildcard. Vous ajoutez un plugin DNS une fois avec le jeton API de votre fournisseur. Exemple Cloudflare:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "Pour d'autres fournisseurs (Gandi, OVH, AWS Route 53, etc.), consultez pvenode acme plugin add dns --help. Proxmox inclut tous les plugins DNS acme.sh." + }, + "step3": { + "heading": "Étape 3 — Relier le domaine à votre nœud", + "http01Rich": "Pour HTTP-01 (utilise le plugin standalone intégré):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "Pour DNS-01 avec le plugin Cloudflare de l'étape 2 :", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Certificat générique ? Ajoutez domain=*.example.com dans une entrée séparée --acmedomainN. Les certificats génériques nécessitent DNS-01." + }, + "step4": { + "heading": "Étape 4 — Commander le certificat", + "code": "pvenode acme cert order", + "afterRich": "Le premier tour prend généralement 30–60 secondes pendant que le défi se propage. Sur succès Proxmox écrit /etc/pve/local/pveproxy-ssl.pem + .key, redémarre pveproxy, et une cron de renouvellement est programmée automatiquement (Let's Encrypt certs sont valides 90 jours; Proxmox renouvelle à 60)." + }, + "step5": { + "heading": "Étape 5 — Vérifier l'émetteur", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "devrait imprimer un émetteur commençant par O = Let's Encrypt. S'il lit plutôt CN = Proxmox Virtual Environment l'ordre a échoué et le fichier est toujours le certificat PVE autosigné — re-exécuter pvenode acme cert order --debug 1 pour voir pourquoi." + }, + "gui": { + "title": "Vous préférez l'interface utilisateur ? Mêmes cinq étapes", + "bodyRich": "Datacenter → ACME enregistre le compte ; Datacenter → ACME → Plugins ajoute le plugin DNS ; Node → Certificates → ACME associe le domaine ; Order Certificates Now exécute l'étape 4. Les commandes précédentes servent à automatiser et à travailler sans interface graphique." + } + }, + "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." + }, + "custom": { + "heading": "Certificat personnalisé — quand l'utiliser", + "intro": "Cliquez sur Utiliser un certificat personnalisé et saisissez deux chemins absolus :", + "items": [ + "Certificat — chaîne complète en PEM (.pem ou .crt).", + "Clé privée : clé correspondante en PEM (.key)." + ], + "outro": "Les deux fichiers sont lus par le processus Monitor au démarrage. Assurez-vous qu'ils restent lisibles par l'utilisateur que l'unité systemd fonctionne comme (root par défaut). Emplacements communs lorsque l'ACME est géré à l'extérieur de Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Symlink cibles vs fichiers", + "bodyRich": "certbot stocke les fichiers réels dans /etc/letsencrypt/archive/ et le chemin live/ est un lien symbolique. Pointez le moniteur sur le lien symbolique live/ et non sur le fichier archive/ — de cette façon, chaque cycle de renouvellement réécrit simplement la cible de lien symbolique et le moniteur récupère le nouveau certificat au début du service suivant." + } + }, + "afterHttps": { + "heading": "Après avoir activé HTTPS", + "bodyRich": "Enregistrer le changement écrit /etc/proxmenux/ssl_config.json et déclenche un redémarrage de l'unité proxmenux-monitor systemd. L'onglet navigateur perdra sa connexion actuelle — rechargez-la sur https://<host>:8008/. La première charge avec un certificat Let's Encrypt ne doit montrer aucun avertissement; avec le certificat Proxmox autosigné, le navigateur aura besoin d'une exception, la même que pour le port 8006.", + "reverse": { + "heading": "Proxies inverses et webhooks", + "bodyRich": "Lorsque le moniteur passe à HTTPS, les URL Proxmox VE webhook enregistrées pour les notifications retournent automatiquement de http:// à https://. Les entrées existantes de webhook sont réécrites la prochaine fois qu'une règle de notification est enregistrée ou redétectée, de sorte qu'aucune modification manuelle de la configuration de notification Proxmox n'est nécessaire." + } + }, + "trustCa": { + "heading": "Faites confiance à l'AC autosignée Proxmox", + "intro1Rich": "Pour conserver le certificat Proxmox par défaut sans ACME, importez l'AC racine du cluster dans le magasin de confiance du système ou du navigateur. Monitor détecte /etc/pve/local/pve-ssl.pem comme certificat de repli. Le client fait alors confiance à l'émetteur ; l'URL doit toujours correspondre à un nom du certificat.", + "intro2Rich": "Le fichier CA vit à /etc/pve/pve-root-ca.pem sur chaque nœud. Dans un cluster, le chemin est sur le système de fichiers partagé pmxcfs de sorte que tous les nœuds partagent la même racine, et une seule importation couvre l'ensemble du datacenter. Copiez-le sur votre machine :", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Importez-le du côté client :", + "items": [ + "Linux (tout le système) : exécutez sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt, puis sudo update-ca-certificates. Les navigateurs utilisant le magasin système (Chromium, Edge) la reconnaissent après redémarrage ; Firefox utilise son propre magasin et nécessite l'importation manuelle ci-dessous.", + "macOS : ouvrez le fichier .pem par double-clic dans Trousseaux d'accès, ajoutez-le au trousseau Système, puis choisissez Se fier → Toujours approuver.", + "Windows : clic droit sur le fichier → Installer le certificat → placez-le dans Autorités de certification racines de confiance.", + "Firefox (tout OS) : Paramètres → Vie privée et sécurité → Certificats → Afficher les certificats → Autorités → Importer ; cochez Faire confiance à cette AC pour identifier les sites web." + ], + "standalone": { + "title": "Nœuds autonomes en dehors d'un cluster", + "bodyRich": "Chaque hôte Proxmox non clustered possède son propre pve-root-ca.pem. Si vous gérez plusieurs hôtes autonomes, répétez la copie/import pour chacun, ou rejoignez-les à un cluster afin qu'ils partagent une seule racine." + } + }, + "disable": { + "heading": "Désactivation de HTTPS", + "bodyRich": "Cliquez sur Désactiver HTTPS dans le même panneau. Monitor redémarre en HTTP non chiffré, utile si un certificat personnalisé illisible empêche le démarrage. Aucune désinstallation supplémentaire n'est nécessaire ; les fichiers de certificats restent intacts.", + "stateCallout": { + "title": "Emplacement de l'état", + "bodyRich": "/etc/proxmenux/ssl_config.json stocke les chemins de cert actif et la source (none, proxmox, custom). La suppression du fichier équivaut à la désactivation de HTTPS — le prochain démarrage du moniteur revient à HTTP." + } + } +} diff --git a/web/messages/it/docs/security/fail2ban.json b/web/messages/it/docs/security/fail2ban.json new file mode 100644 index 00000000..f7823c8f --- /dev/null +++ b/web/messages/it/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | Documentazione ProxMenux", + "description": "Installa Fail2Ban con tre jails ottimizzati per Proxmox (SSH aggressivo, Proxmox UI 8006, ProxMenux Monitor 8008). Include la correzione journald MaxLevelStore, servizi di registro personalizzati per l'affidabilità, backend nftables/iptables rilevati automaticamente e SSH MaxAuthTries rafforzamento.", + "ogTitle": "Fail2Ban | Documentazione ProxMenux", + "ogDescription": "Protezione contro la forza bruta per SSH e le interfacce web Proxmox, con correzioni di journald e del backend specifiche per Proxmox." + }, + "header": { + "title": "Fail2Ban", + "description": "Installa Fail2Ban per Proxmox: tre jail (SSH aggressivo, interfaccia Proxmox su 8006, ProxMenux Monitor su 8008 e proxy inverso), correzione del livello journald per salvare gli eventi SSH, due servizi che copiano il journal in file per aggirare un problema del backend systemd, rilevamento del backend firewall e rafforzamento SSH con MaxAuthTries.", + "section": "Sicurezza" + }, + "intro": { + "title": "Cosa fa", + "body": "Installa Fail2Ban dai repository Debian e scrive una configurazione Proxmox che protegge subito SSH, interfaccia web Proxmox e ProxMenux Monitor. Se rileva una installazione esistente, offre invece un menu per reinstallare o rimuovere." + }, + "firstLaunch": { + "heading": "dialogo di primo lancio", + "body": "Se Fail2Ban non è installato, lo script mostra una conferma con ciò che installerà e configurerà. Annulla termina senza modifiche; confermare avvia l'installazione.", + "imageAlt": "Conferma di installazione di Fail2Ban con tre jail, correzione di journald e rafforzamento SSH" + }, + "jails": { + "heading": "Le tre jail", + "headerJail": "Jail", + "headerProtects": "Protegge", + "headerRetries": "Tentativi / Finestra", + "headerBan": "Durata del blocco", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (modalità aggressive — include ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 ore" + }, + { + "jail": "[proxmox]", + "protects": "Proxmox web UI (porta 8006)", + "retries": "3 / 10 min", + "ban": "1 ora" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (porta 8008 + proxy inverso http/https)", + "retries": "3 / 10 min", + "ban": "1 ora" + } + ], + "outro": "Impostazioni predefinite globali da jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (fallback di 24 ore per jails che non lo sovrascrivono), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Perché la correzione journald è importante", + "intro": "Proxmox fornisce /etc/systemd/journald.conf con MaxLevelStore=warning. journald scarta i messaggi di livello inferiore a warning prima di salvarli. SSH e PAM segnalano gli errori di autenticazione ai livelli info / notice, quindi:", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "errore di autenticazione\n(livello info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ evento scartato senza avviso", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "non vede niente\n→ non vieta mai nulla", + "arrowLabel": "Proxmox predefinito" + }, + "afterDiagram": "L'installatore rileva questo e scrive un drop-in a /etc/systemd/journald.conf.d/proxmenux-loglevel.conf aumentando sia MaxLevelStore che MaxLevelSyslog a info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Poi riavvia systemd-journald. Il drop-in viene rimosso su disinstallazione, ripristinando il default Proxmox originale." + }, + "loggers": { + "heading": "Perché due servizi logger personalizzati", + "intro1": "Fail2Ban può leggere direttamente il journal systemd (backend = systemd), ma su Proxmox questo backend ha problemi noti con i processi worker di pvedaemon: gli eventi di autenticazione compaiono nel journal ma non sempre vengono rilevati. Il problema riguarda occasionalmente anche sshd.", + "intro2": "ProxMenux crea due piccoli servizi systemd che seguono le unità interessate con journalctl -f e aggiungono ogni riga a un file. Fail2Ban legge questi file con il backend affidabile backend = auto (modalità file):", + "headerService": "Servizio", + "headerSource": "Unità di origine", + "headerOutput": "File di uscita", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Entrambi i servizi sono dichiarati PartOf=fail2ban.service in modo da riavviare con Fail2Ban e fermarsi con esso. Modalità 640 di proprietà di root:adm sui file di registro. Il terzo registro utilizzato dalla prigione [proxmenux] (/var/log/proxmenux-auth.log) è scritto direttamente dall'app ProxMenux Monitor Flask — nessun servizio logger necessario per questo." + }, + "backend": { + "heading": "Auto-detezione backend Firewall", + "intro": "L'installatore sonda l'host: se nft list ruleset funziona, sceglie nftables come l'azione di divieto. In caso contrario torna a iptables-multiport / iptables-allports. La scelta è scritta in jail.local:", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "Rafforzamento SSH: MaxAuthTries", + "intro": "Il controllo Lynis SSH-7408 raccomanda MaxAuthTries 3 in sshd_config. La jail SSH usa maxretry = 2 per bloccare dopo errori ripetuti; MaxAuthTries limita i tentativi nella stessa connessione. Questa impostazione aggiunge protezione anche quando Fail2Ban è fermo per manutenzione.", + "installerIntro": "L'installatore:", + "items": [ + "Legge il valore attuale di MaxAuthTries (o di default a 6 se assente).", + "Lo salva a /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Modifica sshd_config direttamente: sostituisce la riga esistente, rimuove il commento dalla riga commentata o ne aggiunge una nuova.", + "Ricarica sshd senza riavviarlo, mantenendo attive le sessioni esistenti." + ], + "outro": "Su disinstallazione, il valore originale salvato viene ripristinato e sshd viene ricaricato di nuovo." + }, + "manage": { + "heading": "Gestire un'installazione esistente", + "intro": "Se Fail2Ban è già installato, lo script lo rileva e mostra un menu di gestione invece di ripetere l'installazione.", + "headerAction": "Azione", + "headerWhat": "Che cosa fa", + "rows": [ + { + "action": "Reinstallare", + "what": "Esegue nuovamente l'installazione completa e riscrive tutte le jail con i valori attuali di ProxMenux. Usalo quando un aggiornamento modifica le raccomandazioni." + }, + { + "action": "Rimuovi", + "what": "Arresta fail2ban e i due servizi di logger, elimina il pacchetto apt, rimuove tutti i jail / file filtro, elimina il drop-in journald (ripristinando le impostazioni predefinite di Proxmox) e ripristina il SSH originale MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Verifica che funzioni.", + "intro": "Dopo l'installazione, comandi utili dall'host:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "neverBansTitle": "Fail2Ban corre ma non vieta mai nulla", + "neverBansBody": "Verifica che tail -f /var/log/ssh-auth.log riceva una voce dopo un tentativo SSH con password errata da un'altra macchina. Se rimane vuoto, controlla systemctl status ssh-auth-logger.service. Se il logger è attivo, consulta gli eventi recenti con journalctl -u ssh.service -n 50 --no-pager -p info e verifica che il drop-in journald consenta il livello info.", + "monitorEmptyTitle": "ProxMenux Monitor jail non ha voci anche dopo accessi non riusciti", + "monitorEmptyBody": "Il [proxmenux] jail legge /var/log/proxmenux-auth.log, che è scritto dall'app ProxMenux Monitor Flask - non da un logger journald. Se non esegui il Monitor, il file rimane vuoto e il jail non si attiva mai. Questo è previsto; la configurazione jail è innocua. Se esegui il Monitor e il registro è vuoto, controlla la configurazione di registrazione Flask.", + "selfBanTitle": "Ho bloccato il mio stesso accesso", + "selfBanIntro": "Da una console / IPMI / iKVM (o da un altro IP whitelist):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "apt-get non riesce: ''Unable to locate package fail2ban''", + "aptFailBody": "L'host manca il repos Debian (comune sulle installazioni di Barebones Proxmox). L'installatore rileva questo e scrive /etc/apt/sources.list.d/debian.sources con il nome giusto in codice (bookworm / trixie) prima di riprovare. Se non riesce ancora, controllare /etc/os-release per VERSION_CODENAME e confermare che l'URL repo è raggiungibile.", + "lockoutTitle": "SSH mi blocca dopo l'installazione", + "lockoutBody": "L'installatore imposta MaxAuthTries=3. Se il tuo password manager / agente richiama più chiavi, puoi superarlo su un singolo tentativo di connessione. Limitare le chiavi offerte: ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. O sollevare temporaneamente MaxAuthTries in sshd_config mentre si debug." + }, + "files": { + "heading": "File scritti", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Correlati", + "monitorLabel": "ProxMenux Monitor → Scheda di sicurezza", + "monitorTail": " — installazione dal pannello, stato delle jail in tempo reale, IP bloccati e regolazione di tentativi e durata del blocco per ogni jail dal browser.", + "lynisLabel": "Lynis", + "lynisTail": " — esegui un audit prima e dopo per verificare che il controllo SSH-7408 sia soddisfatto.", + "securityLabel": "Panoramica della sicurezza", + "securityTail": "— di nuovo alla panoramica della sezione." + } +} diff --git a/web/messages/it/docs/security/index.json b/web/messages/it/docs/security/index.json new file mode 100644 index 00000000..2f7f44be --- /dev/null +++ b/web/messages/it/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Sicurezza Proxmox — Fail2Ban e audit di rafforzamento Lynis | ProxMenux", + "description": "Due strumenti di sicurezza installabili per Proxmox VE: Fail2Ban (prevenzione delle intrusioni per SSH, interfaccia web Proxmox e ProxMenux Monitor) e Lynis (analisi della sicurezza e del rafforzamento del sistema, installato dal repository ufficiale GitHub).", + "ogTitle": "Sicurezza Proxmox — Fail2Ban e audit di rafforzamento Lynis", + "ogDescription": "Fail2Ban e Lynis per Proxmox VE: prevenzione delle intrusioni e audit di rafforzamento, installati e gestiti da ProxMenux.", + "twitterTitle": "Sicurezza Proxmox | ProxMenux", + "twitterDescription": "Fail2Ban e Lynis per Proxmox VE: prevenzione delle intrusioni e audit di rafforzamento." + }, + "header": { + "title": "Sicurezza", + "description": "Due strumenti complementari per Proxmox VE: difesa attiva (Fail2Ban blocca gli IP che attaccano SSH o le interfacce web) e audit locale (Lynis cerca lacune nel rafforzamento e assegna un punteggio da 0 a 100). Ognuno ha un menu di installazione e gestione che rileva installazioni esistenti prima di ogni azione.", + "section": "Sicurezza" + }, + "intro": { + "title": "Difesa attiva e audit locale", + "body": "Rispondono a domande diverse. Fail2Ban risponde a «Ci stanno attaccando ora?» monitorando i log di autenticazione e bloccando gli IP responsabili. Lynis risponde a «Quanto è rafforzata la sicurezza di questo host?» con un audit singolo e raccomandazioni concrete. Usali insieme: Lynis indica cosa migliorare e Fail2Ban reagisce agli attacchi in corso." + }, + "opening": { + "heading": "Apertura del menu", + "body": "Dal menu principale di ProxMenux seleziona Sicurezza. Vedrai questo:", + "imageAlt": "Menu di sicurezza con opzioni Fail2Ban e Lynis" + }, + "pick": { + "heading": "Scegli il tuo strumento", + "body": "I due strumenti di sicurezza sono indipendenti — installare uno, entrambi, o nessuno. Ogni scheda qui sotto salta alla sezione che spiega lo strumento in dettaglio." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Prevenzione attiva delle intrusioni. Controlla i log di autenticazione SSH e web e blocca gli IP dopo errori ripetuti.", + "bullets": [ + "Tre jails: SSH, Proxmox UI (8006), ProxMenux Monitor (8008)", + "Corregge le impostazioni predefinite di Proxmox journald che bloccano i registri di autenticazione", + "Rilevamento automatico nftables / iptables", + "Indurimento SSH: MaxAuthTries=3 (Consiglio di Lynis)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Auditor locale di sicurezza. Analizza l'host e mostra un punteggio di rafforzamento e suggerimenti concreti per correggere i problemi.", + "bullets": [ + "Installato dal repository ufficiale CISOfy su GitHub (versione più recente)", + "Punteggio di rafforzamento 0–100 ed elenco di avvisi e suggerimenti", + "Audit e aggiornamento dal menu", + "Progettato in sola lettura: non modifica mai il sistema" + ] + } + }, + "workflowTip": { + "title": "Flusso di lavoro che utilizza entrambi", + "body": "Esegui prima Lynis per vedere il punteggio iniziale e le raccomandazioni principali. Applica le correzioni desiderate. Poi installa Fail2Ban: implementa automaticamente la protezione SSH contro la forza bruta e regola MaxAuthTries in sshd_config per soddisfare il controllo SSH-7408. Esegui di nuovo Lynis per verificare il miglioramento." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "ProxMenux installa Fail2Ban con una configurazione specifica per Proxmox. Oltre alla jail SSH standard protegge l'interfaccia web Proxmox e ProxMenux Monitor. Aggira due problemi specifici: un'impostazione journald che scarta gli eventi di autenticazione e un problema del backend systemd che impedisce la lettura affidabile di alcune fonti del registro. La pagina dettagliata spiega installazione, tre jail, correzione di journald, rafforzamento SSH e risoluzione dei problemi.", + "optionTitle": "Fail2Ban — installazione e gestione", + "optionDescription": "Tre jail preconfigurate (sshd aggressivo, interfaccia Proxmox su 8006, ProxMenux Monitor su 8008 e proxy inverso), rilevamento di nftables/iptables, correzione di journald e rafforzamento SSH con MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "ProxMenux clona Lynis da github.com/CISOfy/lynis in /opt/lynis e lo rende disponibile come /usr/local/bin/lynis. Evita il pacchetto Debian perché è indietro di diverse versioni principali rispetto al progetto originale. La pagina dettagliata spiega installazione, audit, aggiornamento, disinstallazione, lettura del rapporto e azioni sui risultati.", + "optionTitle": "Lynis — installazione, audit e gestione", + "optionDescription": "Installa l'ultimo Lynis dal repo ufficiale CISOfy GitHub (non il vecchio pacchetto Debian), esegue controlli di sistema con punteggio di indurimento e aggiornamenti tramite git pull." + }, + "componentStatus": { + "heading": "Stato del componente", + "body": "Entrambi gli installatori registrano il loro stato in /usr/local/share/proxmenux/components_status.json sotto la categoria security. ProxMenux utilizza questo file per decidere se mostrare l'installazione o il menu di gestione delle operazioni successive, e gli stessi dati alimentano le dashboard nel ProxMenux Monitor quando presente." + } +} diff --git a/web/messages/it/docs/security/lynis.json b/web/messages/it/docs/security/lynis.json new file mode 100644 index 00000000..bf23305b --- /dev/null +++ b/web/messages/it/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | Documentazione ProxMenux", + "description": "Installa la versione più recente di Lynis dal repository ufficiale CISOfy su GitHub, esegui un audit con punteggio di rafforzamento 0–100 e aggiorna tramite git pull. Auditor in sola lettura: non modifica il sistema.", + "ogTitle": "Lynis | Documentazione ProxMenux", + "ogDescription": "Installa ed esegui Lynis su Proxmox VE. Installazione dal repository originale GitHub, audit e aggiornamento." + }, + "header": { + "title": "Lynis", + "description": "Clona la versione più recente di Lynis dal repository ufficiale CISOfy su GitHub, la rende disponibile come /usr/local/bin/lynis e offre audit, aggiornamento, reinstallazione e disinstallazione dal menu. L'analisi è in sola lettura: produce rapporti senza modificare il sistema.", + "section": "Sicurezza" + }, + "intro": { + "title": "Cosa fa", + "body": "Installa Lynis, auditor di sicurezza open source di CISOfy, dal repository GitHub in /opt/lynis e crea un lanciatore in /usr/local/bin/lynis per renderlo disponibile nel PATH. Se rileva una installazione esistente, mostra un menu per audit, aggiornamento, reinstallazione o rimozione." + }, + "manageMenu": { + "heading": "Menu di gestione (dopo l'installazione)", + "intro": "Una volta che Lynis è installato, ogni successiva invocazione apre il menu di gestione invece di rifare l'installatore. Da qui si avvia un audit, si aggiorna tramite git pull, reinstalla o disinstalla:", + "imageAlt": "Menù di gestione Lynis con controllo, aggiornamento, reinstallare e rimuovere le opzioni" + }, + "whyUpstream": { + "heading": "Perché il repository ufficiale GitHub invece di apt", + "intro": "Debian distribuisce Lynis tramite apt, ma il pacchetto è spesso indietro di diverse versioni principali. Nuovi controlli, correzioni per nuovi vettori di attacco e raccomandazioni migliorate arrivano prima su GitHub. Lynis è uno script shell autonomo, senza compilazione e senza dipendenze di installazione oltre a git; clonare il repository è quindi il metodo raccomandato da CISOfy.", + "headerSource": "Fonte", + "headerPath": "Install path", + "headerUpdate": "Metodo di aggiornamento", + "headerFresh": "Aggiornamento della versione", + "rows": [ + { + "sourceRich": "ProxMenux (questo script)", + "path": "/opt/lynis/", + "update": "git pull (dal menu)", + "fresh": "Ultima versione del progetto originale" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Spesso mesi / anni dietro" + } + ] + }, + "install": { + "heading": "Come funziona l'installazione", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(installa prima git se manca)", + "node2Label": "/opt/lynis/", + "node2Detail": "repository completo\nincluso lo script ./lynis", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "wrapper:\ncd /opt/lynis && ./lynis $@", + "outro": "Il wrapper è obbligatorio — Lynis insiste per essere eseguito dalla propria directory perché carica percorsi relativi per plugin e dati del profilo. L'involucro nasconde quel dettaglio così lynis audit system funziona da qualsiasi luogo." + }, + "detection": { + "heading": "Sentieri di rilevamento", + "intro": "Prima di mostrare il menu, lo script controlla tre posizioni per decidere se Lynis è già presente:", + "items": [ + "/usr/local/bin/lynis — wrapper installato da ProxMenux", + "/opt/lynis/lynis — percorso diretto (nel caso in cui l'involucro sia stato rimosso)", + "/usr/bin/lynis — versione apt-installata, se l'utente l'ha installato in quel modo prima" + ], + "outro": "Se trova uno di questi percorsi apre il menu di gestione. Lo script non disinstalla Lynis installato con apt; rimuove solo quello gestito in /opt/lynis e il relativo lanciatore." + }, + "audit": { + "heading": "Eseguire un audit", + "intro": "Dal menu di gestione scegli Esegui audit di sicurezza ora. Equivale a eseguire:", + "code": "lynis audit system --no-colors", + "outro": "L'audit richiede da 30 secondi ad alcuni minuti secondo le dimensioni dell'host. Lo output viene mostrato direttamente nel terminale, senza animazione di attesa. Lynis organizza i controlli per categoria e marca ogni test come [ OK ], [ WARNING ] o [ SUGGESTION ]. Il riepilogo finale mostra i dati principali:", + "summary": "================================================================================\n\n Dettagli della scansione di sicurezza Lynis:\n\n Indice di rafforzamento : 76 [############ ]\n Test eseguiti : 247\n Plugin abilitati : 0\n\n Componenti:\n - Firewall [V]\n - Scanner malware [X]\n\n Moduli Lynis:\n - Stato di conformità [?]\n - Audit di sicurezza [V]\n - Scansione vulnerabilità [V]\n\n================================================================================" + }, + "report": { + "heading": "Leggere il rapporto", + "intro": "Le due sezioni importanti sono Warnings (le cose che si devono risolvere presto) e Suggestions (raccomandazioni per migliorare l'indurimento). Ogni elemento porta un ID di controllo come SSH-7408 — utile quando si cerca i documenti Lynis per la razionalità e la correzione.", + "headerMarker": "Marcatore", + "headerMeaning": "Significato", + "headerAction": "Azione", + "rows": [ + { + "markerRich": "OK", + "meaning": "Test superato", + "action": "Niente." + }, + { + "markerRich": "WARNING", + "meaning": "Problema reale, fix consigliato", + "action": "Leggi la descrizione del controllo, pianifica una correzione" + }, + { + "markerRich": "SUGGESTION", + "meaning": "Miglioramento dell'indurimento disponibile", + "action": "Applica se adatto al modello di minaccia" + } + ], + "outro": "Il rapporto completo e i dati leggibili dalla macchina sono scritti a /var/log/lynis.log e /var/log/lynis-report.dat da Lynis stesso." + }, + "pairFail2ban": { + "title": "Combina con Fail2Ban", + "body": "SSH-7408 (MaxAuthTries) è uno degli avvisi più comuni. Installazione di Fail2Ban da ProxMenux lo sgombera automaticamente perché l'installatore imposta MaxAuthTries=3 come parte della sua fase di indurimento SSH. Eseguire Lynis di nuovo dopo per confermare che l'avvertimento è andato." + }, + "update": { + "heading": "Aggiornamento Lynis", + "body": "Nel menu, Aggiorna Lynis all'ultima versione esegue git pull --quiet in /opt/lynis. Se la directory esiste ma non è una copia di lavoro Git, per esempio dopo una copia manuale dei file, lo script esegue una reinstallazione completa." + }, + "reinstall": { + "heading": "Reinstallare / disinstallare", + "headerAction": "Azione", + "headerWhat": "Che cosa fa", + "rows": [ + { + "actionRich": "Reinstalla", + "whatRich": "Rimuove /opt/lynis, clona nuovamente da GitHub e ricrea il lanciatore. Usalo se la copia locale è danneggiata." + }, + { + "actionRich": "Rimuovi", + "whatRich": "Elimina /opt/lynis e /usr/local/bin/lynis. I registri a /var/log/lynis* sono mantenuti (sono storia dell'audit). Un Lynis apt-installed a /usr/bin/lynis è lasciato intatto." + } + ] + }, + "cli": { + "heading": "Opzioni utili CLI", + "intro": "Una volta installato, Lynis può essere invocata direttamente da una shell con opzioni extra non esposte dal menu:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Risoluzione dei problemi", + "cloneTitle": "git clone non riesce durante l'installazione", + "cloneBody": "L'host ha bisogno di accesso alla rete e DNS per raggiungere github.com. Da una console: curl -sI https://github.com e git ls-remote https://github.com/CISOfy/lynis.git riveleranno l'errore effettivo (DNS, TLS, proxy, repo URL). Se è richiesto un proxy, impostare https_proxy nell'ambiente prima di ri-inviare il menu.", + "notFoundTitle": "lynis: comando non trovato dopo l'installazione", + "notFoundIntro": "L'involucro a /usr/local/bin/lynis non è stato creato o rimosso. Correzione più rapida: reinstallare dal menu. Correzione manuale:", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "L'audit mostra \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "Il daemon SSH consente troppi tentativi auth per connessione. Installare Fail2Ban da ProxMenux (che imposta automaticamente MaxAuthTries=3) o modificare /etc/ssh/sshd_config a mano:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "Score è andato giù dopo un aggiornamento Proxmox", + "scoreBody": "Eseguire lynis update info prima — un aggiornamento Lynis potrebbe aver aggiunto nuovi controlli che contrassegnano la configurazione esistente. Aggiornare Lynis dal menu, ri-audit e affrontare i nuovi risultati." + }, + "files": { + "heading": "File scritti", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Rapporto di esempio", + "intro": "ProxMenux Monitor genera per ogni esecuzione di Lynis un PDF multipagina dalla scheda Sicurezza. La prima pagina riepiloga punteggio di rafforzamento, informazioni di sistema e stato della sicurezza. Le successive spiegano ogni avviso, elencano i suggerimenti per impatto e includono l'inventario dei pacchetti usato nell'audit.", + "imageAlt": "Prima pagina del Lynis Security Audit Report PDF — Riepilogo esecutivo con Hardening Index 71/100, blocco informazioni di sistema e panoramica della postura di sicurezza con firewall, malware scanner e pacchetti installati conteggio", + "captionPrefix": "Prima pagina di un rapporto campione. Il PDF completo (", + "captionLink": "campione", + "captionSuffix": ") continua con avvisi dettagliati, suggerimenti e la lista dei pacchetti installati.", + "cli": "Sul lato CLI gli stessi dati sono in /var/log/lynis-report.dat (file piatto leggibile dalla macchina) e /var/log/lynis.log (il registro di esecuzione leggibile dall'uomo). Il PDF viene generato su richiesta da ProxMenux Monitor — l'esecuzione di lynis dalla riga di comando non ne produce uno." + }, + "related": { + "heading": "Correlati", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor → Scheda di sicurezza", + "tail": "— eseguire l'audit, sfogliare i rapporti storici e scaricare il PDF direttamente dal cruscotto." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": " — attua il SSH protezione brute-force che Lynis raccomanda." + }, + { + "href": "/docs/security", + "label": "Panoramica della sicurezza", + "tail": "— di nuovo alla panoramica della sezione." + } + ] + } +} diff --git a/web/messages/it/docs/security/ssl-letsencrypt.json b/web/messages/it/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..19f7938b --- /dev/null +++ b/web/messages/it/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS per ProxMenux Monitor — certificato dell'host Proxmox o percorsi personalizzati | ProxMenux", + "description": "Fornisce ProxMenux Monitor via HTTPS con il certificato dell'host Proxmox (rilevato automaticamente, incluso quello Let’s Encrypt installato tramite ACME di Proxmox) o con un certificato personalizzato in qualsiasi percorso sul disco.", + "ogTitle": "HTTPS per ProxMenux Monitor — Host Proxmox o certificato personalizzato", + "ogDescription": "Abilitare HTTPS sulla porta 8008 con il certificato host Proxmox o qualsiasi coppia di .pem / .key personalizzata." + }, + "header": { + "title": "HTTPS per ProxMenux Monitor", + "description": "Attiva HTTPS per Monitor sulla porta 8008 con il certificato dell'host Proxmox rilevato automaticamente o uno personalizzato sul disco. L'opzione Proxmox preferisce il certificato ACME, se presente, e riutilizza Let’s Encrypt gestito da Proxmox senza un'ulteriore attività di rinnovo.", + "section": "Sicurezza" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Due modi per abilitare HTTPS", + "proxmox": { + "title": "Usa certificato Proxmox", + "summary": "Un clic: Monitor riutilizza il certificato presentato da Proxmox VE sulla porta 8006.", + "items": [ + "Rilevato automaticamente, nessun percorso da digitare", + "Preferisce il certificato ACME se presente, altrimenti quello autofirmato predefinito", + "Proxmox gestisce il rinnovo; Monitor convalida e carica il certificato alla prossima nuova connessione TLS" + ] + }, + "custom": { + "title": "Usa certificato personalizzato", + "summaryRich": "Puntare il Monitor su una coppia .pem / .key ovunque sul disco.", + "items": [ + "Solo percorsi assoluti (il processo Monitor deve essere in grado di leggere entrambi)", + "Utile quando i cert vengono da certbot, acme.sh o una wildcard", + "Gestisci tu il rinnovo: riavvia Monitor quando cambiano i file" + ] + } + }, + "proxmoxCert": { + "heading": "Certificato di Proxmox — ciò che effettivamente indica", + "intro": "Proxmox VE mantiene due coppie di certificati in /etc/pve/local/:", + "table": { + "headers": { + "file": "File", + "origin": "Origine", + "when": "Quando il Monitor lo usa" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Certificato personalizzato caricato tramite l'UI Proxmox — compreso il certificato Let's Encrypt rilasciato sotto Datacenter → Nodo → Certificati → ACME.", + "when": "Preferito se entrambi i file esistono; Monitor usa il certificato presentato da Proxmox." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Certificato autofirmato generato al momento di installazione dal Proxmox PKI.", + "whenRich": "Fallback quando non è presente nessun pveproxy-ssl.pem." + } + ] + }, + "callout": { + "title": "Stessa priorità di Proxmox stesso", + "bodyRich": "pveproxy applica la stessa selezione: scegliendo Proxmox, Monitor usa il certificato presentato dall'interfaccia Proxmox su 8006, senza copie né un processo di rinnovo separato." + } + }, + "letsencrypt": { + "heading": "Ottenere un certificato Let's Encrypt tramite Proxmox ACME", + "intro": "Proxmox VE include un client ACME. «Order Certificate Now» salva il risultato in /etc/pve/local/pveproxy-ssl.pem, letto da Monitor. I cinque passi seguenti riassumono la guida ufficiale Proxmox nei comandi necessari.", + "prereqs": { + "title": "Prerequisiti", + "items": [ + "Il nodo ha un vero nome DNS (non solo un IP) che indica.", + "O porta 80 raggiungibile da Internet (verifica HTTP-01, la più semplice), oppure un provider DNS con credenziali API (DNS-01, funziona dietro NAT e supporta wildcard).", + "Un indirizzo email per l'account ACME." + ] + }, + "step1": { + "heading": "Passo 1 — Registrare l'account ACME", + "introRich": "Sostituisci l'email con il tuo. Il nome dell'account default è solo un'etichetta — è possibile utilizzare qualsiasi stringa.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "Proxmox chiede di accettare i termini di servizio Let's Encrypt. Dopo di che l'account è persistito sotto /etc/pve/priv/acme/ e la scheda ACME nella GUI lo mostrerà." + }, + "step2": { + "heading": "Passo 2 — Scegli un tipo di sfida", + "http01Rich": "HTTP-01 è il più semplice: è costruito in, nessun plugin aggiuntivo. Let's Encrypt si collega a http://<your-host>/.well-known/acme-challenge/... sulla porta 80, quindi la porta 80 deve essere aperta da internet pubblico al nodo — tipicamente una porta avanti sul router.", + "dns01Rich": "DNS-01 funziona senza esporre la porta 80 (buona per le homelab dietro CG-NAT) ed è l'unica opzione per i certificati jolly. Aggiungi un plugin DNS una volta con il token API del tuo fornitore. Esempio Cloudflare:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "Per altri provider (Gandi, OVH, AWS Route 53, ecc.) consulta pvenode acme plugin add dns --help. Proxmox include tutti i plugin DNS di acme.sh." + }, + "step3": { + "heading": "Passo 3 — Legare il dominio al nodo", + "http01Rich": "Per HTTP-01 (utilizza il plugin standalone integrato):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "Per DNS-01 con il plugin Cloudflare di Step 2:", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Certificato wildcard? Aggiungi domain=*.example.com in una voce separata --acmedomainN. I certificati wildcard richiedono DNS-01." + }, + "step4": { + "heading": "Passo 4 — Ordinare il certificato", + "code": "pvenode acme cert order", + "afterRich": "La prima esecuzione richiede tipicamente 30–60 secondi mentre la sfida si propaga. Su successo Proxmox scrive /etc/pve/local/pveproxy-ssl.pem + .key, riavvia pveproxy, e un cron di rinnovo è programmato automaticamente (Let's Encrypt certs sono validi 90 giorni; Proxmox rinnova a 60)." + }, + "step5": { + "heading": "Passo 5 — Verificare l'emittente", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "Dovrebbe stampare un emittente a partire da O = Let's Encrypt. Se invece legge CN = Proxmox Virtual Environment l'ordine non è riuscito e il file è ancora l'auto-firmato PVE cert — eseguire pvenode acme cert order --debug 1 per vedere perché." + }, + "gui": { + "title": "Preferisci la GUI? Stessi cinque passi", + "bodyRich": "Datacenter → ACME registra l'account; Datacenter → ACME → Plugins aggiunge il plugin DNS; Node → Certificates → ACME associa il dominio; Order Certificates Now esegue il passo 4. I comandi precedenti sono utili per script e sistemi senza interfaccia grafica." + } + }, + "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." + }, + "custom": { + "heading": "Certificato personalizzato — quando utilizzarlo", + "intro": "Fai clic su Usa certificato personalizzato e inserisci due percorsi assoluti:", + "items": [ + "Certificato: catena completa in PEM (.pem o .crt).", + "Chiave privata: chiave corrispondente in PEM (.key)." + ], + "outro": "Entrambi i file sono letti dal processo Monitor all'avvio. Assicurarsi che rimangano leggibili dall'utente l'unità systemd viene eseguita come (root per impostazione predefinita). Località comuni quando ACME è gestito fuori Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Obiettivi Symlink vs. file", + "bodyRich": "certbot memorizza i file reali in /etc/letsencrypt/archive/ e il percorso live/ è un symlink. Indirizzi il Monitor al simlink live/, non il file archive/ — in questo modo ogni ciclo di rinnovo semplicemente riscrive l'obiettivo di symlink e il Monitor raccoglie il nuovo cert all'inizio del servizio successivo." + } + }, + "afterHttps": { + "heading": "Dopo aver attivato HTTPS", + "bodyRich": "Salvataggio del cambiamento scrive /etc/proxmenux/ssl_config.json e attiva un riavvio dell'unità proxmenux-monitor systemd. La scheda del browser perderà la sua connessione corrente — ricaricarla su https://<host>:8008/. Il primo carico con un cert Let's Encrypt non dovrebbe mostrare alcun avvertimento; con il Proxmox auto-firmato cert il browser richiederà un'eccezione, la stessa che per la porta 8006.", + "reverse": { + "heading": "Proxy inversi e webhook", + "bodyRich": "Quando il Monitor passa a HTTPS, gli URL Proxmox VE webhook registrati per le notifiche girano automaticamente da http:// a https://. Le voci webhook esistenti vengono riscritte la prossima volta che viene salvata o ri-detetta una regola di notifica, quindi non è necessaria alcuna modifica manuale della configurazione di notifica Proxmox." + } + }, + "trustCa": { + "heading": "Considerare attendibile la CA autofirmata di Proxmox", + "intro1Rich": "Per mantenere il certificato predefinito Proxmox senza ACME, importa la CA radice del cluster nell'archivio di fiducia del sistema o del browser. Monitor rileva /etc/pve/local/pve-ssl.pem come certificato alternativo. Il client considera attendibile l'emittente; l'URL deve comunque corrispondere a un nome del certificato.", + "intro2Rich": "Il file CA vive a /etc/pve/pve-root-ca.pem su ogni nodo. In un cluster il percorso è sul filesystem pmxcfs condiviso in modo che tutti i nodi condividono la stessa radice, e una singol'importazione copre l'intero datacenter. Copialo sulla tua macchina:", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Poi importarlo sul lato cliente:", + "items": [ + "Linux (intero sistema): esegui sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt e poi sudo update-ca-certificates. I browser che usano l'archivio di sistema (Chromium, Edge) riconoscono la CA dopo il riavvio; Firefox usa un archivio proprio e richiede l'importazione manuale seguente.", + "macOS: apri il file .pem con doppio clic in Accesso Portachiavi, aggiungilo al portachiavi Sistema e imposta Attendibilità → Considera sempre attendibile.", + "Windows: clic destro sul file → Installa certificato → inseriscilo in Autorità di certificazione radice attendibili.", + "Firefox (qualsiasi sistema operativo): Impostazioni → Privacy e sicurezza → Certificati → Mostra certificati → Autorità → Importa; seleziona Considera attendibile questa CA per identificare siti web." + ], + "standalone": { + "title": "Nodi standalone fuori da un cluster", + "bodyRich": "Ogni host Proxmox non incluso ha il proprio pve-root-ca.pem. Se gestisci diversi host standalone, ripeti la copia/importazione per ciascuno, o unisciti a un cluster in modo da condividere una singola radice." + } + }, + "disable": { + "heading": "Disabilitare HTTPS", + "bodyRich": "Fai clic su Disabilita HTTPS nello stesso pannello. Monitor si riavvia con HTTP non cifrato, utile se un certificato personalizzato non è leggibile e il servizio non parte. Non serve una disinstallazione separata e i file di certificato non vengono modificati.", + "stateCallout": { + "title": "Dove viene salvato lo stato", + "bodyRich": "/etc/proxmenux/ssl_config.json memorizza i percorsi di cert attivo e la fonte (none, proxmox, custom). Cancellare il file è equivalente a disabilitare HTTPS — il prossimo inizio il Monitor rientra a HTTP." + } + } +} diff --git a/web/messages/pt/docs/security/fail2ban.json b/web/messages/pt/docs/security/fail2ban.json new file mode 100644 index 00000000..2c4c86f6 --- /dev/null +++ b/web/messages/pt/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | Documentação do ProxMenux", + "description": "Instalar Fail2Ban com três jails sintonizado para Proxmox (SSH agressivo, Proxmox UI 8006, ProxMenux Monitor 8008). Inclui o journald MaxLevelStore correção, serviços de log personalizados para confiabilidade, backend nftables/iptables detectado automaticamente e SSH MaxAuthTries endurecimento.", + "ogTitle": "Fail2Ban | Documentação do ProxMenux", + "ogDescription": "Proteção contra força bruta para SSH e interfaces web Proxmox, com correções do journald e do backend específicas para Proxmox." + }, + "header": { + "title": "Fail2Ban", + "description": "Instala Fail2Ban para Proxmox: três jails (SSH agressivo, interface Proxmox em 8006, ProxMenux Monitor em 8008 e proxy inverso), correção do nível do journald para guardar eventos SSH, dois serviços que copiam o journal para ficheiros para contornar um problema do backend systemd, deteção do backend de firewall e reforço SSH com MaxAuthTries.", + "section": "Segurança" + }, + "intro": { + "title": "O que isto faz", + "body": "Instala Fail2Ban dos repositórios Debian e cria uma configuração Proxmox que protege de imediato SSH, interface web Proxmox e ProxMenux Monitor. Se detetar uma instalação existente, oferece um menu de gestão para reinstalar ou remover." + }, + "firstLaunch": { + "heading": "Janela de primeiro lançamento", + "body": "Sem Fail2Ban instalado, o script resume num diálogo o que vai instalar e configurar. Cancelar termina sem alterações; confirmar inicia a instalação.", + "imageAlt": "Confirmação de instalação do Fail2Ban com três jails, correção do journald e reforço SSH" + }, + "jails": { + "heading": "As três jails", + "headerJail": "Jail", + "headerProtects": "Protege", + "headerRetries": "Tentativas / Janela", + "headerBan": "Duração do bloqueio", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (modo aggressive — inclui ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 horas" + }, + { + "jail": "[proxmox]", + "protects": "Interface web do Proxmox (porta 8006)", + "retries": "3 / 10 min", + "ban": "1 hora" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (porta 8008 + proxy inverso http/https)", + "retries": "3 / 10 min", + "ban": "1 hora" + } + ], + "outro": "Defaults globais de jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (fallback de 24h para prisões que não o sobrepõem), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Por que a correção journald é importante", + "intro": "O Proxmox fornece /etc/systemd/journald.conf com MaxLevelStore=warning. O journald descarta as mensagens de nível inferior a warning antes de as guardar. SSH e PAM emitem falhas de autenticação nos níveis info / notice, pelo que:", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "falha de autenticação\n(nível info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ evento descartado sem aviso", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "não vê nada\n→ nunca proíbe nada", + "arrowLabel": "Proxmox por omissão" + }, + "afterDiagram": "O instalador detecta isso e escreve um drop-in em /etc/systemd/journald.conf.d/proxmenux-loglevel.conf elevando tanto MaxLevelStore quanto MaxLevelSyslog para info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Em seguida, reinicia systemd-journald. O drop-in é removido ao desinstalar, restaurando o padrão original do Proxmox." + }, + "loggers": { + "heading": "Porquê dois serviços de registo personalizado", + "intro1": "O Fail2Ban pode ler diretamente o journal do systemd (backend = systemd), mas no Proxmox este backend tem problemas conhecidos com processos worker de pvedaemon: os eventos de autenticação aparecem no journal mas nem sempre são detetados. O problema também afeta ocasionalmente sshd.", + "intro2": "O ProxMenux cria dois pequenos serviços systemd que acompanham as unidades relevantes com journalctl -f e acrescentam cada linha a um ficheiro. O Fail2Ban lê esses ficheiros com o fiável backend = auto (modo ficheiro):", + "headerService": "Serviço", + "headerSource": "Unidade de origem", + "headerOutput": "Ficheiro de saída", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Ambos os serviços são declarados PartOf=fail2ban.service para que eles reiniciar com Fail2Ban e parar com ele. Modo 640 de propriedade do root:adm nos arquivos de log. O terceiro log usado pela cadeia [proxmenux] (/var/log/proxmenux-auth.log) é escrito diretamente pelo aplicativo ProxMenux Monitor Flask — nenhum serviço de registrador necessário para esse." + }, + "backend": { + "heading": "Deteção automática do backend de firewall", + "intro": "O instalador sonda o host: se o nft list ruleset funcionar, ele escolhe o nftables como ação de ban. Caso contrário, volta para iptables-multiport / iptables-allports. A escolha é escrita em jail.local:", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "Reforço SSH: MaxAuthTries", + "intro": "O controlo Lynis SSH-7408 recomenda MaxAuthTries 3 em sshd_config. A jail SSH utiliza maxretry = 2 para bloquear após falhas repetidas; MaxAuthTries limita as tentativas numa ligação. Este ajuste SSH acrescenta defesa em profundidade, mesmo quando Fail2Ban está parado para manutenção.", + "installerIntro": "O instalador:", + "items": [ + "Lê o valor atual do MaxAuthTries (ou padrão para 6 se ausente).", + "Guarda-o em /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Edita sshd_config diretamente: substitui a linha existente, remove o comentário da linha comentada ou acrescenta uma nova.", + "Recarrega o sshd (recarregar, não reiniciar, para manter as sessões existentes vivas)." + ], + "outro": "Na desinstalação, o valor original salvo é restaurado e sshd é recarregado novamente." + }, + "manage": { + "heading": "Gerenciar uma instalação existente", + "intro": "Se o Fail2Ban já estiver instalado quando você abrir o menu, o script detecta-o e mostra um menu de gerenciamento em vez de repetir o instalador:", + "headerAction": "Ação", + "headerWhat": "O que ele faz", + "rows": [ + { + "action": "Reinstalar", + "what": "Repete a instalação completa e reescreve todas as jails com os valores atuais do ProxMenux. Utilize quando uma atualização alterar as recomendações." + }, + { + "action": "Remover", + "what": "Para fail2ban e os dois serviços de registro, limpa o pacote apt, remove todos os jail / arquivos de filtro, exclui o drop-in journald (restaurando o padrão do Proxmox) e restaura o SSH original MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Verifica se está a funcionar.", + "intro": "Após a instalação, comandos úteis da máquina:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "neverBansTitle": "O Fail2Ban é executado mas nunca proíbe nada", + "neverBansBody": "Verifique se tail -f /var/log/ssh-auth.log recebe uma entrada após uma tentativa SSH com palavra-passe errada a partir de outra máquina. Caso contrário, consulte systemctl status ssh-auth-logger.service. Se o serviço estiver ativo, veja os eventos recentes com journalctl -u ssh.service -n 50 --no-pager -p info e confirme que o complemento journald permite o nível info.", + "monitorEmptyTitle": "A jail ProxMenux Monitor não tem entradas mesmo após inícios de sessão falhados", + "monitorEmptyBody": "O [proxmenux] jail lê /var/log/proxmenux-auth.log, que é escrito pelo aplicativo ProxMenux Monitor Flask — não por um criador de logs journald. Se você não executar o Monitor, o arquivo permanecerá vazio e o jail nunca será acionado. Isso é esperado; a configuração jail é inofensiva. Se você executar o Monitor e o log estiver vazio, verifique a configuração de registro do Flask.", + "selfBanTitle": "Eu bani-me.", + "selfBanIntro": "A partir de uma consola / IPMI / iKVM (ou de outro IP autorizado):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "O apt-get falha: ''Unable to locate package fail2ban''", + "aptFailBody": "O host está faltando o Debian repos (comum nas instalações do 'barebones' Proxmox). O instalador detecta isso e escreve /etc/apt/sources.list.d/debian.sources com o codinome certo (bookworm / trixie) antes de tentar novamente. Se ainda falhar, verifique /etc/os-release para VERSION_CODENAME e confirme que o URL do repo está acessível.", + "lockoutTitle": "SSH me bloqueia depois de instalar", + "lockoutBody": "O instalador define MaxAuthTries=3. Se seu gerenciador de senhas / agente repetir várias chaves, você pode exceder isso em uma única tentativa de conexão. Limite as chaves oferecidas: ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. Ou aumentar temporariamente o MaxAuthTries em sshd_config enquanto você depura." + }, + "files": { + "heading": "Ficheiros gravados", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Relacionado", + "monitorLabel": "ProxMenux Monitor → Guia de segurança", + "monitorTail": " — instalação pelo painel, estado das jails em direto, IPs bloqueados e ajuste de tentativas e duração do bloqueio por jail no navegador.", + "lynisLabel": "Lynis", + "lynisTail": " — execute uma auditoria antes e depois para confirmar que o controlo SSH-7408 é cumprido.", + "securityLabel": "Visão geral da segurança", + "securityTail": " — voltar ao panorama da secção." + } +} diff --git a/web/messages/pt/docs/security/index.json b/web/messages/pt/docs/security/index.json new file mode 100644 index 00000000..5dfb80b8 --- /dev/null +++ b/web/messages/pt/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Segurança do Proxmox — Fail2Ban e auditoria de reforço com Lynis | ProxMenux", + "description": "Duas ferramentas de segurança para Proxmox VE: Fail2Ban (prevenção de intrusões para SSH, interface web do Proxmox e ProxMenux Monitor) e Lynis (auditoria de segurança e reforço do sistema, instalado a partir do repositório original no GitHub).", + "ogTitle": "Segurança do Proxmox — Fail2Ban e auditoria de reforço com Lynis", + "ogDescription": "Fail2Ban e Lynis para Proxmox VE: prevenção de intrusões e auditoria de reforço, instalados e geridos pelo ProxMenux.", + "twitterTitle": "Segurança do Proxmox | ProxMenux", + "twitterDescription": "Fail2Ban e Lynis para Proxmox VE: prevenção de intrusões e auditoria de reforço." + }, + "header": { + "title": "Segurança", + "description": "Duas ferramentas complementares para Proxmox VE: defesa ativa (Fail2Ban bloqueia IPs que atacam SSH ou interfaces web) e auditoria local (Lynis procura falhas de reforço e dá uma pontuação de 0 a 100). Cada ferramenta tem o seu menu de instalação e gestão, com deteção de instalações existentes antes de agir.", + "section": "Segurança" + }, + "intro": { + "title": "Defesa ativa e auditoria local", + "body": "Respondem a perguntas diferentes. Fail2Ban responde a «Estamos a ser atacados agora?» observando os registos de autenticação e bloqueando os IPs responsáveis. Lynis responde a «Qual é o nível de reforço deste anfitrião?» através de uma auditoria pontual com recomendações concretas. Utilize ambos: Lynis indica o que melhorar e Fail2Ban reage aos ataques em curso." + }, + "opening": { + "heading": "Abrir o menu", + "body": "No menu principal do ProxMenux selecione Segurança. Verá o seguinte:", + "imageAlt": "Menu de segurança com opções Fail2Ban e Lynis" + }, + "pick": { + "heading": "Escolha sua ferramenta", + "body": "As duas ferramentas de segurança são independentes — instale uma, ambas ou nenhuma. Cada carta abaixo salta para a seção que explica a ferramenta em detalhes." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Prevenção ativa de intrusões. Observa os registos de autenticação SSH e web e bloqueia IPs após falhas repetidas.", + "bullets": [ + "Três jails: SSH, interface Proxmox (8006), ProxMenux Monitor (8008)", + "Corrige as predefinições do journald no Proxmox que bloqueiam os registos de autenticação", + "Detecta automaticamente nftables / iptables", + "Endurecimento de SSH: MaxAuthTries=3 (recomendação Lynis)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Auditor local de segurança. Analisa o anfitrião e apresenta uma pontuação de reforço e sugestões concretas de correção.", + "bullets": [ + "Instalado a partir do repositório original da CISOfy no GitHub (versão mais recente)", + "Pontuação de reforço 0–100 e lista de avisos e sugestões", + "Auditoria e atualização através do menu", + "Concebido apenas para leitura: nunca altera o sistema" + ] + } + }, + "workflowTip": { + "title": "Fluxo de trabalho que utiliza ambos", + "body": "Execute primeiro Lynis para conhecer a pontuação inicial e as principais recomendações. Aplique as correções desejadas. Depois instale Fail2Ban: implementa automaticamente a proteção SSH contra força bruta e ajusta MaxAuthTries em sshd_config para cumprir o controlo SSH-7408. Execute Lynis novamente para verificar a melhoria." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "O ProxMenux instala Fail2Ban com uma configuração específica para Proxmox. Além da jail SSH padrão, protege a interface web Proxmox e o ProxMenux Monitor. Contorna dois problemas específicos: uma predefinição do journald que descarta eventos de autenticação e um problema do backend systemd que impede a leitura fiável de certas fontes do registo. A página detalhada explica a instalação, as três jails, a correção do journald, o reforço SSH e a resolução de problemas.", + "optionTitle": "Fail2Ban — instalar e gerenciar", + "optionDescription": "Três jails pré-configuradas (sshd agressivo, interface Proxmox em 8006, ProxMenux Monitor em 8008 e proxy inverso), deteção de nftables/iptables, correção do journald e reforço SSH com MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "O ProxMenux clona Lynis de github.com/CISOfy/lynis para /opt/lynis e disponibiliza-o como /usr/local/bin/lynis. Evita o pacote Debian porque está várias versões principais atrás do projeto original. A página detalhada explica instalação, auditoria, atualização, desinstalação, leitura do relatório e ações sobre os resultados.", + "optionTitle": "Lynis — instalar, auditar e gerenciar", + "optionDescription": "Instala a versão mais recente do Lynis a partir do repositório oficial CISOfy no GitHub (em vez do pacote Debian mais antigo), executa auditorias com pontuação de reforço e atualiza através de git pull." + }, + "componentStatus": { + "heading": "Estado do componente", + "body": "Ambos os instaladores registram seu estado em /usr/local/share/proxmenux/components_status.json na categoria security. ProxMenux usa este arquivo para decidir se deve mostrar a instalação ou o menu de gerenciamento em sequências, e os mesmos dados alimentam os painéis no ProxMenux Monitor quando presente." + } +} diff --git a/web/messages/pt/docs/security/lynis.json b/web/messages/pt/docs/security/lynis.json new file mode 100644 index 00000000..6b019ae9 --- /dev/null +++ b/web/messages/pt/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | Documentação do ProxMenux", + "description": "Instale a versão mais recente do Lynis do repositório oficial CISOfy no GitHub, execute uma auditoria com pontuação de reforço de 0 a 100 e atualize através de git pull. Auditor apenas de leitura: não altera o sistema.", + "ogTitle": "Lynis | Documentação do ProxMenux", + "ogDescription": "Instale e execute Lynis no Proxmox VE. Instalação do repositório original GitHub, auditoria e atualização." + }, + "header": { + "title": "Lynis", + "description": "Clona a versão mais recente do Lynis do repositório oficial CISOfy no GitHub, disponibiliza-a como /usr/local/bin/lynis e oferece auditoria, atualização, reinstalação e desinstalação no menu. O auditor apenas lê e apresenta resultados sem alterar o sistema.", + "section": "Segurança" + }, + "intro": { + "title": "O que isto faz", + "body": "Instala Lynis, o auditor de segurança de código aberto da CISOfy, do repositório GitHub em /opt/lynis e cria um lançador em /usr/local/bin/lynis para o disponibilizar no PATH. Se detetar uma instalação existente, apresenta um menu de auditoria, atualização, reinstalação ou remoção." + }, + "manageMenu": { + "heading": "Menu de gestão (após a instalação)", + "intro": "Uma vez instalado o Lynis, cada invocação subsequente abre o menu de gerenciamento em vez de repetir o instalador. A partir daqui você inicia uma auditoria, atualização via git pull, reinstalar ou desinstalar:", + "imageAlt": "Menu de gerenciamento do Lynis com as opções de auditoria, atualização, reinstalação e remoção" + }, + "whyUpstream": { + "heading": "Porquê o repositório oficial GitHub em vez de apt", + "intro": "O Debian distribui Lynis através de apt, mas o pacote costuma estar várias versões principais atrás do projeto original. Novos controlos, correções de novos vetores de ataque e recomendações melhoradas chegam primeiro ao GitHub. Lynis é um script shell autónomo, sem compilação nem dependências de instalação além de git; clonar o repositório é o método recomendado pela CISOfy.", + "headerSource": "Origem", + "headerPath": "Install path", + "headerUpdate": "Método de atualização", + "headerFresh": "Atualidade da versão", + "rows": [ + { + "sourceRich": "ProxMenux (este programa)", + "path": "/opt/lynis/", + "update": "git pull (no menu)", + "fresh": "Versão mais recente do projeto original" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Muitas vezes meses / anos atrás" + } + ] + }, + "install": { + "heading": "Como funciona a instalação", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(instale git primeiro, se faltar)", + "node2Label": "/opt/lynis/", + "node2Detail": "repositório completo\nincluindo o script ./lynis", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "lançador:\ncd /opt/lynis && ./lynis $@", + "outro": "O lançador é necessário: Lynis precisa de ser executado no próprio diretório porque utiliza caminhos relativos para plugins e perfis. O lançador esconde esse detalhe e permite que lynis audit system funcione a partir de qualquer diretório." + }, + "detection": { + "heading": "Caminhos de detecção", + "intro": "Antes de mostrar o menu, o script verifica três locais para decidir se Lynis já está presente:", + "items": [ + "/usr/local/bin/lynis — invólucro instalado por ProxMenux", + "/opt/lynis/lynis — caminho direto (caso o invólucro tenha sido removido)", + "/usr/bin/lynis — versão apt-instalada, se o usuário a instalou dessa forma anteriormente" + ], + "outro": "Se encontrar um destes caminhos, abre o menu de gestão. O script não desinstala Lynis instalado por apt; remove apenas o que gere em /opt/lynis e o respetivo lançador." + }, + "audit": { + "heading": "Executar uma auditoria", + "intro": "No menu de gestão escolha Executar auditoria de segurança agora. Equivale a executar:", + "code": "lynis audit system --no-colors", + "outro": "A auditoria demora de 30 segundos a alguns minutos conforme a dimensão do anfitrião. A saída aparece diretamente no terminal, sem animação de espera. Lynis organiza os controlos por categoria e marca cada teste como [ OK ], [ WARNING ] ou [ SUGGESTION ]. O resumo final apresenta os números principais:", + "summary": "================================================================================\n\n Detalhes da análise de segurança Lynis:\n\n Índice de reforço : 76 [############ ]\n Testes realizados : 247\n Plugins ativados : 0\n\n Componentes:\n - Firewall [V]\n - Analisador de malware [X]\n\n Módulos Lynis:\n - Estado de conformidade [?]\n - Auditoria de segurança [V]\n - Análise de vulnerabilidades [V]\n\n================================================================================" + }, + "report": { + "heading": "Ler o relatório", + "intro": "As duas seções importantes são Avisos (coisas que você deve corrigir em breve) e Sugestão (recomendações para melhorar o endurecimento). Cada item carrega um ID de controle como SSH-7408 — útil ao pesquisar os documentos Lynis para a lógica e a correção.", + "headerMarker": "Marcador", + "headerMeaning": "Significado", + "headerAction": "Ação", + "rows": [ + { + "markerRich": "OK", + "meaning": "Passado o teste", + "action": "Nada." + }, + { + "markerRich": "WARNING", + "meaning": "Problema real, correção recomendada", + "action": "Leia a descrição do controle, planeje uma correção" + }, + { + "markerRich": "SUGGESTION", + "meaning": "Melhoria do endurecimento disponível", + "action": "Aplicar se se encaixa no seu modelo de ameaça" + } + ], + "outro": "Relatório completo e dados legíveis por máquina são escritos para /var/log/lynis.log e /var/log/lynis-report.dat pelo próprio Lynis." + }, + "pairFail2ban": { + "title": "Combinar com Fail2Ban", + "body": "SSH-7408 (MaxAuthTries) é uma das advertências mais comuns. Instalar o Fail2Ban da ProxMenux o limpa automaticamente porque o instalador configura o MaxAuthTries=3 como parte da sua etapa de endurecimento do SSH. Corra Lynis novamente depois para confirmar que o aviso se foi." + }, + "update": { + "heading": "Actualizar o Lynis", + "body": "No menu, Atualizar Lynis para a versão mais recente executa git pull --quiet em /opt/lynis. Se o diretório existir mas não for uma cópia de trabalho Git, por exemplo após copiar ficheiros manualmente, o script reinstala por completo." + }, + "reinstall": { + "heading": "Reinstalar / desinstalar", + "headerAction": "Ação", + "headerWhat": "O que ele faz", + "rows": [ + { + "actionRich": "Reinstalar", + "whatRich": "Remove /opt/lynis, clona novamente do GitHub e recria o lançador. Utilize se a cópia local estiver danificada." + }, + { + "actionRich": "Remover", + "whatRich": "Exclui /opt/lynis e /usr/local/bin/lynis. Os logs em /var/log/lynis* são mantidos (são histórico de auditoria). Um Lynis apt-instalado em /usr/bin/lynis é deixado intocado." + } + ] + }, + "cli": { + "heading": "Opções CLI úteis", + "intro": "Uma vez instalado, Lynis pode ser invocado diretamente de uma shell com opções extras não expostas pelo menu:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Resolução de Problemas", + "cloneTitle": "git clone falha durante a instalação", + "cloneBody": "O host precisa de acesso à rede e DNS para alcançar github.com. A partir de um console: curl -sI https://github.com e git ls-remote https://github.com/CISOfy/lynis.git irá revelar o erro real (DNS, TLS, proxy, URL repo). Se for necessário um proxy, defina https_proxy no ambiente antes de executar novamente o menu.", + "notFoundTitle": "lynis: comando não encontrado após a instalação", + "notFoundIntro": "O lançador /usr/local/bin/lynis não foi criado ou foi removido. A solução mais rápida é reinstalar a partir do menu. Correção manual:", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "A auditoria mostra \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "O servidor SSH permite demasiadas tentativas de autenticação por ligação. Instale o Fail2Ban do ProxMenux (que define o MaxAuthTries=3 automaticamente) ou edite o /etc/ssh/sshd_config à mão:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "A pontuação caiu após uma atualização do Proxmox", + "scoreBody": "Executar lynis update info primeiro — uma atualização Lynis pode ter adicionado novos controles que sinalizam a configuração existente. Atualizar Lynis do menu, reauditar e abordar as novas descobertas." + }, + "files": { + "heading": "Ficheiros gravados", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Relatório de exemplo", + "intro": "O ProxMenux Monitor gera um PDF de várias páginas para cada execução do Lynis no separador Segurança. A primeira página resume a pontuação de reforço, informações do sistema e estado da segurança. As seguintes explicam cada aviso, ordenam sugestões por impacto e incluem o inventário de pacotes utilizado na auditoria.", + "imageAlt": "Primeira página do Lynis Security Audit Report PDF — resumo executivo com índice de endurecimento 71/100, bloco de informações do sistema e visão geral da postura de segurança com firewall, scanner de malware e contagem de pacotes instalados", + "captionPrefix": "Primeira página de um relatório de amostra. O PDF completo (", + "captionLink": "amostra", + "captionSuffix": ") continua com advertências detalhadas, sugestões e a lista de pacotes instalados.", + "cli": "No lado CLI os mesmos dados estão em /var/log/lynis-report.dat (arquivo plano legível por máquina) e /var/log/lynis.log (o log de execução legível por humanos). O PDF é gerado sob demanda pelo ProxMenux Monitor — executar o lynis a partir da linha de comando não produz um." + }, + "related": { + "heading": "Relacionado", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor → Guia de segurança", + "tail": " — executar a auditoria, procurar relatórios históricos e baixar o PDF diretamente do painel." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": " — implementa a proteção SSH à força bruta que Lynis recomenda." + }, + { + "href": "/docs/security", + "label": "Visão geral da segurança", + "tail": " — voltar ao panorama da secção." + } + ] + } +} diff --git a/web/messages/pt/docs/security/ssl-letsencrypt.json b/web/messages/pt/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..3e8ba3c2 --- /dev/null +++ b/web/messages/pt/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS para ProxMenux Monitor — certificado do anfitrião Proxmox ou caminhos personalizados | ProxMenux", + "description": "Disponibilize o ProxMenux Monitor por HTTPS com o certificado do anfitrião Proxmox (detetado automaticamente, incluindo Let’s Encrypt instalado pelo ACME do Proxmox) ou um certificado personalizado em qualquer caminho do disco.", + "ogTitle": "HTTPS para ProxMenux Monitor — Host Proxmox ou certificado personalizado", + "ogDescription": "Habilite o HTTPS na porta 8008 com o certificado de host Proxmox ou qualquer par personalizado .pem / .key." + }, + "header": { + "title": "HTTPS para ProxMenux Monitor", + "description": "Ative HTTPS para o Monitor na porta 8008 com o certificado do anfitrião Proxmox detetado automaticamente ou um certificado personalizado no disco. A opção Proxmox prefere o certificado ACME, se existir, e reutiliza Let’s Encrypt gerido pelo Proxmox sem outra tarefa de renovação.", + "section": "Segurança" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Duas maneiras de ativar o HTTPS", + "proxmox": { + "title": "Usar certificado Proxmox", + "summary": "Um clique: o Monitor reutiliza o certificado apresentado pelo Proxmox VE na porta 8006.", + "items": [ + "Detetado automaticamente, sem introduzir caminhos", + "Prefere o certificado ACME, se existir; caso contrário usa o certificado autoassinado predefinido", + "O Proxmox renova; o Monitor valida e carrega o certificado na próxima nova ligação TLS" + ] + }, + "custom": { + "title": "Usar certificado personalizado", + "summaryRich": "Aponte o monitor em um par .pem / .key em qualquer lugar no disco.", + "items": [ + "Apenas caminhos absolutos (o processo Monitor deve ser capaz de ler ambos)", + "Útil para certificados de certbot, acme.sh ou certificados wildcard", + "A renovação fica a seu cargo: reinicie o Monitor quando os ficheiros mudarem" + ] + } + }, + "proxmoxCert": { + "heading": "Certificado Proxmox — o que ele realmente aponta", + "intro": "Proxmox VE mantém dois pares de certificados em /etc/pve/local/:", + "table": { + "headers": { + "file": "Arquivo", + "origin": "Origem", + "when": "Quando o Monitor o usa" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Certificado personalizado carregado através da UI Proxmox — incluindo o certificado Let's Encrypt emitido sob Datacenter → Node → Certificados → ACME.", + "when": "Preferido quando ambos os ficheiros existem; o Monitor utiliza o certificado apresentado pelo Proxmox." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Certificado autoassinado gerado no momento da instalação pelo Proxmox PKI.", + "whenRich": "Alternativa quando não existe pveproxy-ssl.pem." + } + ] + }, + "callout": { + "title": "A mesma prioridade que o próprio Proxmox", + "bodyRich": "pveproxy aplica a mesma seleção. Com a opção Proxmox, o Monitor utiliza o certificado apresentado pela interface Proxmox em 8006, sem cópias nem processo de renovação separado." + } + }, + "letsencrypt": { + "heading": "Obtendo um certificado Let's Encrypt via Proxmox ACME", + "intro": "O Proxmox VE inclui o seu cliente ACME. «Order Certificate Now» guarda o resultado em /etc/pve/local/pveproxy-ssl.pem, que o Monitor lê. Os cinco passos seguintes resumem o guia oficial do Proxmox nos comandos necessários.", + "prereqs": { + "title": "Pré-requisitos", + "items": [ + "O nó tem um verdadeiro nome DNS (não apenas um IP) apontando para ele.", + "Ou porta 80 acessível a partir da Internet (validação HTTP-01, a mais simples), ou fornecedor DNS com credenciais API (DNS-01, funciona atrás de NAT e suporta certificados wildcard).", + "Um endereço de email para a conta ACME." + ] + }, + "step1": { + "heading": "Etapa 1 — Registo da conta ACME", + "introRich": "Substitua o email pelo seu. O nome da conta default é apenas um rótulo — você pode usar qualquer string.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "A Proxmox pede que você aceite os termos de serviço Let's Encrypt. Depois disso, a conta é persistida em /etc/pve/priv/acme/ e a guia ACME na GUI irá mostrá-la." + }, + "step2": { + "heading": "Passo 2 — Escolha um tipo de desafio", + "http01Rich": "HTTP-01 é o mais fácil: é embutido, sem plugin extra. O Let's Encrypt conecta-se ao http://<your-host>/.well-known/acme-challenge/... na porta 80, então a porta 80 deve estar aberta da internet pública ao seu nó — normalmente uma porta para frente no seu roteador.", + "dns01Rich": "DNS-01 funciona sem expor o porto 80 (bom para homelabs atrás do CG-NAT) e é a única opção para certificados wildcard. Você adiciona um plugin DNS uma vez com o token API do seu provedor. Exemplo Cloudflare:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "Para outros fornecedores (Gandi, OVH, AWS Route 53, etc.), consulte pvenode acme plugin add dns --help. O Proxmox inclui todos os plugins DNS do acme.sh." + }, + "step3": { + "heading": "Passo 3 — Ligar o domínio ao seu nó", + "http01Rich": "Para o HTTP-01 (usa o plug- in incorporado do standalone):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "Para DNS-01 com o plugin Cloudflare do Passo 2:", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Wildcard? Adicionar domain=*.example.com em uma entrada separada --acmedomainN. Wildcards requerem DNS-01." + }, + "step4": { + "heading": "Passo 4 — Ordenar o certificado", + "code": "pvenode acme cert order", + "afterRich": "A primeira execução normalmente leva 30–60 segundos enquanto o desafio se propaga. No sucesso Proxmox escreve /etc/pve/local/pveproxy-ssl.pem + .key, reinicia pveproxy, e um cron de renovação é agendado automaticamente (Let's Encrypt certs são válidos 90 dias; Proxmox renova em 60)." + }, + "step5": { + "heading": "Etapa 5 — Verificar o emitente", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "Deve imprimir um emissor começando com O = Let's Encrypt. Se em vez disso ele lê CN = Proxmox Virtual Environment a ordem falhou e o arquivo ainda é o certificado PVE auto-assinado — re-executar pvenode acme cert order --debug 1 para ver porquê." + }, + "gui": { + "title": "Preferes a GUI? Os mesmos cinco passos", + "bodyRich": "Datacenter → ACME regista a conta; Datacenter → ACME → Plugins adiciona o plugin DNS; Node → Certificates → ACME associa o domínio; Order Certificates Now executa o passo 4. Os comandos anteriores são úteis para scripts e sistemas sem interface gráfica." + } + }, + "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." + }, + "custom": { + "heading": "Certificado personalizado — quando usá-lo", + "intro": "Clique em Usar certificado personalizado e introduza dois caminhos absolutos:", + "items": [ + "Certificado — cadeia completa em PEM (.pem ou .crt).", + "Chave privada: chave correspondente em PEM (.key)." + ], + "outro": "Ambos os arquivos são lidos pelo processo Monitor na inicialização. Certifique-se de que eles permanecem legíveis pelo usuário que a unidade systemd executa como (root por padrão). Locais comuns quando o ACME é administrado fora do Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Alvos do Symlink vs. arquivos", + "bodyRich": "certbot armazena os arquivos reais em /etc/letsencrypt/archive/ e o caminho live/ é um link simbólico. Aponte o Monitor no link simbólico live/, não no arquivo archive/ — dessa forma cada ciclo de renovação simplesmente reescreve o alvo do link simbólico e o Monitor capta o novo certificado no próximo início do serviço." + } + }, + "afterHttps": { + "heading": "Após activar o HTTPS", + "bodyRich": "Salvar a mudança escreve /etc/proxmenux/ssl_config.json e ativa um reinício da unidade proxmenux-monitor systemd. A guia do navegador perderá sua conexão atual — recarregue-a no https://<host>:8008/. A primeira carga com um certificado Let's Encrypt não deve mostrar nenhum aviso; com o certificado auto-assinado Proxmox o navegador exigirá uma exceção, a mesma que para porta 8006.", + "reverse": { + "heading": "Proxies inversos e webhooks", + "bodyRich": "Quando o Monitor muda para HTTPS, as URLs Proxmox VE webhook registradas para notificações também passam de http:// para https:// automaticamente. Entradas webhook existentes são reescritas da próxima vez que uma regra de notificação for salva ou redetectada, então nenhuma edição manual da configuração de notificação Proxmox é necessária." + } + }, + "trustCa": { + "heading": "Confiar na CA autoassinada do Proxmox", + "intro1Rich": "Para manter o certificado Proxmox predefinido sem ACME, importe a CA raiz do cluster para o repositório de confiança do sistema ou do navegador. O Monitor deteta /etc/pve/local/pve-ssl.pem como certificado de recurso. O cliente passa a confiar no emissor; o URL tem ainda de corresponder a um nome do certificado.", + "intro2Rich": "O arquivo CA vive em /etc/pve/pve-root-ca.pem em cada nó. Em um cluster, o caminho está no sistema de arquivos pmxcfs compartilhado, então todos os nós compartilham a mesma raiz, e uma única importação cobre todo o datacenter. Copie para a sua máquina:", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Depois importa-o do lado do cliente:", + "items": [ + "Linux (todo o sistema): execute sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt e depois sudo update-ca-certificates. Os navegadores que utilizam o repositório de certificados do sistema (Chromium, Edge) reconhecem a CA após reiniciar; Firefox usa um repositório próprio e requer a importação manual abaixo.", + "macOS: abra o ficheiro .pem com duplo clique no Acesso a Porta-chaves, adicione-o ao porta-chaves Sistema e defina Confiança → Confiar sempre.", + "Windows: clique com o botão direito no ficheiro → Instalar certificado → coloque-o em Autoridades de certificação de raiz fidedigna.", + "Firefox (qualquer SO): Configurações → Privacidade e segurança → Certificados → Ver certificados → Autoridades → Importar; selecione Confiar nesta CA para identificar sites." + ], + "standalone": { + "title": "Nós autônomos fora de um cluster", + "bodyRich": "Cada host Proxmox não-aglomerado tem seu próprio pve-root-ca.pem. Se você gerenciar vários hosts autônomos, repita a cópia/importação para cada um, ou junte-os a um cluster para que eles compartilhem uma única raiz." + } + }, + "disable": { + "heading": "Desactivando o HTTPS", + "bodyRich": "Clique em Desativar HTTPS no mesmo painel. O Monitor reinicia com HTTP sem cifragem, útil se um certificado personalizado ilegível impedir o arranque. Não existe outro passo de desinstalação e os ficheiros de certificados não são alterados.", + "stateCallout": { + "title": "Onde é guardado o estado", + "bodyRich": "/etc/proxmenux/ssl_config.json armazena os caminhos do certificado ativo e a fonte (none, proxmox, custom). Excluir o arquivo é equivalente a desativar o HTTPS — no início seguinte o Monitor cai para o HTTP." + } + } +} diff --git a/web/messages/sk/docs/security/fail2ban.json b/web/messages/sk/docs/security/fail2ban.json new file mode 100644 index 00000000..33062ab3 --- /dev/null +++ b/web/messages/sk/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | Dokumentácia ProxMenux", + "description": "Nainštalujte Fail2Ban s tromi jails vyladenými pre Proxmox (SSH agresívny, Proxmox UI 8006, ProxMenux Monitor 8008). Zahŕňa opravu journald MaxLevelStore, vlastné protokolové služby pre spoľahlivosť, automaticky detekovaný backend nftables/iptables a zosilnenie SSH MaxAuthTries.", + "ogTitle": "Fail2Ban | Dokumentácia ProxMenux", + "ogDescription": "Ochrana pred hrubou silou pre SSH a webové rozhrania Proxmox s opravami journald a backendu špecifickými pre Proxmox." + }, + "header": { + "title": "Fail2Ban", + "description": "Nainštaluje Fail2Ban pre Proxmox: tri jaily (agresívny SSH, rozhranie Proxmox na 8006, ProxMenux Monitor na 8008 a reverzný proxy), opravu úrovne journald na ukladanie udalostí SSH, dve služby kopírujúce denník do súborov na obídenie známeho problému backendu systemd, detekciu backendu firewallu a posilnenie SSH pomocou MaxAuthTries.", + "section": "Zabezpečenie" + }, + "intro": { + "title": "Čo to robí?", + "body": "Inštaluje Fail2Ban z repozitárov Debian a vytvorí konfiguráciu Proxmox, ktorá ihneď chráni SSH, rozhranie Proxmox a ProxMenux Monitor. Ak rozpozná existujúcu inštaláciu, ponúkne menu správy na preinštalovanie alebo odstránenie." + }, + "firstLaunch": { + "heading": "Dialóg prvého spustenia", + "body": "Ak Fail2Ban nie je nainštalovaný, skript zobrazí potvrdenie so súhrnom plánovanej inštalácie a konfigurácie. Zrušenie ukončí postup bez zmien; potvrdenie spustí inštaláciu.", + "imageAlt": "Potvrdenie inštalácie Fail2Ban s tromi jailmi, opravou journald a posilnením SSH" + }, + "jails": { + "heading": "Tri jails", + "headerJail": "Jail", + "headerProtects": "Chráni", + "headerRetries": "Pokusy / Časové okno", + "headerBan": "Trvanie blokovania", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (agresívny režim — zahŕňa ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 hodín" + }, + { + "jail": "[proxmox]", + "protects": "Webové rozhranie Proxmox (port 8006)", + "retries": "3 / 10 min", + "ban": "1 hodina" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (port 8008 + http/https reverzný proxy)", + "retries": "3 / 10 min", + "ban": "1 hodina" + } + ], + "outro": "Globálne predvolené hodnoty od jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (24-hodinová rezerva pre jails ktoré to neprepíšu), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Prečo je oprava journald dôležitá", + "intro": "Proxmox dodáva /etc/systemd/journald.conf s MaxLevelStore=warning. journald pred uložením zahodí správy s úrovňou nižšou než warning. SSH a PAM hlásia chyby overovania na úrovniach info / notice, preto:", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "chyba overovania\n(úroveň info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ udalosť sa bez upozornenia zahodí", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "nevidí nič\n→ nikdy nič nezakazuje", + "arrowLabel": "predvolený Proxmox" + }, + "afterDiagram": "Inštalátor to rozpozná a vytvorí doplnkový súbor /etc/systemd/journald.conf.d/proxmenux-loglevel.conf, ktorý nastaví MaxLevelStore aj MaxLevelSyslog na info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Potom reštartuje systemd-journald. Pri odinštalovaní sa doplnkový súbor odstráni a obnoví sa pôvodné nastavenie Proxmox." + }, + "loggers": { + "heading": "Prečo dve vlastné logger služby", + "intro1": "Fail2Ban môže čítať priamo denník systemd (backend = systemd), ale tento backend má na Proxmoxe známe problémy s pracovnými procesmi pvedaemon: udalosti overovania sa objavia v denníku, ale nie vždy sa zachytia. Problém sa občas týka aj sshd.", + "intro2": "ProxMenux vytvorí dve malé služby systemd, ktoré sledujú príslušné jednotky pomocou journalctl -f a pridávajú každý riadok do súboru. Fail2Ban číta tieto súbory spoľahlivým backend = auto (súborový režim):", + "headerService": "Služba", + "headerSource": "Zdrojová jednotka", + "headerOutput": "Výstupný súbor", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Obe služby sú deklarované ako PartOf=fail2ban.service, takže sa reštartujú s Fail2Ban a zastavia sa s ním. Režim 640, ktorý vlastní root:adm v súboroch denníka. Tretí protokol používaný [proxmenux] jail (/var/log/proxmenux-auth.log) je zapísaný priamo aplikáciou ProxMenux Monitor Flask – nie je potrebná žiadna služba zapisovača." + }, + "backend": { + "heading": "Automatická detekcia backendu firewallu", + "intro": "Inštalátor sonduje hostiteľa: ak nft list ruleset funguje, vyberie nftables ako akciu zákazu. Inak sa vráti do iptables-multiport / iptables-allports. Voľba je napísaná do jail.local:", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "Posilnenie SSH: MaxAuthTries", + "intro": "Kontrola Lynis SSH-7408 odporúča MaxAuthTries 3 v sshd_config. SSH jail používa maxretry = 2 na blokovanie po opakovaných zlyhaniach; MaxAuthTries obmedzuje pokusy v rámci jedného spojenia. Toto nastavenie SSH pridáva ďalšiu vrstvu ochrany aj pri zastavení Fail2Ban počas údržby.", + "installerIntro": "Inštalátor:", + "items": [ + "Číta aktuálnu hodnotu MaxAuthTries (alebo predvolené hodnoty 6 v prípade neprítomnosti).", + "Ukladá to na /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Upraví sshd_config priamo: nahradí existujúci riadok, odkomentuje zakomentovaný alebo pridá nový.", + "Znova načíta sshd bez reštartu, aby existujúce relácie zostali aktívne." + ], + "outro": "Na odinštalovanie je uložená pôvodná hodnota obnovená a sshd je znovu načítaná." + }, + "manage": { + "heading": "Spravovať existujúcu inštaláciu", + "intro": "Ak je Fail2Ban nainštalovaný už pri otvorení ponuky, skript ho detekuje a zobrazí riadiace menu namiesto opätovného spustenia inštalátora:", + "headerAction": "Akcia", + "headerWhat": "Čo to robí?", + "rows": [ + { + "action": "Preinštalovať", + "what": "Znova spustí celý inštalátor a prepíše všetky jaily aktuálnymi predvolenými hodnotami ProxMenux. Použite to, keď aktualizácia zmení odporúčané hodnoty." + }, + { + "action": "Odstrániť", + "what": "Zastaví fail2ban a dve loggerové služby, vyčistí balík apt, odstráni všetky jail / súbory filtrov, odstráni drop-in journald (obnoví predvolené nastavenie Proxmox) a obnoví pôvodný SSH MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Overte, či funguje.", + "intro": "Po inštalácii užitočné príkazy od hostiteľa:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "neverBansTitle": "Fail2Ban beží, ale nikdy nič nezakazuje", + "neverBansBody": "Overte, či tail -f /var/log/ssh-auth.log dostane záznam po pokuse o SSH prihlásenie s nesprávnym heslom z iného počítača. Ak zostane prázdny, skontrolujte systemctl status ssh-auth-logger.service. Ak služba beží, prezrite udalosti príkazom journalctl -u ssh.service -n 50 --no-pager -p info a overte, či doplnková konfigurácia journald povoľuje úroveň info.", + "monitorEmptyTitle": "ProxMenux Monitor jail nemá žiadne položky ani po neúspešnom prihlásení", + "monitorEmptyBody": "[proxmenux] jail číta /var/log/proxmenux-auth.log, ktorý je napísaný aplikáciou ProxMenux Monitor Flask – nie zapisovačom journald. Ak nespustíte Monitor, súbor zostane prázdny a jail sa nikdy nespustí. To sa očakáva; konfigurácia jail je neškodná. Ak spustíte Monitor a protokol je prázdny, skontrolujte konfiguráciu protokolovania Flask.", + "selfBanTitle": "Zakázal som sám seba.", + "selfBanIntro": "Z konzoly / IPMI / iKVM (alebo inej povolenej IP adresy):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "apt-get zlyhá: ''Unable to locate package fail2ban''", + "aptFailBody": "Hostiteľovi chýbajú repozitáre Debian (bežné pri minimálnej inštalácii Proxmox). Inštalátor to rozpozná a pred opakovaním vytvorí /etc/apt/sources.list.d/debian.sources so správnym kódovým označením (bookworm / trixie). Ak chyba pretrváva, skontrolujte VERSION_CODENAME v /etc/os-release a dostupnosť URL repozitára.", + "lockoutTitle": "SSH ma po inštalácii zamkne", + "lockoutBody": "Inštalatér nastaví MaxAuthTries=3. Ak váš správca hesla / agent získa viac kľúčov, môžete to prekročiť pri jednom pokuse o pripojenie. Obmedzte ponúkané kľúče: ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. Alebo dočasne zvýšiť MaxAuthTries v sshd_config, zatiaľ čo vy ladíte." + }, + "files": { + "heading": "Súbory napísané", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Súvisiace", + "monitorLabel": "ProxMenux Monitor → Bezpečnostná karta", + "monitorTail": " — inštalácia z ovládacieho panela, aktuálny stav jailov, blokované IP adresy a nastavenie počtu pokusov a trvania blokovania pre každý jail v prehliadači.", + "lynisLabel": "Lynis", + "lynisTail": " — spustite bezpečnostný audit pred zmenou a po nej a overte splnenie kontroly SSH-7408.", + "securityLabel": "Prehľad bezpečnosti", + "securityTail": " — Späť na prehľad sekcie." + } +} diff --git a/web/messages/sk/docs/security/index.json b/web/messages/sk/docs/security/index.json new file mode 100644 index 00000000..39762fe4 --- /dev/null +++ b/web/messages/sk/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Zabezpečenie Proxmox — Fail2Ban a audit zabezpečenia Lynis | ProxMenux", + "description": "Dva bezpečnostné nástroje pre Proxmox VE: Fail2Ban (prevencia prienikov cez SSH, webové rozhranie Proxmox a ProxMenux Monitor) a Lynis (audit bezpečnosti a posilnenia zabezpečenia, inštalovaný z pôvodného repozitára GitHub).", + "ogTitle": "Zabezpečenie Proxmox — Fail2Ban a audit zabezpečenia Lynis", + "ogDescription": "Fail2Ban a Lynis pre Proxmox VE: prevencia prienikov a audit zabezpečenia, inštalované a spravované cez ProxMenux.", + "twitterTitle": "Zabezpečenie Proxmox | ProxMenux", + "twitterDescription": "Fail2Ban a Lynis pre Proxmox VE: prevencia prienikov a audit zabezpečenia." + }, + "header": { + "title": "Zabezpečenie", + "description": "Dva doplnkové nástroje pre Proxmox VE: aktívna ochrana (Fail2Ban blokuje IP adresy útočiace na SSH alebo webové rozhrania) a lokálny audit (Lynis hľadá nedostatky zabezpečenia a udeľuje skóre 0–100). Každý má vlastné menu inštalácie a správy, ktoré pred každou akciou rozpozná existujúcu inštaláciu.", + "section": "Zabezpečenie" + }, + "intro": { + "title": "Aktívna ochrana a lokálny audit", + "body": "Odpovedajú na rôzne otázky. Fail2Ban odpovedá na „Útočí na nás niekto práve teraz?“ sledovaním protokolov overovania a blokovaním útočiacich IP adries. Lynis odpovedá na „Ako dobre je tento hostiteľ zabezpečený?“ jednorazovým auditom a konkrétnymi odporúčaniami. Používajte oba: Lynis ukáže, čo zlepšiť, a Fail2Ban reaguje na prebiehajúce útoky." + }, + "opening": { + "heading": "Otvorenie menu", + "body": "V hlavnom menu ProxMenux vyberte Zabezpečenie. Zobrazí sa toto:", + "imageAlt": "Bezpečnostné menu s možnosťami Fail2Ban a Lynis" + }, + "pick": { + "heading": "Vyberte si nástroj", + "body": "Tieto dva nástroje sú nezávislé: nainštalujte jeden, oba alebo žiadny. Každá karta nižšie odkazuje na podrobný opis nástroja." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Aktívna prevencia prienikov. Sleduje protokoly overovania SSH a webových rozhraní a po opakovaných zlyhaniach blokuje IP adresy.", + "bullets": [ + "Tri jails: SSH, Proxmox UI (8006), ProxMenux Monitor (8008)", + "Opravuje predvolené nastavenia Proxmox journald, ktoré blokujú protokoly overenia", + "Automaticky rozpozná nftables / iptables", + "Posilnenie SSH: MaxAuthTries=3 (odporúčanie Lynis)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Lokálny bezpečnostný audítor. Preskúma hostiteľa a zobrazí skóre zabezpečenia a konkrétne odporúčania na nápravu.", + "bullets": [ + "Inštalované z pôvodného repozitára CISOfy na GitHube (najnovšia verzia)", + "Skóre zabezpečenia 0–100 a zoznam upozornení a návrhov", + "Audit a aktualizácia priamo v menu", + "Navrhnuté iba na čítanie: nikdy nemení systém" + ] + } + }, + "workflowTip": { + "title": "Pracovný tok, ktorý využíva oboje", + "body": "Najprv spustite audit Lynis, aby ste poznali počiatočné skóre a hlavné odporúčania. Použite vybrané opravy. Potom nainštalujte Fail2Ban: automaticky zavedie ochranu SSH pred hrubou silou a upraví MaxAuthTries v sshd_config podľa kontroly SSH-7408. Opätovným auditom Lynis overte zlepšenie." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "ProxMenux inštaluje Fail2Ban s konfiguráciou pre Proxmox. Okrem štandardného SSH jailu chráni webové rozhranie Proxmox a ProxMenux Monitor. Obchádza dva špecifické problémy: predvolené nastavenie journald zahadzujúce udalosti overovania a problém backendu systemd, ktorý bráni spoľahlivému čítaniu niektorých zdrojov systémového denníka. Podrobná stránka vysvetľuje inštaláciu, tri jaily, opravu journald, posilnenie SSH a riešenie problémov.", + "optionTitle": "Fail2Ban — inštalácia a správa", + "optionDescription": "Tri predkonfigurované jaily (agresívny režim sshd, rozhranie Proxmox na 8006, ProxMenux Monitor na 8008 a reverzný proxy), detekcia nftables/iptables, oprava journald a posilnenie SSH pomocou MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "ProxMenux klonuje Lynis z github.com/CISOfy/lynis do /opt/lynis a sprístupní ho ako /usr/local/bin/lynis. Balík Debian zámerne nepoužíva, pretože za pôvodným projektom zaostáva o viacero hlavných verzií. Podrobná stránka opisuje inštaláciu, audit, aktualizáciu, odinštalovanie, čítanie správy a riešenie zistení.", + "optionTitle": "Lynis — inštalácia, audit a správa", + "optionDescription": "Nainštaluje najnovší Lynis z oficiálneho repozitára CISOfy na GitHube (namiesto staršieho balíka Debian), vykonáva audity systému so skóre zabezpečenia a aktualizuje cez git pull." + }, + "componentStatus": { + "heading": "Stav komponentu", + "body": "Obaja inštalátori registrujú svoj stav v /usr/local/share/proxmenux/components_status.json v kategórii security. ProxMenux používa tento súbor na rozhodnutie, či zobraziť inštaláciu alebo menu správy pri následných spusteniach, a rovnaké dáta napája palubnú dosku v ProxMenux Monitor keď je prítomný." + } +} diff --git a/web/messages/sk/docs/security/lynis.json b/web/messages/sk/docs/security/lynis.json new file mode 100644 index 00000000..c010cf27 --- /dev/null +++ b/web/messages/sk/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | Dokumentácia ProxMenux", + "description": "Nainštalujte najnovší Lynis z oficiálneho repozitára CISOfy na GitHube, vykonajte audit so skóre zabezpečenia 0–100 a aktualizujte cez git pull. Audítor iba na čítanie: nemení systém.", + "ogTitle": "Lynis | Dokumentácia ProxMenux", + "ogDescription": "Nainštalujte a spustite bezpečnostný audítor Lynis na Proxmox VE. Inštalácia z pôvodného repozitára GitHub, audit a aktualizácia." + }, + "header": { + "title": "Lynis", + "description": "Klonuje najnovší Lynis z oficiálneho repozitára CISOfy na GitHube, sprístupní ho ako /usr/local/bin/lynis a v menu ponúka audit, aktualizáciu, preinštalovanie a odinštalovanie. Audítor iba číta a vytvára správy bez zmeny systému.", + "section": "Zabezpečenie" + }, + "intro": { + "title": "Čo to robí?", + "body": "Nainštaluje Lynis, bezpečnostný audítor s otvoreným zdrojovým kódom od CISOfy, z jeho repozitára GitHub do /opt/lynis a vytvorí spúšťací skript /usr/local/bin/lynis, aby bol dostupný cez PATH. Ak rozpozná existujúcu inštaláciu, zobrazí menu auditu, aktualizácie, preinštalovania alebo odstránenia." + }, + "manageMenu": { + "heading": "Menu správy (po inštalácii)", + "intro": "Keď je Lynis nainštalovaný, každé následné vyvolanie otvorí menu správy namiesto opätovného spustenia inštalátora. Odtiaľ spustíte audit, aktualizáciu prostredníctvom git pull, preinštalovať alebo odinštalovať:", + "imageAlt": "Lynis management menu s auditom, aktualizácia, preinštalovať a odstrániť možnosti" + }, + "whyUpstream": { + "heading": "Prečo pôvodný repozitár GitHub namiesto apt", + "intro": "Debian poskytuje Lynis cez apt, ale balík zvyčajne zaostáva o viacero hlavných verzií. Nové kontroly, opravy nových vektorov útoku a vylepšené odporúčania prichádzajú najprv na GitHub. Lynis je samostatný shellový skript bez kompilácie a inštalačných závislostí okrem git; klonovanie repozitára je preto metóda odporúčaná CISOfy.", + "headerSource": "Zdroj", + "headerPath": "Install path", + "headerUpdate": "Spôsob aktualizácie", + "headerFresh": "Aktuálnosť verzie", + "rows": [ + { + "sourceRich": "ProxMenux (tento skript)", + "path": "/opt/lynis/", + "update": "git pull (v menu)", + "fresh": "Najnovšia verzia pôvodného projektu" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Často mesiace / roky pozadu" + } + ] + }, + "install": { + "heading": "Ako funguje inštalácia", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(najprv nainštaluje git, ak chýba)", + "node2Label": "/opt/lynis/", + "node2Detail": "celý repozitár\nvrátane skriptu ./lynis", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "obal:\ncd /opt/lynis && ./lynis $@", + "outro": "Balíčk je povinný – Lynis trvá na tom, aby bol spustený z vlastného adresára, pretože načítava relatívne cesty pre zásuvné moduly a údaje profilu. Obal tento detail skrýva, takže lynis audit system jednoducho funguje odkiaľkoľvek." + }, + "detection": { + "heading": "Zisťovacie cesty", + "intro": "Pred zobrazením menu skript kontroluje tri miesta, aby rozhodol, či je Lynis už prítomný:", + "items": [ + "/usr/local/bin/lynis Wrapper nainštalovaný ProxMenux", + "/opt/lynis/lynis — priama cesta, ak bol spúšťací skript odstránený", + "/usr/bin/lynis — verzia nainštalovaná cez apt, ak ju používateľ nainštaloval týmto spôsobom" + ], + "outro": "Ak sa nájde niektorá z ciest, otvorí sa menu správy. Skript neodinštaluje Lynis nainštalovaný cez apt; odstráni iba vlastnú inštaláciu v /opt/lynis a jej spúšťací skript." + }, + "audit": { + "heading": "Spustiť audit", + "intro": "V menu správy vyberte Spustiť bezpečnostný audit teraz. Zodpovedá to spusteniu:", + "code": "lynis audit system --no-colors", + "outro": "Audit trvá od 30 sekúnd do niekoľkých minút podľa veľkosti hostiteľa. Výstup ide priamo do terminálu bez animácie čakania. Lynis člení kontroly podľa kategórií a každý test označí ako [ OK ], [ WARNING ] alebo [ SUGGESTION ]. Záverečné zhrnutie ukazuje hlavné údaje:", + "summary": "================================================================================\n\n Podrobnosti bezpečnostného auditu Lynis:\n\n Index zabezpečenia : 76 [############ ]\n Vykonané testy : 247\n Povolené doplnky : 0\n\n Komponenty:\n - Firewall [V]\n - Skener škodlivého softvéru [X]\n\n Moduly Lynis:\n - Stav súladu [?]\n - Bezpečnostný audit [V]\n - Kontrola zraniteľností [V]\n\n================================================================================" + }, + "report": { + "heading": "Čítanie správy", + "intro": "Dve dôležité časti sú Upozornenia (veci, ktoré by ste mali čoskoro opraviť) a Návrhy (odporúčania na zlepšenie vytvrdzovania). Každá položka nesie kontrolné ID ako SSH-7408 – užitočné pri hľadaní zdôvodnenia a opravy v dokumentoch Lynis.", + "headerMarker": "Značka", + "headerMeaning": "Význam", + "headerAction": "Akcia", + "rows": [ + { + "markerRich": "OK", + "meaning": "Skúška prešla", + "action": "Nič." + }, + { + "markerRich": "WARNING", + "meaning": "Skutočný problém; odporúčaná oprava", + "action": "Prečítajte si kontrolný popis, naplánujte opravu" + }, + { + "markerRich": "SUGGESTION", + "meaning": "Možné zlepšenie zabezpečenia", + "action": "Aplikujte, ak vyhovuje vášmu modelu hrozby" + } + ], + "outro": "Kompletná správa a strojovo čitateľné údaje sú napísané /var/log/lynis.log a /var/log/lynis-report.dat samotným Lynisom." + }, + "pairFail2ban": { + "title": "Používajte spolu s Fail2Ban", + "body": "SSH-7408 (MaxAuthTries) je jedným z najčastejších varovaní. Inštalácia Fail2Ban od ProxMenux automaticky ho vymaže, pretože inštalátor nastaví MaxAuthTries=3 ako súčasť SSH tvrdnúci krok. Spustite znova Lynis potom potvrdiť, že varovanie je preč." + }, + "update": { + "heading": "Aktualizácia Lynis", + "body": "V menu možnosť Aktualizovať Lynis na najnovšiu verziu spustí git pull --quiet v /opt/lynis. Ak adresár existuje, ale nie je pracovnou kópiou Git, napríklad po ručnom kopírovaní súborov, skript vykoná úplnú preinštaláciu." + }, + "reinstall": { + "heading": "Preinštalovať/odinštalovať", + "headerAction": "Akcia", + "headerWhat": "Čo to robí?", + "rows": [ + { + "actionRich": "Preinštalovať", + "whatRich": "Odstráni /opt/lynis, znova klonuje z GitHubu a vytvorí spúšťací skript. Použite pri poškodenej miestnej pracovnej kópii." + }, + { + "actionRich": "Odstrániť", + "whatRich": "Odstráni /opt/lynis a /usr/local/bin/lynis. Záznamy na /var/log/lynis* sú vedené (sú audit histórie). Apt-inštalovaný Lynis na /usr/bin/lynis zostáva nedotknutý." + } + ] + }, + "cli": { + "heading": "Užitočné možnosti CLI", + "intro": "Po nainštalovaní sa Lynis môže použiť priamo z shellu s extra možnosťami, ktoré nie sú vystavené menu:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Riešenie problémov", + "cloneTitle": "git clone počas inštalácie zlyhá", + "cloneBody": "Hostiteľ potrebuje prístup k sieti a DNS dosiahnuť github.com. Z konzoly: curl -sI https://github.com a git ls-remote https://github.com/CISOfy/lynis.git odhalí skutočnú chybu (DNS, TLS, proxy, repo URL). Ak sa vyžaduje proxy, nastavte https_proxy v prostredí pred opätovným spustením menu.", + "notFoundTitle": "lynis: príkaz nenájdený po inštalácii", + "notFoundIntro": "Obal na /usr/local/bin/lynis buď nebol vytvorený, alebo bol odstránený. Najrýchlejšia oprava: preinštalovať z menu. Ručné nastavenie:", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "Audit zobrazuje \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "Démon SSH umožňuje príliš veľa auth pokusov na pripojenie. Buď nainštalujte Fail2Ban z ProxMenux (ktorý nastaví MaxAuthTries=3), alebo editujte /etc/ssh/sshd_config ručne:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "Skóre klesol po upgrade Proxmox", + "scoreBody": "Najprv spustite lynis update info; aktualizácia Lynis mohla pridať kontroly, ktoré upozorňujú na existujúcu konfiguráciu. Aktualizujte Lynis z menu, zopakujte audit a riešte nové zistenia." + }, + "files": { + "heading": "Súbory napísané", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Ukážková správa", + "intro": "ProxMenux Monitor vytvorí pre každé spustenie Lynis viacstranové PDF dostupné na karte Zabezpečenie. Prvá strana obsahuje skóre zabezpečenia, údaje o systéme a prehľad bezpečnostného stavu. Ďalšie strany vysvetľujú každé upozornenie, zoradia návrhy podľa vplyvu a uvedú inventár balíkov použitý pri audite.", + "imageAlt": "Prvá strana PDF bezpečnostného auditu Lynis: zhrnutie so skóre zabezpečenia 71/100, systémovými informáciami a prehľadom firewallu, skenera škodlivého softvéru a počtu nainštalovaných balíkov", + "captionPrefix": "Prvá strana ukážkovej správy. Úplné PDF (", + "captionLink": "vzorka", + "captionSuffix": ") pokračuje podrobnými upozorneniami, návrhmi a zoznamom nainštalovaných balíkov.", + "cli": "Rovnaké údaje sú v CLI v súbore /var/log/lynis-report.dat (strojovo čitateľný textový súbor) a /var/log/lynis.log (čitateľný denník spustenia). PDF vytvára na požiadanie ProxMenux Monitor; spustenie lynis z príkazového riadka PDF nevytvorí." + }, + "related": { + "heading": "Súvisiace", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor → Bezpečnostná karta", + "tail": "— spustite audit, prezerajte historické správy a stiahnite si PDF priamo z hlavného panela." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": " — zavádza ochranu SSH pred hrubou silou, ktorú odporúča Lynis." + }, + { + "href": "/docs/security", + "label": "Prehľad bezpečnosti", + "tail": " — Späť na prehľad sekcie." + } + ] + } +} diff --git a/web/messages/sk/docs/security/ssl-letsencrypt.json b/web/messages/sk/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..418a3d3a --- /dev/null +++ b/web/messages/sk/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS pre ProxMenux Monitor — certifikát hostiteľa Proxmox alebo vlastné cesty | ProxMenux", + "description": "Sprístupnite ProxMenux Monitor cez HTTPS s automaticky rozpoznaným certifikátom hostiteľa Proxmox vrátane Let’s Encrypt nainštalovaného cez ACME Proxmox, alebo s vlastným certifikátom na ľubovoľnej ceste na disku.", + "ogTitle": "HTTPS pre ProxMenux Monitor — certifikát hostiteľa Proxmox alebo vlastný certifikát", + "ogDescription": "Povoľte HTTPS na porte 8008 s certifikátom hostiteľa Proxmox alebo vlastnou dvojicou .pem / .key." + }, + "header": { + "title": "HTTPS pre ProxMenux Monitor", + "description": "Zapnite HTTPS pre Monitor na porte 8008 s automaticky rozpoznaným certifikátom hostiteľa Proxmox alebo vlastným certifikátom na disku. Možnosť Proxmox uprednostní certifikát ACME, ak existuje, a znovu použije Let’s Encrypt spravovaný Proxmoxom bez ďalšej úlohy obnovy.", + "section": "Zabezpečenie" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Dva spôsoby, ako umožniť HTTPS", + "proxmox": { + "title": "Použiť Proxmox certifikát", + "summary": "Jedno kliknutie: Monitor použije certifikát, ktorý Proxmox VE poskytuje na porte 8006.", + "items": [ + "Automaticky rozpoznané, bez zadávania ciest", + "Uprednostní certifikát ACME, ak existuje; inak použije predvolený certifikát s vlastným podpisom", + "Proxmox spravuje obnovu; Monitor overí a načíta certifikát pri nasledujúcom novom spojení TLS" + ] + }, + "custom": { + "title": "Použiť vlastný certifikát", + "summaryRich": "Nastavte Monitor tak, aby používal dvojicu .pem / .key uloženú kdekoľvek na disku.", + "items": [ + "Iba absolútne cesty (proces monitora musí byť schopný čítať oboje)", + "Užitočné pre certifikáty z certbot, acme.sh alebo wildcard certifikáty", + "Obnovu spravujete vy: po zmene súborov reštartujte Monitor" + ] + } + }, + "proxmoxCert": { + "heading": "Certifikát Proxmox — na čo skutočne ukazuje", + "intro": "Proxmox VE uchováva dva páry certifikátov v /etc/pve/local/:", + "table": { + "headers": { + "file": "Súbor", + "origin": "Pôvod", + "when": "Keď ho Monitor používa" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Vlastný certifikát nahraný cez rozhranie Proxmox vrátane certifikátu Let’s Encrypt vydaného cez Datacenter → Node → Certificates → ACME.", + "when": "Uprednostní sa, ak existujú oba súbory; Monitor použije certifikát poskytovaný Proxmoxom." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Samopodpísaný certifikát vytvorený v čase inštalácie spoločnosťou Proxmox PKI.", + "whenRich": "Náhradný certifikát, ak pveproxy-ssl.pem neexistuje." + } + ] + }, + "callout": { + "title": "Rovnaká priorita ako samotný Proxmox", + "bodyRich": "pveproxy používa rovnaké pravidlo výberu. Pri možnosti Proxmox Monitor použije certifikát webového rozhrania Proxmox na porte 8006 bez kopírovania a samostatného procesu obnovy." + } + }, + "letsencrypt": { + "heading": "Získanie certifikátu Let's Encrypt cez Proxmox ACME", + "intro": "Proxmox VE obsahuje vlastného klienta ACME. „Order Certificate Now“ uloží výsledok do /etc/pve/local/pveproxy-ssl.pem, ktorý Monitor číta. Nasledujúcich päť krokov zhŕňa oficiálnu príručku Proxmox do potrebných príkazov.", + "prereqs": { + "title": "Predpoklady", + "items": [ + "Uzol má skutočný názov DNS (nielen IP) ukazuje na to.", + "Buď port 80 dostupný z internetu (overenie HTTP-01, najjednoduchšie), alebo poskytovateľ DNS s prihlasovacími údajmi API (DNS-01, funguje za NAT a podporuje wildcard certifikáty).", + "E-mailová adresa pre účet ACME." + ] + }, + "step1": { + "heading": "Krok 1 — Zaregistrujte účet ACME", + "introRich": "Nahraďte e-mail vlastnou adresou. Názov účtu default je iba označenie; môžete použiť ľubovoľný reťazec.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "Proxmox vyžiada súhlas s podmienkami služby Let’s Encrypt. Potom uloží účet do /etc/pve/priv/acme/ a zobrazí ho na karte ACME v grafickom rozhraní." + }, + "step2": { + "heading": "Krok 2 — Vyberte typ overenia", + "http01Rich": "HTTP-01 je najjednoduchší: je zabudovaný, bez ďalšieho pluginu. Let's Encrypt sa pripája k http://<your-host>/.well-known/acme-challenge/... na porte 80, takže port 80 musí byť otvorený z verejného internetu do vášho uzla – zvyčajne port forward na vašom smerovači.", + "dns01Rich": "DNS-01 funguje bez sprístupnenia portu 80, takže je vhodné pre domáce laby za CG-NAT, a je jedinou možnosťou pre wildcard certifikáty. Jednorazovo pridajte DNS doplnok s tokenom API poskytovateľa. Príklad pre Cloudflare:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "Pre iných poskytovateľov (Gandi, OVH, AWS Route 53 atď.) použite pvenode acme plugin add dns --help. Proxmox obsahuje všetky DNS doplnky acme.sh." + }, + "step3": { + "heading": "Krok 3 — Priraďte doménu k uzlu", + "http01Rich": "Pre HTTP-01 (používa vstavaný standalone modul):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "Pre DNS-01 s plugin Cloudflare zo Step 2:", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Wildcard? Pridať domain=*.example.com na samostatnú položku --acmedomainN. Wildcards vyžadujú DNS-01." + }, + "step4": { + "heading": "Krok 4 — Vyžiadajte certifikát", + "code": "pvenode acme cert order", + "afterRich": "Prvý beh zvyčajne trvá 30–60 sekundy, kým sa výzva šíri. O úspechu Proxmox píše /etc/pve/local/pveproxy-ssl.pem + .key, reštartuje pveproxy, a obnovenie cron je naplánované automaticky (Let's Encrypt Certs sú platné 90 dní; Proxmox obnovuje pri 60)." + }, + "step5": { + "heading": "Krok 5 — Overte vydavateľa", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "Výstup má obsahovať vydavateľa začínajúceho O = Let's Encrypt. Ak uvádza CN = Proxmox Virtual Environment, požiadavka zlyhala a súbor je stále certifikát PVE s vlastným podpisom. Príčinu zistíte opätovným spustením pvenode acme cert order --debug 1." + }, + "gui": { + "title": "Preferujete GUI? Rovnakých päť krokov", + "bodyRich": "Datacenter → ACME zaregistruje účet; Datacenter → ACME → Plugins pridá DNS doplnok; Node → Certificates → ACME priradí doménu; Order Certificates Now vykoná krok 4. Uvedené príkazy CLI sú vhodné na skriptovanie a systémy bez grafického rozhrania." + } + }, + "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." + }, + "custom": { + "heading": "Vlastný certifikát — kedy ho použiť", + "intro": "Kliknite na Použiť vlastný certifikát a zadajte dve absolútne cesty:", + "items": [ + "Osvedčenie Celý reťazec v PEM (.pem alebo .crt).", + "Súkromný kľúč: zodpovedajúci kľúč vo formáte PEM (.key)." + ], + "outro": "Oba súbory načíta proces Monitora pri štarte. Musia zostať čitateľné pre používateľa, pod ktorým beží jednotka systemd (predvolene root). Bežné umiestnenia pri správe ACME mimo Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Symlink ciele vs. súbory", + "bodyRich": "certbot ukladá skutočné súbory do /etc/letsencrypt/archive/ a cesta live/ je symbolický odkaz. Nasmerujte monitor na symbolický odkaz live/, nie na súbor archive/ – takto každý cyklus obnovy jednoducho prepíše cieľ symbolického odkazu a monitor si pri ďalšom spustení služby vyberie nový certifikát." + } + }, + "afterHttps": { + "heading": "Po umožnení HTTPS", + "bodyRich": "Uloženie zmeny zapíše /etc/proxmenux/ssl_config.json a reštartuje jednotku systemd proxmenux-monitor. Karta prehliadača stratí spojenie; načítajte ju znova na https://<host>:8008/. Certifikát Let’s Encrypt by nemal vyvolať upozornenie; pri certifikáte Proxmox s vlastným podpisom bude prehliadač vyžadovať výnimku, rovnako ako na porte 8006.", + "reverse": { + "heading": "Reverzné proxy a webhooky", + "bodyRich": "Pri prepnutí Monitora na HTTPS sa adresy webhookov Proxmox VE pre oznámenia automaticky zmenia z http:// na https://. Existujúce záznamy sa prepíšu pri ďalšom uložení alebo opätovnom rozpoznaní pravidla oznámení; ručná úprava konfigurácie oznámení Proxmox nie je potrebná." + } + }, + "trustCa": { + "heading": "Dôverujte samopodpísanej CA spoločnosti Proxmox", + "intro1Rich": "Ak chcete zachovať predvolený certifikát Proxmox bez ACME, importujte koreňovú CA klastra do úložiska dôveryhodných certifikátov systému alebo prehliadača. Monitor rozpozná /etc/pve/local/pve-ssl.pem ako náhradný certifikát. Klient tak dôveruje vydavateľovi; URL musí stále zodpovedať názvu v certifikáte.", + "intro2Rich": "CA súbor žije v /etc/pve/pve-root-ca.pem na každom uzle. V klastri cesta je na zdieľanom pmxcfs súborovom systéme, takže všetky uzly zdieľajú rovnaký koreň a jeden import pokrýva celý dátový stred. Skopírujte ho do svojho stroja:", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Potom ho importovať na strane klienta:", + "items": [ + "Linux (celý systém): spustite sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt a potom sudo update-ca-certificates. Prehliadače používajúce systémové úložisko certifikátov (Chromium, Edge) rozpoznajú CA po reštarte; Firefox má vlastné úložisko a potrebuje ručný import uvedený nižšie.", + "macOS: dvojklikom otvorte súbor .pem v Keychain Access, pridajte ho do kľúčenky System a nastavte Trust → Always Trust.", + "Windows: kliknite pravým tlačidlom na súbor → Inštalovať certifikát → uložte ho do Dôveryhodné koreňové certifikačné autority.", + "Firefox (ľubovoľný OS): Nastavenia → Súkromie a bezpečnosť → Certifikáty → Zobraziť certifikáty → Autority → Importovať; vyberte Dôverovať tejto CA pri identifikácii webových stránok." + ], + "standalone": { + "title": "Samostatné uzly mimo klastra", + "bodyRich": "Každý non-clustered Proxmox hostiteľ má svoje vlastné pve-root-ca.pem. Ak spravujete niekoľko samostatných hostiteľov, opakujte kópiu/dovoz pre každého, alebo sa pripojte k klastru, takže zdieľajú jeden koreň." + } + }, + "disable": { + "heading": "Zastavenie HTTPS", + "bodyRich": "Kliknite na Vypnúť HTTPS v rovnakom paneli. Monitor sa reštartuje s nešifrovaným HTTP, čo pomôže pri nečitateľnom vlastnom certifikáte brániacom štartu služby. Samostatné odinštalovanie nie je potrebné a súbory certifikátov sa nemenia.", + "stateCallout": { + "title": "Kde sa ukladá stav", + "bodyRich": "/etc/proxmenux/ssl_config.json ukladá aktívne cesty k certifikátom a zdroj (none, proxmox, custom). Odstránenie súboru zodpovedá vypnutiu HTTPS; pri ďalšom štarte Monitor použije HTTP." + } + } +} diff --git a/web/messages/sv/docs/security/fail2ban.json b/web/messages/sv/docs/security/fail2ban.json new file mode 100644 index 00000000..ce691098 --- /dev/null +++ b/web/messages/sv/docs/security/fail2ban.json @@ -0,0 +1,152 @@ +{ + "meta": { + "title": "Fail2Ban | ProxMenux-dokumentation", + "description": "Installera Fail2Ban med tre jails inställd för Proxmox (SSH aggressiv, Proxmox UI 8006, ProxMenux Monitor 8008). Inkluderar journald MaxLevelStore fix, anpassade loggtjänster för tillförlitlighet, automatiskt upptäckta nftables/iptables backend och SSH MaxAuthTries härdning.", + "ogTitle": "Fail2Ban | ProxMenux-dokumentation", + "ogDescription": "Skydd mot lösenordsattacker för SSH och Proxmox webbgränssnitt, med Proxmox-specifika korrigeringar av journald och backend." + }, + "header": { + "title": "Fail2Ban", + "description": "Installerar Fail2Ban för Proxmox: tre jails (aggressivt SSH, Proxmox-gränssnitt på 8006, ProxMenux Monitor på 8008 och omvänd proxy), journald-korrigering för att spara SSH-händelser, två tjänster som kopierar journalen till filer för att kringgå ett känt systemd-backendproblem, automatisk brandväggsbackend och SSH-härdning med MaxAuthTries.", + "section": "Säkerhet" + }, + "intro": { + "title": "Vad detta gör", + "body": "Installerar Fail2Ban från Debians paketförråd och skapar en Proxmox-konfiguration som direkt skyddar SSH, Proxmox webbgränssnitt och ProxMenux Monitor. En befintlig installation upptäcks och ger i stället en hanteringsmeny för ominstallation eller borttagning." + }, + "firstLaunch": { + "heading": "Första lanseringen dialog", + "body": "Om Fail2Ban saknas visar skriptet en bekräftelse med vad som ska installeras och konfigureras. Avbryt avslutar utan ändringar; bekräftelse startar installationen.", + "imageAlt": "Installationsbekräftelse för Fail2Ban med tre jails, journald-korrigering och SSH-härdning" + }, + "jails": { + "heading": "De tre jails", + "headerJail": "Jail", + "headerProtects": "Skyddar", + "headerRetries": "Försök / Tidsfönster", + "headerBan": "Blockeringstid", + "rows": [ + { + "jail": "[sshd]", + "protects": "SSH (aggressivt läge — omfattar ddos, mode, normal)", + "retries": "2 / 60 min", + "ban": "9 timmar" + }, + { + "jail": "[proxmox]", + "protects": "Proxmox webb UI (port 8006)", + "retries": "3 / 10 min", + "ban": "1 timme" + }, + { + "jail": "[proxmenux]", + "protects": "ProxMenux Monitor (port 8008 + http / https omvänd proxy)", + "retries": "3 / 10 min", + "ban": "1 timme" + } + ], + "outro": "Globala standardinställningar från jail.local: ignoreip = 127.0.0.1/8 ::1, ignoreself = true, bantime = 86400 (24h reserv för jails som inte åsidosätter det), maxretry = 2, findtime = 1800." + }, + "journald": { + "heading": "Varför journald-korrigeringen är viktig", + "intro": "Proxmox levererar /etc/systemd/journald.conf med MaxLevelStore=warning. journald kastar meddelanden på nivåer under warning innan de lagras. SSH och PAM rapporterar autentiseringsfel på nivåerna info / notice, så:", + "diagram": { + "sshLabel": "SSH / PAM", + "sshDetail": "autentiseringsfel\n(nivå info / notice)", + "journaldLabel": "journald", + "journaldDetail": "MaxLevelStore=warning\n→ händelsen kastas utan varning", + "fail2banLabel": "Fail2Ban", + "fail2banDetail": "ser ingenting\naldrig förbjuder något", + "arrowLabel": "Standard Proxmox" + }, + "afterDiagram": "Installatören upptäcker detta och skriver en drop-in på /etc/systemd/journald.conf.d/proxmenux-loglevel.conf höja både MaxLevelStore och MaxLevelSyslog till info:", + "code": "[Journal]\nMaxLevelStore=info\nMaxLevelSyslog=info", + "outro": "Startar sedan om systemd-journald. Vid avinstallation tas tillägget bort och Proxmox ursprungliga standardvärde återställs." + }, + "loggers": { + "heading": "Varför två anpassade loggertjänster", + "intro1": "Fail2Ban kan läsa systemd-journalen direkt (backend = systemd), men på Proxmox har denna backend kända problem med arbetsprocesser i pvedaemon: autentiseringshändelser syns i journalen men upptäcks inte alltid. Även sshd berörs ibland.", + "intro2": "ProxMenux skapar två små systemd-tjänster som följer berörda enheter med journalctl -f och lägger till varje rad i en fil. Fail2Ban läser filerna med tillförlitliga backend = auto (filläge):", + "headerService": "Tjänst", + "headerSource": "Källenhet", + "headerOutput": "Utdatafil", + "rows": [ + { + "service": "proxmox-auth-logger.service", + "source": "pvedaemon.service", + "output": "/var/log/proxmox-auth.log" + }, + { + "service": "ssh-auth-logger.service", + "source": "ssh.service", + "output": "/var/log/ssh-auth.log" + } + ], + "outro": "Båda tjänsterna är deklarerade PartOf=fail2ban.service så de startar om med Fail2Ban och slutar med det. Läget 640 som ägs av root:adm på loggfilerna. Den tredje loggen som används av [proxmenux] jail (/var/log/proxmenux-auth.log) skrivs direkt av appen ProxMenux Monitor Flask – ingen loggertjänst behövs för den." + }, + "backend": { + "heading": "Automatisk identifiering av brandväggsbackend", + "intro": "Installationsproberar värden: om nft list ruleset fungerar, väljer den nftables som förbudsåtgärden. Annars faller det tillbaka till iptables-multiport / iptables-allports. Valet är skrivet i jail.local:", + "code": "# nftables host\nbanaction = nftables\nbanaction_allports = nftables[type=allports]\n\n# iptables host (fallback)\nbanaction = iptables-multiport\nbanaction_allports = iptables-allports" + }, + "hardening": { + "heading": "SSH-härdning: MaxAuthTries", + "intro": "Lynis-kontrollen SSH-7408 rekommenderar MaxAuthTries 3 i sshd_config. SSH-jailen använder maxretry = 2 för blockering efter upprepade fel; MaxAuthTries begränsar försök inom samma anslutning. SSH-inställningen ger ett extra skyddslager även när Fail2Ban stoppats för underhåll.", + "installerIntro": "Installationsprogrammet:", + "items": [ + "Läser det aktuella MaxAuthTries-värdet (eller standarder till 6 om det saknas).", + "Spara det till /usr/local/share/proxmenux/sshd_maxauthtries_backup.", + "Redigerar sshd_config direkt: ersätter befintlig rad, avkommenterar en kommenterad rad eller lägger till en ny.", + "Laddar om sshd utan omstart så att befintliga sessioner behålls." + ], + "outro": "Vid avinstallation återställs det sparade ursprungliga värdet och sshd laddas om igen." + }, + "manage": { + "heading": "Hantera en befintlig installation", + "intro": "Om Fail2Ban redan är installerad när du öppnar menyn upptäcker manuset den och visar en meny istället för att köra om installationsprogrammet:", + "headerAction": "Handling", + "headerWhat": "Vad det gör", + "rows": [ + { + "action": "Reinstallera", + "what": "Kör hela installationen igen och skriver om alla jails med aktuella ProxMenux-standardvärden. Använd när en uppdatering ändrar rekommendationerna." + }, + { + "action": "Ta bort", + "what": "Stoppar fail2ban och de två loggertjänsterna, rensar apt-paketet, tar bort alla jail / filtrera filer, tar bort journald drop-in (återställer Proxmox standard) och återställer originalet SSH MaxAuthTries." + } + ] + }, + "verify": { + "heading": "Verifiera att det fungerar", + "intro": "Efter installation, användbara kommandon från värden:", + "code": "# Service status and version\nsystemctl status fail2ban\nfail2ban-client --version\n\n# All active jails\nfail2ban-client status\n\n# Detail on one jail\nfail2ban-client status sshd\nfail2ban-client status proxmox\nfail2ban-client status proxmenux\n\n# Currently banned IPs in a jail\nfail2ban-client status sshd | grep \"Banned IP\"\n\n# Manually unban an IP (use this if you ban yourself)\nfail2ban-client unban 192.0.2.10\n\n# Tail the auth logs Fail2Ban watches\ntail -f /var/log/ssh-auth.log\ntail -f /var/log/proxmox-auth.log\ntail -f /var/log/fail2ban.log" + }, + "troubleshoot": { + "heading": "Felsökning", + "neverBansTitle": "Fail2Ban körs men förbjuder aldrig något", + "neverBansBody": "Kontrollera att tail -f /var/log/ssh-auth.log får en post efter ett SSH-försök med fel lösenord från en annan dator. Om filen är tom granskar du systemctl status ssh-auth-logger.service. Om loggern körs visar du aktuella händelser med journalctl -u ssh.service -n 50 --no-pager -p info och kontrollerar att journald-tillägget tillåter nivån info.", + "monitorEmptyTitle": "ProxMenux Monitor jail har inga poster även efter misslyckade inloggningar", + "monitorEmptyBody": "Jailen [proxmenux] läser /var/log/proxmenux-auth.log, som skrivs av Flask-appen ProxMenux Monitor, inte av en journald-logger. Utan Monitor förblir filen tom och jailen utlöses inte. Det är väntat och konfigurationen är ofarlig. Om Monitor körs men loggen är tom kontrollerar du loggningskonfigurationen i Flask.", + "selfBanTitle": "Jag har blockerat min egen åtkomst", + "selfBanIntro": "Från en konsol / IPMI / iKVM (eller annan vitlistad IP):", + "selfBanCode": "fail2ban-client unban '<'YOUR_IP'>'\n\n# To prevent it next time, edit /etc/fail2ban/jail.local and add your IP:\nignoreip = 127.0.0.1/8 ::1 192.168.1.0/24 203.0.113.42\n\n# Then reload\nfail2ban-client reload", + "aptFailTitle": "apt-get misslyckas: ''Unable to locate package fail2ban''", + "aptFailBody": "Värden saknas Debian repos (gemensamt på barebones Proxmox installationer). Installatören upptäcker detta och skriver /etc/apt/sources.list.d/debian.sources med rätt kodnamn (bookworm / trixie) innan du går tillbaka. Om det fortfarande misslyckas, kontrollera /etc/os-release för VERSION_CODENAME och bekräfta att repo URL är tillgänglig.", + "lockoutTitle": "SSH låser mig efter installation", + "lockoutBody": "Installationsprogrammet sätter MaxAuthTries=3. Om din lösenordshanterare / agent har flera nycklar kan du överstiga det på ett enda anslutningsförsök. Begränsa nycklarna som erbjuds: ssh -o IdentitiesOnly=yes -i ~/.ssh/specific_key user@host. Eller tillfälligt höja MaxAuthTries i sshd_config medan du debug." + }, + "files": { + "heading": "Filer skrivna", + "code": "/etc/fail2ban/jail.local # global defaults + [sshd]\n/etc/fail2ban/jail.d/proxmox.conf # [proxmox]\n/etc/fail2ban/jail.d/proxmenux.conf # [proxmenux]\n/etc/fail2ban/filter.d/proxmox.conf # auth-failure regex for pvedaemon\n/etc/fail2ban/filter.d/proxmenux.conf # auth-failure regex for Monitor\n/etc/systemd/system/proxmox-auth-logger.service # journal → file (pvedaemon)\n/etc/systemd/system/ssh-auth-logger.service # journal → file (sshd)\n/etc/systemd/journald.conf.d/proxmenux-loglevel.conf # MaxLevelStore=info\n/etc/ssh/sshd_config # MaxAuthTries=3 (in-place edit)\n/var/log/proxmox-auth.log # written by logger service\n/var/log/ssh-auth.log # written by logger service\n/var/log/proxmenux-auth.log # written by Monitor Flask app\n/usr/local/share/proxmenux/sshd_maxauthtries_backup # for restore on uninstall" + }, + "related": { + "heading": "Relaterad", + "monitorLabel": "ProxMenux Monitor → Säkerhetsflik", + "monitorTail": " — installation via panelen, aktuell jail-status, blockerade IP-adresser och inställning av försök och blockeringstid per jail i webbläsaren.", + "lynisLabel": "Lynis", + "lynisTail": " — kör en säkerhetsgranskning före och efter för att bekräfta att kontrollen SSH-7408 uppfylls.", + "securityLabel": "Säkerhetsöversikt", + "securityTail": " - tillbaka till avsnittsöversikten." + } +} diff --git a/web/messages/sv/docs/security/index.json b/web/messages/sv/docs/security/index.json new file mode 100644 index 00000000..bf05f49e --- /dev/null +++ b/web/messages/sv/docs/security/index.json @@ -0,0 +1,70 @@ +{ + "meta": { + "title": "Proxmox-säkerhet — Fail2Ban och härdningsgranskning med Lynis | ProxMenux", + "description": "Två säkerhetsverktyg för Proxmox VE: Fail2Ban (intrångsskydd för SSH, Proxmox webbgränssnitt och ProxMenux Monitor) och Lynis (säkerhets- och härdningsgranskning installerad från det officiella GitHub-arkivet).", + "ogTitle": "Proxmox-säkerhet — Fail2Ban och härdningsgranskning med Lynis", + "ogDescription": "Fail2Ban och Lynis för Proxmox VE: intrångsskydd och härdningsgranskning, installerade och hanterade av ProxMenux.", + "twitterTitle": "Proxmox säkerhet | ProxMenux", + "twitterDescription": "Fail2Ban och Lynis för Proxmox VE: intrångsskydd och härdningsgranskning." + }, + "header": { + "title": "Säkerhet", + "description": "Två kompletterande verktyg för Proxmox VE: aktivt skydd (Fail2Ban blockerar IP-adresser som angriper SSH eller webbgränssnitt) och lokal granskning (Lynis söker härdningsbrister och ger 0–100 poäng). Varje verktyg har en egen installations- och hanteringsmeny som upptäcker befintliga installationer före varje åtgärd.", + "section": "Säkerhet" + }, + "intro": { + "title": "Aktivt skydd och lokal granskning", + "body": "De besvarar olika frågor. Fail2Ban svarar på ”Angrips vi just nu?” genom att bevaka autentiseringsloggar och blockera angripande IP-adresser. Lynis svarar på ”Hur väl härdad är värden?” med en engångsgranskning och konkreta rekommendationer. Använd båda: Lynis visar vad som bör förbättras och Fail2Ban hanterar pågående angrepp." + }, + "opening": { + "heading": "Öppna menyn", + "body": "Välj Säkerhet i ProxMenux huvudmeny. Du får se följande:", + "imageAlt": "Säkerhetsmeny med Fail2Ban och Lynis alternativ" + }, + "pick": { + "heading": "Välj ditt verktyg", + "body": "Verktygen är oberoende: installera ett, båda eller inget. Varje kort nedan leder till avsnittet som förklarar verktyget i detalj." + }, + "cards": { + "fail2ban": { + "title": "Fail2Ban", + "body": "Aktivt intrångsskydd. Bevakar autentiseringsloggar för SSH och webbgränssnitt och blockerar IP-adresser efter upprepade fel.", + "bullets": [ + "Tre jails: SSH, Proxmox UI (8006), ProxMenux Monitor (8008)", + "Korrigerar Proxmox standardinställningar för journald som blockerar autentiseringsloggar", + "Upptäcker nftables / iptables automatiskt", + "SSH härdning: MaxAuthTries=3 (Lynis rekommendation)" + ] + }, + "lynis": { + "title": "Lynis", + "body": "Lokal säkerhetsgranskare. Skannar värden och visar härdningspoäng samt konkreta åtgärdsförslag.", + "bullets": [ + "Installerat från CISOfys officiella GitHub-arkiv (senaste versionen)", + "Härdningspoäng 0–100 och lista över varningar och förslag", + "Granskning och uppdatering via menyn", + "Utformat för enbart läsning: ändrar aldrig systemet" + ] + } + }, + "workflowTip": { + "title": "Arbetsflöde som använder båda", + "body": "Kör först Lynis för att se startpoäng och viktigaste rekommendationer. Genomför önskade åtgärder. Installera sedan Fail2Ban: det inför automatiskt SSH-skydd mot lösenordsattacker och anpassar MaxAuthTries i sshd_config till kontrollen SSH-7408. Kör Lynis igen för att bekräfta förbättringen." + }, + "fail2banSection": { + "heading": "Fail2Ban", + "body": "ProxMenux installerar Fail2Ban med en konfiguration för Proxmox. Utöver standardjailen för SSH skyddas Proxmox webbgränssnitt och ProxMenux Monitor. Två specifika problem hanteras: en standardinställning i journald som släpper autentiseringshändelser och ett problem i systemd-backend som hindrar tillförlitlig läsning av vissa journalkällor. Detaljsidan beskriver installationen, de tre jails, journald-korrigeringen, SSH-härdningen och felsökningen.", + "optionTitle": "Fail2Ban – installera och hantera", + "optionDescription": "Tre förkonfigurerade jails (aggressivt sshd, Proxmox-gränssnitt på 8006, ProxMenux Monitor på 8008 och omvänd proxy), automatisk nftables/iptables-detektering, journald-korrigering och SSH-härdning med MaxAuthTries." + }, + "lynisSection": { + "heading": "Lynis", + "body": "ProxMenux klonar Lynis från github.com/CISOfy/lynis till /opt/lynis och gör det tillgängligt som /usr/local/bin/lynis. Debianpaketet undviks avsiktligt eftersom det ligger flera huvudversioner efter originalprojektet. Detaljsidan beskriver installation, granskning, uppdatering, avinstallation, rapportläsning och åtgärder utifrån resultaten.", + "optionTitle": "Lynis — installera, granska och hantera", + "optionDescription": "Installerar den senaste Lynis från den officiella CISOfy GitHub repo (inte det äldre Debian-paketet), kör systemrevisioner med härdningspoäng och uppdateringar via git pull." + }, + "componentStatus": { + "heading": "Komponentstatus", + "body": "Båda installatörerna registrerar sitt tillstånd i /usr/local/share/proxmenux/components_status.json under kategorin security. ProxMenux använder den här filen för att bestämma om du vill visa installationen eller menyn för hantering påföljande körningar, och samma data matar instrumentpanelerna i ProxMenux Monitor när de är närvarande." + } +} diff --git a/web/messages/sv/docs/security/lynis.json b/web/messages/sv/docs/security/lynis.json new file mode 100644 index 00000000..2eb520b4 --- /dev/null +++ b/web/messages/sv/docs/security/lynis.json @@ -0,0 +1,170 @@ +{ + "meta": { + "title": "Lynis | ProxMenux-dokumentation", + "description": "Installera senaste Lynis från CISOfys officiella GitHub-arkiv, kör en säkerhetsgranskning med härdningspoäng 0–100 och uppdatera med git pull. Utformat för enbart läsning, utan systemändringar.", + "ogTitle": "Lynis | ProxMenux-dokumentation", + "ogDescription": "Installera och kör säkerhetsgranskaren Lynis på Proxmox VE. Installation från det officiella GitHub-arkivet, granskning och uppdatering." + }, + "header": { + "title": "Lynis", + "description": "Klonar senaste Lynis från CISOfys officiella GitHub-arkiv, gör det tillgängligt som /usr/local/bin/lynis och erbjuder granskning, uppdatering, ominstallation och avinstallation via menyn. Granskaren läser bara och rapporterar utan att ändra systemet.", + "section": "Säkerhet" + }, + "intro": { + "title": "Vad detta gör", + "body": "Installerar Lynis, CISOfys säkerhetsgranskare med öppen källkod, från dess GitHub-arkiv till /opt/lynis och skapar ett startskript i /usr/local/bin/lynis så att det nås via PATH. En befintlig installation ger i stället en meny för granskning, uppdatering, ominstallation eller borttagning." + }, + "manageMenu": { + "heading": "Hanteringsmeny (efter installation)", + "intro": "Efter installationen öppnar varje nytt anrop hanteringsmenyn. Där kan du starta en granskning, uppdatera med git pull, installera om eller avinstallera.", + "imageAlt": "Lynis management meny med revision, uppdatering, ominstallera och ta bort alternativ" + }, + "whyUpstream": { + "heading": "Varför det officiella GitHub-arkivet i stället för apt", + "intro": "Debian erbjuder Lynis via apt, men paketet ligger vanligtvis flera huvudversioner efter originalprojektet. Nya kontroller, åtgärder för nya angreppssätt och förbättrade rekommendationer kommer först till GitHub. Lynis är ett fristående skalskript utan kompilering eller installationsberoenden utöver git; kloning av arkivet är därför metoden CISOfy rekommenderar.", + "headerSource": "Källa", + "headerPath": "Install path", + "headerUpdate": "Uppdateringsmetod", + "headerFresh": "Versionens aktualitet", + "rows": [ + { + "sourceRich": "ProxMenux (detta manus) ", + "path": "/opt/lynis/", + "update": "git pull (via menyn)", + "fresh": "Senaste versionen av originalprojektet" + }, + { + "sourceRich": "Debian apt", + "path": "/usr/bin/lynis", + "update": "apt upgrade", + "fresh": "Ofta månader / år bakom" + } + ] + }, + "install": { + "heading": "Hur installationen fungerar", + "node1Label": "github.com/CISOfy/lynis", + "node1Detail": "git clone\n(installera git först om det saknas)", + "node2Label": "/opt/lynis/", + "node2Detail": "hela arkivet\ninklusive skriptet ./lynis", + "node3Label": "/usr/local/bin/lynis", + "node3Detail": "wrapper:\ncd /opt/lynis && ./lynis $@", + "outro": "Wrapper är obligatorisk - Lynis insisterar på att köras från sin egen katalog eftersom det laddar relativa vägar för plugins och profildata. Wrapper döljer den detaljen så lynis audit system fungerar bara från var som helst." + }, + "detection": { + "heading": "Detektering vägar", + "intro": "Innan du visar menyn kontrollerar manuset tre platser för att avgöra om Lynis redan är närvarande:", + "items": [ + "/usr/local/bin/lynis Wrapper installerad av ProxMenux", + "/opt/lynis/lynis - direkt väg (om wrapperen togs bort)", + "/usr/bin/lynis apt-installerad version, om användaren installerade den på det sättet tidigare" + ], + "outro": "Om någon av sökvägarna hittas öppnas hanteringsmenyn. Skriptet avinstallerar inte Lynis som installerats via apt, bara den egna installationen i /opt/lynis och dess startskript." + }, + "audit": { + "heading": "Kör en revision", + "intro": "Välj Kör säkerhetsgranskning nu i hanteringsmenyn. Det motsvarar att köra:", + "code": "lynis audit system --no-colors", + "outro": "Granskningen tar 30 sekunder till några minuter beroende på värdens storlek. Utdata visas direkt i terminalen utan väntanimation. Lynis grupperar kontroller efter kategori och märker varje test med [ OK ], [ WARNING ] eller [ SUGGESTION ]. Slutets sammanfattning visar de viktigaste siffrorna:", + "summary": "================================================================================\n\n Detaljer för Lynis säkerhetsgranskning:\n\n Härdningsindex : 76 [############ ]\n Utförda tester : 247\n Aktiverade insticksmoduler : 0\n\n Komponenter:\n - Brandvägg [V]\n - Skanner för skadlig kod [X]\n\n Lynis-moduler:\n - Efterlevnadsstatus [?]\n - Säkerhetsgranskning [V]\n - Sårbarhetsgranskning [V]\n\n================================================================================" + }, + "report": { + "heading": "Läsa rapporten", + "intro": "De två viktiga avsnitten är Warnings (saker du bör fixa snart) och Suggestions (rekommendationer för att förbättra härdning). Varje objekt bär ett kontroll-ID som SSH-7408 - användbart när du söker Lynis-dokumenten för motiveringen och fixen.", + "headerMarker": "Markör", + "headerMeaning": "Betydelse", + "headerAction": "Handling", + "rows": [ + { + "markerRich": "OK", + "meaning": "Test passerade", + "action": "Ingenting" + }, + { + "markerRich": "WARNING", + "meaning": "Verklig fråga, fix rekommenderad", + "action": "Läs kontrollbeskrivningen, planera en fix" + }, + { + "markerRich": "SUGGESTION", + "meaning": "Hardening förbättring tillgänglig", + "action": "Tillämpa om det passar din hotmodell" + } + ], + "outro": "Fullständig rapport och maskinläsbara data skrivs till /var/log/lynis.log och /var/log/lynis-report.dat av Lynis själv." + }, + "pairFail2ban": { + "title": "Kombinera med Fail2Ban", + "body": "SSH-7408 (MaxAuthTries) är en av de vanligaste varningarna. Installera Fail2Ban från ProxMenux rensar det automatiskt eftersom installationsprogrammet sätter MaxAuthTries=3 som en del av dess SSH härdning steg. Kör Lynis igen efteråt för att bekräfta varningen är borta." + }, + "update": { + "heading": "Uppdatering Lynis", + "body": "I menyn kör Uppdatera Lynis till senaste versionen kommandot git pull --quiet i /opt/lynis. Om katalogen finns men inte är en Git-arbetskopia, exempelvis efter manuell filkopiering, gör skriptet en fullständig ominstallation." + }, + "reinstall": { + "heading": "Installera om / avinstallera", + "headerAction": "Handling", + "headerWhat": "Vad det gör", + "rows": [ + { + "actionRich": "Installera om", + "whatRich": "Tar bort /opt/lynis, klonar igen från GitHub och återskapar startskriptet. Använd om den lokala arbetskopian är skadad." + }, + { + "actionRich": "Ta bort", + "whatRich": "Ta bort /opt/lynis och /usr/local/bin/lynis. Loggar på /var/log/lynis* hålls (de är revisionshistorik). En apt-installerad Lynis på /usr/bin/lynis lämnas orörd." + } + ] + }, + "cli": { + "heading": "Användbara CLI-alternativ", + "intro": "Efter installationen kan Lynis köras direkt från ett skal med extra alternativ som inte visas i menyn:", + "code": "lynis show version # version + build date\nlynis show commands # list every available command\nlynis show details TEST-ID # explain a specific control\nlynis audit system --quick # skip slow tests (e.g. malware scan)\nlynis audit system --pentest # treat host as untrusted (more aggressive)\nlynis update info # check if a newer Lynis is available\n\n# Filter the report log for just the warnings\ngrep \"Warning\" /var/log/lynis.log\n\n# Same, for suggestions only\ngrep \"Suggestion\" /var/log/lynis.log" + }, + "troubleshoot": { + "heading": "Felsökning", + "cloneTitle": "git clone misslyckas under installationen", + "cloneBody": "Värden behöver nätverksåtkomst och DNS för att nå github.com. Från en konsol: curl -sI https://github.com och git ls-remote https://github.com/CISOfy/lynis.git kommer att avslöja det faktiska felet (DNS, TLS, proxy, repo URL). Om en proxy krävs, sätt https_proxy i miljön innan du kör menyn.", + "notFoundTitle": "lynis: kommandot som inte hittats efter installation", + "notFoundIntro": "Startskriptet /usr/local/bin/lynis skapades inte eller har tagits bort. Snabbaste lösningen är att installera om via menyn. Manuell lösning:", + "notFoundCode": "cat > /usr/local/bin/lynis <<'EOF'\n#!/bin/bash\ncd /opt/lynis && ./lynis \"$@\"\nEOF\nchmod +x /usr/local/bin/lynis", + "sshTitle": "Granskningen visar \"Warning: Test SSH-7408 — MaxAuthTries set incorrectly\"", + "sshIntro": "SSH daemon tillåter för många auth försök per anslutning. Antingen installera Fail2Ban från ProxMenux (som sätter MaxAuthTries=3 automatiskt) eller redigera /etc/ssh/sshd_config för hand:", + "sshCode": "sed -i 's/^#?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config\nsystemctl reload sshd", + "scoreTitle": "Score gick ner efter en Proxmox uppgradering", + "scoreBody": "Kör lynis update info först - en Lynis-uppdatering kan ha lagt till nya kontroller som flaggar befintlig konfig. Uppdatera Lynis från menyn, re-audit och adressera de nya resultaten." + }, + "files": { + "heading": "Filer skrivna", + "code": "/opt/lynis/ # full Lynis git checkout\n/usr/local/bin/lynis # wrapper script (cd + exec)\n/var/log/lynis.log # human-readable audit log (Lynis itself)\n/var/log/lynis-report.dat # machine-readable report (Lynis itself)" + }, + "sample": { + "heading": "Provrapport", + "intro": "ProxMenux Monitor skapar en flersidig PDF för varje Lynis-körning via fliken Säkerhet. Första sidan sammanfattar härdningspoäng, systeminformation och säkerhetsläge. Följande sidor förklarar varje varning, listar förslag efter påverkan och visar paketinventeringen som användes vid granskningen.", + "imageAlt": "Första sidan av Lynis Security Audit Report PDF - verkställande sammanfattning med Hardening Index 71/100, systeminformationsblock och säkerhetsställningsöversikt med brandvägg, malware scanner och installerade paket räknas", + "captionPrefix": "Första sidan i en exempelrapport. Hela PDF-filen (", + "captionLink": "exempel", + "captionSuffix": ") fortsätter med detaljerade varningar, förslag och listan över installerade paket.", + "cli": "På CLI-sidan är samma data i /var/log/lynis-report.dat (maskinläsbar plattfil) och /var/log/lynis.log (den mänskliga läsbara körloggen). PDF genereras på begäran av ProxMenux Monitor - kör lynis från kommandoraden producerar inte en." + }, + "related": { + "heading": "Relaterad", + "items": [ + { + "href": "/docs/monitor/dashboard/security", + "label": "ProxMenux Monitor → Säkerhetsflik", + "tail": " - köra revisionen, bläddra i historiska rapporter och ladda ner PDF direkt från instrumentbrädan." + }, + { + "href": "/docs/security/fail2ban", + "label": "Fail2Ban", + "tailRich": " - implementerar SSH brute-force skydd som Lynis rekommenderar." + }, + { + "href": "/docs/security", + "label": "Säkerhetsöversikt", + "tail": " - tillbaka till avsnittsöversikten." + } + ] + } +} diff --git a/web/messages/sv/docs/security/ssl-letsencrypt.json b/web/messages/sv/docs/security/ssl-letsencrypt.json new file mode 100644 index 00000000..7d11d70d --- /dev/null +++ b/web/messages/sv/docs/security/ssl-letsencrypt.json @@ -0,0 +1,169 @@ +{ + "meta": { + "title": "HTTPS för ProxMenux Monitor — Proxmox-värdcertifikat eller anpassade sökvägar | ProxMenux", + "description": "Tillhandahåll ProxMenux Monitor via HTTPS med Proxmox-värdens automatiskt upptäckta certifikat, inklusive Let’s Encrypt installerat via Proxmox ACME, eller ett eget certifikat på valfri sökväg på disken.", + "ogTitle": "HTTPS för ProxMenux Monitor - Proxmox värd eller anpassat certifikat", + "ogDescription": "Aktivera HTTPS på port 8008 med Proxmox värdcertifikat eller någon anpassad .pem / .key par." + }, + "header": { + "title": "HTTPS för ProxMenux Monitor", + "description": "Aktivera HTTPS för Monitor på port 8008 med det automatiskt upptäckta Proxmox-värdcertifikatet eller ett eget certifikat på disken. Proxmox-alternativet föredrar ACME-certifikatet om det finns och återanvänder Let’s Encrypt som Proxmox hanterar utan en extra förnyelseuppgift.", + "section": "Säkerhet" + }, + "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." + }, + "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." + }, + "twoways": { + "heading": "Två sätt att aktivera HTTPS", + "proxmox": { + "title": "Använd Proxmox-certifikat", + "summary": "Ett klick: Monitor återanvänder certifikatet som Proxmox VE presenterar på port 8006.", + "items": [ + "Upptäcks automatiskt, ingen sökväg att skriva", + "Föredrar ACME-certifikatet om det finns, annars det självsignerade standardcertifikatet", + "Proxmox sköter förnyelsen; Monitor validerar och laddar certifikatet vid nästa nya TLS-anslutning" + ] + }, + "custom": { + "title": "Använd anpassat certifikat", + "summaryRich": "Peka monitorn på ett .pem / .key-par var som helst på disken.", + "items": [ + "Absoluta vägar (övervakningsprocessen måste kunna läsa båda)", + "Användbara när certerna kommer från certbot, acme.sh eller ett wildcard", + "Du ansvarar för förnyelsen: starta om Monitor när filerna ändras" + ] + } + }, + "proxmoxCert": { + "heading": "Proxmox-certifikat - vad det faktiskt pekar på", + "intro": "Proxmox VE håller två certifikatpar i /etc/pve/local/:", + "table": { + "headers": { + "file": "Fil", + "origin": "Ursprung", + "when": "När monitorn använder den" + }, + "rows": [ + { + "fileRich": "pveproxy-ssl.pem
pveproxy-ssl.key", + "originRich": "Anpassade certifikat som laddats upp via Proxmox UI - inklusive Let's Encrypt-certifikatet som utfärdats under Datacenter → Node → Certifikat → ACME.", + "when": "Föredras när båda filerna finns; Monitor använder certifikatet som Proxmox presenterar." + }, + { + "fileRich": "pve-ssl.pem
pve-ssl.key", + "originRich": "Självsignerat certifikat som genereras vid installationstid av Proxmox PKI.", + "whenRich": "Fallback när ingen pveproxy-ssl.pem finns." + } + ] + }, + "callout": { + "title": "Samma prioritet som Proxmox själv", + "bodyRich": "pveproxy använder samma urvalsregel. Med Proxmox-alternativet använder Monitor certifikatet som Proxmox webbgränssnitt presenterar på 8006, utan kopiering eller separat förnyelseflöde." + } + }, + "letsencrypt": { + "heading": "Få ett Let's Encrypt-certifikat via Proxmox ACME", + "intro": "Proxmox VE innehåller en egen ACME-klient. ”Order Certificate Now” sparar resultatet i /etc/pve/local/pveproxy-ssl.pem, som Monitor läser. Följande fem steg sammanfattar Proxmox officiella guide i kommandona som behövs.", + "prereqs": { + "title": "Förutsättningar", + "items": [ + "Noden har ett riktigt DNS-namn (inte bara en IP) som pekar på det.", + "Antingen port 80 nåbar från Internet (HTTP-01-validering, enklast), eller en DNS-leverantör med API-uppgifter (DNS-01, fungerar bakom NAT och stöder wildcard-certifikat).", + "En e-postadress för ACME-kontot." + ] + }, + "step1": { + "heading": "Steg 1 - Registrera ACME-kontot", + "introRich": "Ersätt e-post med din. Kontonamnet default är bara en etikett - du kan använda någon sträng.", + "code": "pvenode acme account register default you@example.com", + "afterRich": "Proxmox uppmanar dig att acceptera Let's Encrypt användarvillkor. Därefter kvarstår kontot under /etc/pve/priv/acme/ och fliken ACME i GUI kommer att visa det." + }, + "step2": { + "heading": "Steg 2 — Välj valideringstyp", + "http01Rich": "HTTP-01 är den enklaste: den är inbyggd, ingen extra plugin. Let's Encrypt ansluter till http://<your-host>/.well-known/acme-challenge/... på port 80, så port 80 måste vara öppen från det offentliga internet till din nod - vanligtvis en port framåt på din router.", + "dns01Rich": "DNS-01 fungerar utan att exponera port 80 (bra för homelabs bakom CG-NAT) och är det enda alternativet för wildcard certifikat. Du lägger till en DNS plugin en gång med din leverantörs API-token. Cloudflare exempel:", + "code": "pvenode acme plugin add dns cf \\\n --api cloudflare \\\n --data CF_Token=YOUR_CLOUDFLARE_API_TOKEN", + "outroRich": "För andra leverantörer (Gandi, OVH, AWS Route 53 osv.) se pvenode acme plugin add dns --help. Proxmox innehåller alla DNS-insticksmoduler från acme.sh." + }, + "step3": { + "heading": "Steg 3 - Bind domänen till din nod", + "http01Rich": "För HTTP-01 (använder den inbyggda standalone plugin):", + "code1": "pvenode config set --acme domains=host.example.com,account=default", + "dns01": "För DNS-01 med Cloudflare plugin från Step 2:", + "code2": "pvenode config set --acmedomain0 domain=host.example.com,plugin=cf\npvenode config set --acme account=default", + "wildcardRich": "Wildcard? Lägg till domain=*.example.com på en separat --acmedomainN-post. Wildcards kräver DNS-01." + }, + "step4": { + "heading": "Steg 4 - Beställ certifikatet", + "code": "pvenode acme cert order", + "afterRich": "Första körningen tar vanligtvis 30-60 sekunder medan utmaningen sprider sig. På framgång Proxmox skriver /etc/pve/local/pveproxy-ssl.pem + .key, startar pveproxy, och en förnyelsekrona schemaläggs automatiskt (Let's Encrypt certs är giltiga 90 dagar; Proxmox förnyar på 60)." + }, + "step5": { + "heading": "Steg 5 - Verifiera emittenten", + "code": "openssl x509 -in /etc/pve/local/pveproxy-ssl.pem -noout -issuer", + "afterRich": "Bör skriva ut en emittent som börjar med O = Let's Encrypt. Om det i stället läser CN = Proxmox Virtual Environment misslyckades ordern och filen är fortfarande den självsignerade PVE-certen - kör pvenode acme cert order --debug 1 för att se varför." + }, + "gui": { + "title": "Föredrar GUI? Samma fem steg", + "bodyRich": "Datacenter → ACME registrerar kontot; Datacenter → ACME → Plugins lägger till DNS-insticksmodulen; Node → Certificates → ACME kopplar domänen; Order Certificates Now kör steg 4. Kommandona ovan passar för skript och system utan grafiskt gränssnitt." + } + }, + "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." + }, + "custom": { + "heading": "Anpassat certifikat - när du ska använda det", + "intro": "Klicka på Använd anpassat certifikat och ange två absoluta sökvägar:", + "items": [ + "Certifikat: hela kedjan i PEM-format (.pem eller .crt).", + "Privat nyckel: matchande nyckel i PEM-format (.key)." + ], + "outro": "Båda filerna läses av Monitor-processen vid start. De måste kunna läsas av användaren som systemd-enheten körs som (root som standard). Vanliga sökvägar när ACME hanteras utanför Proxmox:", + "code": "# certbot\n/etc/letsencrypt/live//fullchain.pem\n/etc/letsencrypt/live//privkey.pem\n\n# acme.sh\n~/.acme.sh/_ecc/fullchain.cer\n~/.acme.sh/_ecc/.key", + "symlinkCallout": { + "title": "Symlink mål vs. filer", + "bodyRich": "certbot lagrar de faktiska filerna i /etc/letsencrypt/archive/ och live/-vägen är en symlänk. Peka Monitorn på live/ symlink, inte archive/-filen - på så sätt omskriver varje förnyelsecykel helt enkelt symlinkmålet och Monitorn plockar upp den nya certen vid nästa servicestart." + } + }, + "afterHttps": { + "heading": "Efter att ha aktiverat HTTPS", + "bodyRich": "Att spara förändringen skriver /etc/proxmenux/ssl_config.json och utlöser en omstart av proxmenux-monitor systemd-enheten. Fliken webbläsare kommer att förlora sin nuvarande anslutning - ladda den på https://<host>:8008/. Den första belastningen med en Let's Encrypt-cert bör inte visa någon varning; med Proxmox självsignerad cert kommer webbläsaren att kräva ett undantag, samma som för port 8006.", + "reverse": { + "heading": "Omvända proxyservrar och webhooks", + "bodyRich": "När Monitorn byter till HTTPS, Proxmox VE webhook URL-adresser registrerade för meddelanden också vända från http:// till https:// automatiskt. Befintliga webhook-poster skrivs om nästa gång en anmälningsregel sparas eller återupptäcks, så ingen manuell redigering av Proxmox-meddelandekonfig behövs." + } + }, + "trustCa": { + "heading": "Lita på Proxmox självsignerade CA", + "intro1Rich": "För att behålla Proxmox standardcertifikat utan ACME importerar du klustrets rot-CA i systemets eller webbläsarens betrodda certifikatlager. Monitor upptäcker /etc/pve/local/pve-ssl.pem som reservcertifikat. Klienten litar då på utfärdaren; URL:en måste fortfarande matcha ett namn i certifikatet.", + "intro2Rich": "CA-filen bor på /etc/pve/pve-root-ca.pem på varje nod. I ett kluster är vägen på det delade pmxcfs-filsystemet så att alla noder delar samma rot, och en enda import täcker hela datacentret. Kopiera den till din maskin:", + "code": "scp root@:/etc/pve/pve-root-ca.pem ./pve-root-ca.pem", + "thenImport": "Importera sedan den på klientsidan:", + "items": [ + "Linux (hela systemet): kör sudo cp pve-root-ca.pem /usr/local/share/ca-certificates/pve-root-ca.crt följt av sudo update-ca-certificates. Webbläsare med systemets certifikatlager (Chromium, Edge) känner igen CA:n efter omstart; Firefox har ett eget lager och behöver den manuella importen nedan.", + "macOS: dubbelklicka på .pem-filen i Nyckelhanterare, lägg till den i nyckelringen System och välj Lita på → Lita alltid på.", + "Windows: högerklicka på filen → Installera certifikat → placera det i Betrodda rotcertifikatutfärdare.", + "Firefox (alla operativsystem): Inställningar → Sekretess och säkerhet → Certifikat → Visa certifikat → Certifikatutfärdare → Importera; markera Lita på denna CA för att identifiera webbplatser." + ], + "standalone": { + "title": "Standalone noder utanför ett kluster", + "bodyRich": "Varje icke-klustrerad Proxmox värd har sin egen pve-root-ca.pem. Om du hanterar flera fristående värdar, upprepa kopian/importen för varje eller gå med dem till ett kluster så att de delar en enda rot." + } + }, + "disable": { + "heading": "Inaktivera HTTPS", + "bodyRich": "Klicka på Inaktivera HTTPS i samma panel. Monitor startar om med okrypterad HTTP, användbart om ett oläsbart eget certifikat hindrar tjänsten från att starta. Ingen separat avinstallation krävs och certifikatfilerna ändras inte.", + "stateCallout": { + "title": "Var tillståndet lagras", + "bodyRich": "/etc/proxmenux/ssl_config.json lagrar de aktiva certvägarna och källan (none, proxmox, custom). Radera filen är likvärdig med att inaktivera HTTPS - nästa start faller Monitor tillbaka till HTTP." + } + } +} From 3e1ae3c4ee009533a23c7f4ec59bd0126ca81c08 Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:49:55 +0200 Subject: [PATCH 18/53] Cache unchanged OCI config reads when opening Manage --- .github/workflows/test-oci-registries.yml | 6 + oci/remote/oci_carried_record.py | 51 +++++++- oci/tests/test_carried_config_cache.py | 134 ++++++++++++++++++++++ 3 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 oci/tests/test_carried_config_cache.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..a5272e32 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -28,6 +28,12 @@ jobs: python-version: '3.13' - name: Install OCI test dependencies run: python3 -m pip install -r oci/requirements.txt + - name: Run carried record, recovery and configuration cache regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: | + python3 -m unittest discover -s oci/tests -p 'test_carried*.py' -v + python3 -m unittest discover -s oci/tests -p test_restore_recovery.py -v - name: Run offline registry account tests env: PYTHONDONTWRITEBYTECODE: '1' diff --git a/oci/remote/oci_carried_record.py b/oci/remote/oci_carried_record.py index 725640dd..14375ef5 100644 --- a/oci/remote/oci_carried_record.py +++ b/oci/remote/oci_carried_record.py @@ -38,6 +38,8 @@ DIRECTORY = '.proxmenux' NAME = 'oci-record.json' KIND = 'proxmenux.oci-carried-record' STAMP = 'carried.sha256' +CONFIG_CACHE = 'carried-config.json' +LOCAL_CONFIGS = Path('/etc/pve/lxc') LIMIT = 16 * 1024 * 1024 STACK_CONTRACT = '/etc/pve/priv/proxmenux-stack-{}.json' NVIDIA_HOOK = re.compile(r'/usr/local/lib/proxmenux/oci/nvidia-mount-([a-f0-9]{64})\.sh') @@ -279,6 +281,47 @@ def read_stamp(path): return value if isinstance(value, dict) else {} +def configuration(root, vmid, verify=False): + """Reuse pct's formatted output only while its local source is unchanged. + + Opening Manage checks every carried copy. Starting a fresh pct process + for each unchanged guest dominates that check. The private host cache is + keyed by the complete native configuration, including notes, pending + changes and snapshot sections. Lifecycle verification always calls pct. + An unavailable source, invalid cache or concurrent edit falls back to pct. + """ + source = LOCAL_CONFIGS / f'{int(vmid)}.conf' + cache = instances.location(root, vmid).parent / CONFIG_CACHE + + def fingerprint(): + try: + return sha(source.read_bytes()) + except OSError: + return None + + before = fingerprint() + if before is not None and not verify: + try: + if not cache.is_symlink() and cache.stat().st_size <= LIMIT: + value = json.loads(cache.read_text()) + if (isinstance(value, dict) and value.get('schema_version') == 1 + and value.get('source_sha256') == before + and isinstance(value.get('config'), str) and value['config'] + and fingerprint() == before): + return value['config'] + except (OSError, ValueError): + pass + result = _run('pct', 'config', str(vmid)) + if result.returncode != 0: + return None + # Do not associate output from one revision with another revision's hash. + if before is not None and fingerprint() == before: + with contextlib.suppress(OSError, ValueError): + instances.write(cache, {'schema_version': 1, 'source_sha256': before, + 'config': result.stdout}) + return result.stdout + + def superseded(record, existing, stamp): """Whether the container carries a copy this host did not write, with a record that differs from the one of this host. That is a container that @@ -308,10 +351,10 @@ def carry(root, vmid, mount_stopped=True, verify=False, adopted=False): if record.get('status') != 'installed' or record.get('pending_transaction') \ or record.get('pending_stack_transaction'): return 'skipped' - result = _run('pct', 'config', str(vmid)) - if result.returncode != 0 or instances.identity(result.stdout.encode()) != record['installation_id']: + config = configuration(root, vmid, verify) + if config is None or instances.identity(config.encode()) != record['installation_id']: return 'skipped' - value = bundle(record, result.stdout) + value = bundle(record, config) expected = digest(value) record_path = instances.location(root, vmid) path = record_path.parent / STAMP @@ -340,7 +383,7 @@ def carry(root, vmid, mount_stopped=True, verify=False, adopted=False): else: value['saved_at'] = datetime.datetime.now(datetime.timezone.utc).isoformat() value['generation'] = uuid.uuid4().hex - write_copy(rootfs, value, mapped_root(result.stdout)) + write_copy(rootfs, value, mapped_root(config)) stamp = {'generation': value['generation']} outcome = 'carried' if outcome == 'stale': diff --git a/oci/tests/test_carried_config_cache.py b/oci/tests/test_carried_config_cache.py new file mode 100644 index 00000000..026a3598 --- /dev/null +++ b/oci/tests/test_carried_config_cache.py @@ -0,0 +1,134 @@ +"""A fast Manage entry still detects config edits and verifies lifecycle work.""" +import contextlib +import copy +import json +import os +from pathlib import Path +import sys +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'remote')) +import oci_carried_record as carried + + +class CarriedConfigCacheTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) / 'instances' + self.source = Path(self.tmp.name) / 'lxc' + self.source.mkdir() + self.config = self.source / '120.conf' + self.config.write_text('# proxmenux-instance=example\nunprivileged: 1\n') + (self.root / '120').mkdir(parents=True) + self.cache = self.root / '120' / carried.CONFIG_CACHE + self.patch = patch.object(carried, 'LOCAL_CONFIGS', self.source) + self.patch.start() + self.addCleanup(self.patch.stop) + + def response(self, text='description: proxmenux-instance=example\nunprivileged: 1\n', status=0): + return SimpleNamespace(returncode=status, stdout=text) + + def test_warm_reads_avoid_pct_and_cache_is_private(self): + with patch.object(carried, '_run', return_value=self.response()) as run: + first = carried.configuration(self.root, 120) + self.assertEqual(carried.configuration(self.root, 120), first) + run.assert_called_once_with('pct', 'config', '120') + self.assertEqual(self.cache.stat().st_mode & 0o777, 0o600) + + def test_native_edits_invalidate_even_when_size_and_mtime_are_restored(self): + with patch.object(carried, '_run', side_effect=[self.response(), self.response('new')]) as run: + carried.configuration(self.root, 120) + info = self.config.stat() + self.config.write_text(self.config.read_text().replace('example', 'changed')) + os.utime(self.config, ns=(info.st_atime_ns, info.st_mtime_ns)) + self.assertEqual(carried.configuration(self.root, 120), 'new') + self.assertEqual(run.call_count, 2) + + def test_verify_forces_a_fresh_pct_read_with_a_warm_cache(self): + with patch.object(carried, '_run', side_effect=[self.response(), self.response('verified')]) as run: + carried.configuration(self.root, 120) + self.assertEqual(carried.configuration(self.root, 120, verify=True), 'verified') + self.assertEqual(run.call_count, 2) + + def test_missing_native_source_never_uses_or_populates_a_cache(self): + self.config.unlink() + with patch.object(carried, '_run', return_value=self.response()) as run: + carried.configuration(self.root, 120) + carried.configuration(self.root, 120) + self.assertEqual(run.call_count, 2) + self.assertFalse(self.cache.exists()) + + def test_concurrent_edit_does_not_cache_output_for_the_wrong_revision(self): + def command(*args): + self.config.write_text('changed while pct was reading\n') + return self.response() + with patch.object(carried, '_run', side_effect=command): + carried.configuration(self.root, 120) + self.assertFalse(self.cache.exists()) + + def test_edit_during_cache_lookup_falls_back_to_pct(self): + with patch.object(carried, '_run', return_value=self.response()): + carried.configuration(self.root, 120) + read_text = Path.read_text + def edit_during_read(path, *args, **kwargs): + text = read_text(path, *args, **kwargs) + if path == self.cache: + self.config.write_text('changed while reading the cache\n') + return text + with patch.object(Path, 'read_text', edit_during_read), \ + patch.object(carried, '_run', return_value=self.response('fresh')) as run: + self.assertEqual(carried.configuration(self.root, 120), 'fresh') + run.assert_called_once_with('pct', 'config', '120') + + def test_corrupt_cache_and_pct_failure_are_not_accepted(self): + for text in ('not json', '[]', '{"schema_version": 999}'): + with self.subTest(cache=text): + self.cache.write_text(text) + with patch.object(carried, '_run', return_value=self.response(status=1)) as run: + self.assertIsNone(carried.configuration(self.root, 120)) + run.assert_called_once() + self.assertEqual(self.cache.read_text(), text) + + def test_symlink_cache_does_not_read_or_replace_its_target(self): + target = Path(self.tmp.name) / 'outside' + target.write_text('private original') + self.cache.symlink_to(target) + with patch.object(carried, '_run', return_value=self.response()) as run: + self.assertEqual(carried.configuration(self.root, 120), self.response().stdout) + run.assert_called_once() + self.assertTrue(self.cache.is_symlink()) + self.assertEqual(target.read_text(), 'private original') + + def test_cached_config_still_detects_a_restored_container_record(self): + installation = '488ed3cb-1145-477a-b90f-c46777d69fb2' + record = {'schema_version': 1, 'vmid': 120, 'installation_id': installation, + 'status': 'installed', 'deployment': {'hostname': 'current'}} + carried.instances.write(carried.instances.location(self.root, 120), record) + native = f'description: proxmenux-instance={installation}\n' + with patch.object(carried, '_run', return_value=self.response(native)): + carried.configuration(self.root, 120) + rootfs = Path(self.tmp.name) / 'rootfs' + rootfs.mkdir() + previous = copy.deepcopy(record) + previous['deployment']['hostname'] = 'restored-snapshot' + carried.write_copy(rootfs, {'kind': carried.KIND, 'schema_version': 1, + 'record': previous, 'generation': 'restored-generation'}, + (os.getuid(), os.getgid())) + stamp = self.root / '120' / carried.STAMP + stamp.write_text(json.dumps({'generation': 'host-generation'})) + with patch.object(carried, '_run', side_effect=AssertionError('cached config expected')), \ + patch.object(carried, 'read_cluster', return_value=None), \ + patch.object(carried, 'running_pid', return_value=123), \ + patch.object(carried, 'container_root', return_value=contextlib.nullcontext(rootfs)): + self.assertEqual(carried.carry(self.root, 120, mount_stopped=False), 'stale') + self.assertFalse(carried.instances.location(self.root, 120).exists()) + self.assertTrue((self.root / '120' / f'retired-{installation}.json').exists()) + + +if __name__ == '__main__': + unittest.main() From 23902575154b2835d0e12df0285b99c2c5937fbc Mon Sep 17 00:00:00 2001 From: Jonatan Date: Sat, 17 Oct 2026 15:53:33 +0200 Subject: [PATCH 19/53] fix(oci): skip preparation of read-only volumes --- .github/workflows/test-oci-registries.yml | 7 + oci/remote/install_oci.sh | 11 ++ oci/tests/test_readonly_volume_preparation.py | 187 ++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 oci/tests/test_readonly_volume_preparation.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..dd39bded 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -28,6 +28,13 @@ jobs: python-version: '3.13' - name: Install OCI test dependencies run: python3 -m pip install -r oci/requirements.txt + - name: Run read-only volume preparation regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: | + test -x /bin/bash + test -x /usr/bin/jq + python3 -m unittest discover -s oci/tests -p test_readonly_volume_preparation.py -v - name: Run offline registry account tests env: PYTHONDONTWRITEBYTECODE: '1' diff --git a/oci/remote/install_oci.sh b/oci/remote/install_oci.sh index 7d1d8e85..5d39523a 100755 --- a/oci/remote/install_oci.sh +++ b/oci/remote/install_oci.sh @@ -1230,6 +1230,17 @@ apply_installer_profile() { fi [[ -d ${rootfs}${target} ]] \ || { oci_log "The volume to prepare does not exist: $target"; failed=1; break; } + # A reused volume may intentionally be mounted read-only (for example, + # /run/secrets). Do not remove files or change ownership/mode on it. + # Use the most specific containing mount: an RW child can override RO. + if [[ $(jq -r --arg target "$target" ' + [.mounts[]? | select(.container_path as $path | + $target == $path or ($target | startswith($path + "/")))] + | sort_by(.container_path | length) | last | .read_only // false' \ + "$DEPLOYMENT_FILE") == true ]]; then + oci_log "Skipping preparation of read-only volume: $target" + continue + fi if [[ $remove_lost_found == true ]]; then rm -rf "${rootfs}${target}/lost+found" || { failed=1; break; } fi diff --git a/oci/tests/test_readonly_volume_preparation.py b/oci/tests/test_readonly_volume_preparation.py new file mode 100644 index 00000000..37f67011 --- /dev/null +++ b/oci/tests/test_readonly_volume_preparation.py @@ -0,0 +1,187 @@ +"""Run the installer's volume preparation loop against filesystem fixtures. + +Write doubles reject every mutation of the protected directory, including +lost+found removal. Writable paths still run real rm/chmod; chown is recorded +without requiring root. These tests never invoke Proxmox or touch a container. +""" + +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +INSTALLER = ROOT / "remote/install_oci.sh" + + +def preparation_loop(): + source = INSTALLER.read_text() + start = source.index(" if (( failed == 0 )) && [[ $(jq -r '.stack_managed") + end = source.index("\n if (( failed == 0 && tls_count == 1 )); then", start) + return source[start:end] + + +def preparation(target): + return { + "container_path": target, + "remove_lost_found": True, + "owner_strategy": "mapped-application-user", + "mode": "0750", + } + + +class ReadOnlyVolumePreparationTests(unittest.TestCase): + def run_case(self, mounts, *, target="/run/secrets", read_only=True, + preparations=None): + preparations = preparations if preparations is not None else [preparation(target)] + with tempfile.TemporaryDirectory() as directory: + fixture = Path(directory) + rootfs = fixture / "rootfs" + for item in preparations: + path = rootfs / item["container_path"].lstrip("/") + path.mkdir(parents=True, exist_ok=True) + (path / "lost+found").mkdir(exist_ok=True) + protected = rootfs / target.lstrip("/") + protected.mkdir(parents=True, exist_ok=True) + sentinel = protected / "sentinel" + sentinel.write_bytes(b"existing secret\n") + sentinel.chmod(0o600) + protected.chmod(0o700) + before = sentinel.stat() + template = {"proxmox": {"installer_profile": { + "volume_preparations": preparations, + }}} + (fixture / "template.json").write_text(json.dumps(template)) + (fixture / "deployment.json").write_text(json.dumps({"mounts": mounts})) + script = r'''set -Eeuo pipefail +rootfs="$FIXTURE/rootfs" +TEMPLATE_FILE="$FIXTURE/template.json" +DEPLOYMENT_FILE="$FIXTURE/deployment.json" +HOST_BIND_UID=110001; HOST_BIND_GID=110001 +HOST_ROOT_UID=100000; HOST_ROOT_GID=100000 +failed=0; volumes_prepared=0 +oci_log() { printf 'log %s\n' "$*"; } +write_op() { + local operation=$1 + shift + local path=${!#} + printf 'WRITE %s %s\n' "$operation" "$path" + if [[ -n $PROTECTED && ( $path == "$PROTECTED" || $path == "$PROTECTED/"* ) ]]; then + printf 'Read-only file system\n' >&2 + return 1 + fi + case "$operation" in + rm|chmod) command "$operation" "$@" ;; + chown) return 0 ;; + esac +} +chown() { write_op chown "$@"; } +chmod() { write_op chmod "$@"; } +rm() { write_op rm "$@"; } +''' + preparation_loop() + r''' +printf 'failed=%s prepared=%s\n' "$failed" "$volumes_prepared" +test "$failed" = 0 +''' + result = subprocess.run( + ["bash", "-c", script], capture_output=True, text=True, + env=dict(os.environ, FIXTURE=str(fixture), + PROTECTED=str(protected) if read_only else ""), + timeout=15, + ) + self.assertEqual(sentinel.read_bytes(), b"existing secret\n") + after = sentinel.stat() + self.assertEqual( + (before.st_ino, before.st_uid, before.st_gid, before.st_mode, before.st_mtime_ns), + (after.st_ino, after.st_uid, after.st_gid, after.st_mode, after.st_mtime_ns), + ) + if read_only: + self.assertEqual(protected.stat().st_mode & 0o777, 0o700) + self.assertTrue((protected / "lost+found").is_dir()) + elif "prepared=1" in result.stdout: + self.assertEqual(protected.stat().st_mode & 0o777, 0o750) + self.assertFalse((protected / "lost+found").exists()) + return result + + def assert_skipped(self, result): + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + self.assertNotIn("WRITE ", result.stdout) + self.assertIn("Skipping preparation of read-only volume", result.stdout) + self.assertIn("prepared=0", result.stdout) + + def assert_prepared(self, result): + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + for operation in ("rm", "chown", "chmod"): + self.assertIn(f"WRITE {operation} ", result.stdout) + self.assertIn("prepared=1", result.stdout) + + def test_read_only_managed_and_bind_volumes_receive_no_writes(self): + for mount_type in ("managed-volume", "host-bind"): + with self.subTest(mount_type=mount_type): + self.assert_skipped(self.run_case([ + {"container_path": "/run/secrets", "type": mount_type, "read_only": True}, + ])) + + def test_read_only_parent_protects_nested_preparation(self): + self.assert_skipped(self.run_case([ + {"container_path": "/run", "read_only": True}, + ])) + + def test_writable_child_overrides_read_only_parent_regardless_of_order(self): + mounts = [ + {"container_path": "/run", "read_only": True}, + {"container_path": "/run/secrets", "read_only": False}, + ] + for ordered in (mounts, mounts[::-1]): + with self.subTest(mounts=ordered): + self.assert_prepared(self.run_case(ordered, read_only=False)) + + def test_child_without_read_only_flag_overrides_read_only_parent(self): + self.assert_prepared(self.run_case([ + {"container_path": "/run", "read_only": True}, + {"container_path": "/run/secrets"}, + ], read_only=False)) + + def test_read_only_child_overrides_writable_parent_regardless_of_order(self): + mounts = [ + {"container_path": "/run/secrets", "read_only": True}, + {"container_path": "/run", "read_only": False}, + ] + for ordered in (mounts, mounts[::-1]): + with self.subTest(mounts=ordered): + self.assert_skipped(self.run_case(ordered)) + + def test_writable_volume_is_still_prepared(self): + self.assert_prepared(self.run_case([ + {"container_path": "/data", "read_only": False}, + ], target="/data", read_only=False)) + + def test_missing_read_only_flag_defaults_to_writable(self): + self.assert_prepared(self.run_case([ + {"container_path": "/data"}, + ], target="/data", read_only=False)) + + def test_unrelated_mount_with_similar_prefix_does_not_skip_preparation(self): + self.assert_prepared(self.run_case([ + {"container_path": "/run/secret", "read_only": True}, + ], read_only=False)) + + def test_directory_outside_mounts_is_still_prepared(self): + self.assert_prepared(self.run_case([], read_only=False)) + + def test_skipping_read_only_volume_does_not_skip_following_writable_volume(self): + result = self.run_case([ + {"container_path": "/run/secrets", "read_only": True}, + {"container_path": "/data", "read_only": False}, + ], preparations=[preparation("/run/secrets"), preparation("/data")]) + self.assert_prepared(result) + self.assertIn("Skipping preparation of read-only volume: /run/secrets", result.stdout) + for line in result.stdout.splitlines(): + if line.startswith("WRITE "): + self.assertTrue(line.endswith("/data") or line.endswith("/data/lost+found"), line) + + +if __name__ == "__main__": + unittest.main() From 1c4220316641b42729bbfe471542bbcfcfed00ab Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:17:27 +0200 Subject: [PATCH 20/53] Add selective settings menu for OCI modifications --- .github/workflows/test-oci-registries.yml | 8 + lang/de.json | 9 + lang/es.json | 9 + lang/fr.json | 9 + lang/it.json | 9 + lang/pt.json | 9 + lang/sk.json | 9 + lang/sv.json | 9 + oci/src/proxmenux_oci/management.py | 21 +-- oci/src/proxmenux_oci/recreation.py | 166 ++++++++++++++---- oci/tests/test_modify_menu.py | 201 ++++++++++++++++++++++ oci/tests/test_shared_directories.py | 14 +- 12 files changed, 415 insertions(+), 58 deletions(-) create mode 100644 oci/tests/test_modify_menu.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..2a200d9c 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -32,3 +32,11 @@ jobs: env: PYTHONDONTWRITEBYTECODE: '1' run: python3 -m unittest discover -s oci/tests -p 'test_registry_*.py' -v + - name: Run OCI modification menu regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: | + python3 -m unittest discover -s oci/tests -p test_modify_menu.py -v + python3 -m unittest discover -s oci/tests -p test_recreation.py -v + python3 -m unittest discover -s oci/tests -p test_shared_directories.py -v + python3 -m unittest discover -s oci/tests -p 'test_wizard*.py' -v diff --git a/lang/de.json b/lang/de.json index 8d643f1a..becced3a 100644 --- a/lang/de.json +++ b/lang/de.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Kann die wiederverwendete Festplatte nicht überprüfen:", "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", "Category": "Kategorie", "Caution: Maximum mode generates more heat.": "Achtung: Im Maximalmodus entsteht mehr Wärme.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Ceph-Prüfung durch Benutzerflag übersprungen (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Wählen Sie einen Zeitplan (systemd-Kalender-Ereignis-Syntax):", "Choose a script to execute:": "Wählen Sie ein auszuführendes Skript aus:", "Choose a server:": "Wählen Sie einen Server:", + "Choose a setting. Changes are applied only after the final review.": "Wähle eine Einstellung. Änderungen werden erst nach der abschließenden Prüfung angewendet.", "Choose a share to mount:": "Wählen Sie eine Freigabe zum Mounten aus:", "Choose a specific supported GPU or USB node": "Wählen Sie einen bestimmten unterstützten GPU- oder USB-Knoten aus", "Choose action for this controller/NVMe:": "Wählen Sie eine Aktion für diesen Controller/NVMe:", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Rpcbind-Dienst und Socket deaktivieren und stoppen ...", "Disabling non-free firmware warnings...": "Warnungen zu nicht kostenloser Firmware werden deaktiviert...", "Disabling portmapper/rpcbind for security...": "Portmapper/rpcbind wird aus Sicherheitsgründen deaktiviert...", + "Discard changes": "Änderungen verwerfen", + "Discard unapplied changes?": "Noch nicht angewendete Änderungen verwerfen?", "Discover targets on portal:": "Entdecken Sie Ziele auf dem Portal:", "Discovering iSCSI targets...": "iSCSI-Ziele werden ermittelt...", "Discovery successful": "Entdeckung erfolgreich", @@ -4261,6 +4265,7 @@ "No password provided.": "Kein Passwort angegeben.", "No paths available": "Keine Pfade verfügbar", "No paths selected. Select at least one path.": "Keine Pfade ausgewählt. Wählen Sie mindestens einen Pfad aus.", + "No pending changes": "Keine ausstehenden Änderungen", "No pending paths to schedule for reboot.": "Keine ausstehenden Pfade zum Planen eines Neustarts.", "No permanent NFS mounts configured.": "Keine permanenten NFS-Mounts konfiguriert.", "No permanent Samba mounts configured.": "Keine permanenten Samba-Mounts konfiguriert.", @@ -4402,6 +4407,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM entfernt (Marionettenartefakte müssen möglicherweise manuell bereinigt werden)", "OVH server detected": "OVH-Server erkannt", "OVH server detection and RTM installation process completed": "OVH-Servererkennung und RTM-Installationsprozess abgeschlossen", + "Object detector": "Objekterkennung", "Object detector for Frigate": "Objektdetektor für Frigate", "Observer is not responding on port 4357": "Beobachter reagiert nicht auf Port 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian ist eine Notiz-App, mit der Sie Ihre Notizen auf Ihrem Gerät erstellen, verknüpfen und organisieren können, mit Hunderten von Plugins und Themes, um Ihren Workflow anzupassen. Sie können Ihre Notizen auch online veröffentlichen, offline darauf zugreifen und sie sicher mit einer End-to-End-Verschlüsselung synchronisieren.", @@ -5328,6 +5334,7 @@ "Reverting ZFS ARC tuning...": "ZFS ARC-Tuning wird zurückgesetzt...", "Reverting pigz wrapper...": "Pigz-Wrapper wird zurückgesetzt...", "Reverting vzdump speed tuning...": "vzdump-Geschwindigkeitsoptimierung wird zurückgesetzt...", + "Review and apply": "Prüfen und anwenden", "Review external OCI changes": "Überprüfen Sie externe OCI-Änderungen", "Review package upgrade": "Paketaktualisierung prüfen", "Review passthrough config files": "Überprüfen Sie die Passthrough-Konfigurationsdateien", @@ -5787,6 +5794,7 @@ "ShareCoveX panel": "ShareCoveX-Panel", "Shared Directory Ready:": "Bereit für das freigegebene Verzeichnis:", "Shared Group": "Geteilte Gruppe", + "Shared directories": "Freigegebene Verzeichnisse", "Shared directory created:": "Gemeinsames Verzeichnis erstellt:", "Shared directory for consume and export": "Gemeinsames Verzeichnis für Konsum und Export", "Shared directory for copy/sync operations": "Gemeinsames Verzeichnis für Copy/Sync operations", @@ -6997,6 +7005,7 @@ "Unable to identify the root filesystem. Aborting.": "Das Root-Dateisystem konnte nicht identifiziert werden. Abbruch.", "Unable to resolve system ZFS pool disks. Aborting.": "System-Pool-Festplatten ZFS können nicht aufgelöst werden. Abbruch.", "Unable to resolve system disk topology. Aborting.": "Die Topologie der Systemfestplatte kann nicht aufgelöst werden. Abbruch.", + "Unapplied changes": "Noch nicht angewendete Änderungen", "Understand the security implications of privileged containers": "Verstehen Sie die Sicherheitsauswirkungen privilegierter Container", "Unexpected Proxmox inventory; recovery blocked": "Unerwartetes Proxmox-Inventar; Erholung blockiert", "Unexpected formatting directory in the new Valkey volume": "Unerwartetes Formatierungsverzeichnis im neuen Valkey-Volume", diff --git a/lang/es.json b/lang/es.json index de4d581e..fa741cf8 100644 --- a/lang/es.json +++ b/lang/es.json @@ -748,6 +748,7 @@ "Cannot verify the reused disk:": "No se puede verificar el disco reutilizado:", "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", "Category": "Categoría", "Caution: Maximum mode generates more heat.": "Precaución: el modo máximo genera más calor.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Comprobación de Ceph omitida por el indicador del usuario (--ignore-ceph-check)", @@ -882,6 +883,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Elija una programación (sintaxis de eventos de calendario de systemd):", "Choose a script to execute:": "Elige un script para ejecutar:", "Choose a server:": "Elige un servidor:", + "Choose a setting. Changes are applied only after the final review.": "Elige qué configurar. Los cambios solo se aplican tras la revisión final.", "Choose a share to mount:": "Elige un recurso compartido para montar:", "Choose a specific supported GPU or USB node": "Elija un nodo concreto de GPU o USB compatible", "Choose action for this controller/NVMe:": "Elige la acción para este controlador/NVMe:", @@ -1764,6 +1766,8 @@ "Disabling and stopping rpcbind service and socket...": "Deshabilitar y detener el servicio y el socket rpcbind...", "Disabling non-free firmware warnings...": "Deshabilitando las advertencias de firmware no libre...", "Disabling portmapper/rpcbind for security...": "Deshabilitando portmapper/rpcbind por seguridad...", + "Discard changes": "Descartar cambios", + "Discard unapplied changes?": "¿Descartar los cambios sin aplicar?", "Discover targets on portal:": "Detectar destinos en el portal:", "Discovering iSCSI targets...": "Descubriendo destinos iSCSI...", "Discovery successful": "Detección correcta", @@ -4256,6 +4260,7 @@ "No password provided.": "No se proporcionó contraseña.", "No paths available": "No hay rutas disponibles", "No paths selected. Select at least one path.": "No hay rutas seleccionadas. Seleccione al menos una ruta.", + "No pending changes": "No hay cambios pendientes", "No pending paths to schedule for reboot.": "No hay rutas pendientes para programar el reinicio.", "No permanent NFS mounts configured.": "No se han configurado montajes NFS permanentes.", "No permanent Samba mounts configured.": "No hay montajes Samba permanentes configurados.", @@ -4394,6 +4399,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM eliminado (los artefactos de las marionetas pueden necesitar una limpieza manual)", "OVH server detected": "Servidor OVH detectado", "OVH server detection and RTM installation process completed": "Finalizado el proceso de detección del servidor OVH e instalación de RTM", + "Object detector": "Detector de objetos", "Object detector for Frigate": "Detector de objetos para Frigate", "Observer is not responding on port 4357": "El observador no responde en el puerto 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian es una aplicación de notas que te permite crear, enlazar y organizar tus notas en tu dispositivo, con cientos de plugins y temas para personalizar tu flujo de trabajo. También puedes publicar tus notas en línea, acceder a ellas sin conexión y sincronizarlas de forma segura con cifrado de extremo a extremo.", @@ -5312,6 +5318,7 @@ "Reverting ZFS ARC tuning...": "Revirtiendo el ajuste ZFS ARC...", "Reverting pigz wrapper...": "Revertir la envoltura de pigz...", "Reverting vzdump speed tuning...": "Revertir el ajuste de velocidad de vzdump...", + "Review and apply": "Revisar y aplicar", "Review external OCI changes": "Revisar cambios externos de OCI", "Review package upgrade": "Revisar la actualización de paquetes", "Review passthrough config files": "Revisar los archivos de configuración de passthrough", @@ -5771,6 +5778,7 @@ "ShareCoveX panel": "Panel de ShareCoveX", "Shared Directory Ready:": "Directorio compartido listo:", "Shared Group": "Grupo compartido", + "Shared directories": "Carpetas compartidas", "Shared directory created:": "Directorio compartido creado:", "Shared directory for consume and export": "Directorio compartido para consumo y exportación", "Shared directory for copy/sync operations": "Directorio compartido para operaciones de copia/sincronización", @@ -6972,6 +6980,7 @@ "Unable to identify the root filesystem. Aborting.": "No se pudo identificar el sistema de archivos raíz. Se cancela.", "Unable to resolve system ZFS pool disks. Aborting.": "No se pudieron resolver los discos del pool ZFS del sistema. Se cancela.", "Unable to resolve system disk topology. Aborting.": "No se pudo resolver la topología de discos del sistema. Se cancela.", + "Unapplied changes": "Cambios sin aplicar", "Understand the security implications of privileged containers": "Comprender las implicaciones de seguridad de los contenedores privilegiados", "Unexpected Proxmox inventory; recovery blocked": "Inventario Proxmox inesperado; recuperación bloqueada", "Unexpected formatting directory in the new Valkey volume": "Directorio de formato inesperado en el nuevo volumen Valkey", diff --git a/lang/fr.json b/lang/fr.json index 2d3be751..861f56e4 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Impossible de vérifier le disque réutilisé :", "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", "Category": "Catégorie", "Caution: Maximum mode generates more heat.": "Attention : le mode maximum génère plus de chaleur.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Vérification Ceph ignorée par l'indicateur utilisateur (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Choisissez un calendrier (syntaxe d'événement de calendrier systemd) :", "Choose a script to execute:": "Choisissez un script à exécuter :", "Choose a server:": "Choisissez un serveur :", + "Choose a setting. Changes are applied only after the final review.": "Choisissez un réglage. Les modifications ne sont appliquées qu’après la vérification finale.", "Choose a share to mount:": "Choisissez un partage à monter :", "Choose a specific supported GPU or USB node": "Choisissez un GPU ou un nœud USB pris en charge spécifique", "Choose action for this controller/NVMe:": "Choisissez une action pour ce contrôleur/NVMe :", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Désactivation et arrêt du service et du socket rpcbind...", "Disabling non-free firmware warnings...": "Désactivation des avertissements du firmware non libre...", "Disabling portmapper/rpcbind for security...": "Désactivation de portmapper/rpcbind pour la sécurité...", + "Discard changes": "Abandonner les modifications", + "Discard unapplied changes?": "Abandonner les modifications non appliquées ?", "Discover targets on portal:": "Découvrez les cibles sur le portail :", "Discovering iSCSI targets...": "Découverte des cibles iSCSI...", "Discovery successful": "Découverte réussie", @@ -4261,6 +4265,7 @@ "No password provided.": "Aucun mot de passe fourni.", "No paths available": "Aucun chemin disponible", "No paths selected. Select at least one path.": "Aucun chemin sélectionné. Sélectionnez au moins un chemin.", + "No pending changes": "Aucune modification en attente", "No pending paths to schedule for reboot.": "Aucun chemin en attente pour planifier le redémarrage.", "No permanent NFS mounts configured.": "Aucun montage NFS permanent configuré.", "No permanent Samba mounts configured.": "Aucun montage Samba permanent configuré.", @@ -4402,6 +4407,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM supprimé (les artefacts de marionnettes peuvent nécessiter un nettoyage manuel)", "OVH server detected": "Serveur OVH détecté", "OVH server detection and RTM installation process completed": "Processus de détection du serveur OVH et d'installation de RTM terminé", + "Object detector": "Détecteur d’objets", "Object detector for Frigate": "Détecteur d'objets pour Frigate", "Observer is not responding on port 4357": "L'observateur ne répond pas au port 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian est une application de prise de notes qui vous permet de créer, de lier et d'organiser vos notes sur votre appareil, avec des centaines de plugins et de thèmes pour personnaliser votre workflow. Vous pouvez également publier vos notes en ligne, les accéder hors ligne et les synchroniser en toute sécurité avec le chiffrement de bout en bout.", @@ -5328,6 +5334,7 @@ "Reverting ZFS ARC tuning...": "Annulation du réglage ZFS ARC...", "Reverting pigz wrapper...": "Rétablissement du wrapper pigz...", "Reverting vzdump speed tuning...": "Annulation du réglage de la vitesse de vzdump...", + "Review and apply": "Vérifier et appliquer", "Review external OCI changes": "Examiner les modifications externes de OCI", "Review package upgrade": "Examiner la mise à niveau des paquets", "Review passthrough config files": "Examiner les fichiers de configuration relais", @@ -5787,6 +5794,7 @@ "ShareCoveX panel": "Panneau ShareCoveX", "Shared Directory Ready:": "Répertoire partagé prêt :", "Shared Group": "Groupe partagé", + "Shared directories": "Dossiers partagés", "Shared directory created:": "Répertoire partagé créé & #160;:", "Shared directory for consume and export": "Répertoire partagé pour la consommation et l'exportation", "Shared directory for copy/sync operations": "Répertoire partagé pour copier/sync operations", @@ -6996,6 +7004,7 @@ "Unable to identify the root filesystem. Aborting.": "Impossible d'identifier le système de fichiers racine. Avorter.", "Unable to resolve system ZFS pool disks. Aborting.": "Impossible de résoudre les disques du pool système ZFS.Avorter.", "Unable to resolve system disk topology. Aborting.": "Impossible de résoudre la topologie du disque système. Avorter.", + "Unapplied changes": "Modifications non appliquées", "Understand the security implications of privileged containers": "Comprendre les implications de sécurité des conteneurs privilégiés", "Unexpected Proxmox inventory; recovery blocked": "stocks imprévus Proxmox; récupération bloquée", "Unexpected formatting directory in the new Valkey volume": "Répertoire de formatage inattendu dans le nouveau volume Valkey", diff --git a/lang/it.json b/lang/it.json index 13e3c788..58abc3a7 100644 --- a/lang/it.json +++ b/lang/it.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Impossibile verificare il disco riutilizzato:", "Cannot write the image archive to {storage}. Check storage access and mount status.": "Impossibile scrivere l'archivio immagini su {storage}. Controlla l'accesso allo spazio di archiviazione 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", "Category": "Categoria", "Caution: Maximum mode generates more heat.": "Attenzione: la modalità massima genera più calore.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Controllo Ceph saltato dal flag utente (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Scegli una pianificazione (sintassi calendar-event di systemd):", "Choose a script to execute:": "Scegli uno script da eseguire:", "Choose a server:": "Scegli un server:", + "Choose a setting. Changes are applied only after the final review.": "Scegli cosa configurare. Le modifiche vengono applicate solo dopo la verifica finale.", "Choose a share to mount:": "Scegli una condivisione da montare:", "Choose a specific supported GPU or USB node": "Scegli uno specifico nodo GPU o USB supportato", "Choose action for this controller/NVMe:": "Scegli l'azione per questo controller/NVMe:", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Disabilitazione e arresto del servizio e del socket rpcbind...", "Disabling non-free firmware warnings...": "Disabilitazione degli avvisi sul firmware non libero...", "Disabling portmapper/rpcbind for security...": "Disabilitazione di portmapper/rpcbind per sicurezza...", + "Discard changes": "Scarta le modifiche", + "Discard unapplied changes?": "Scartare le modifiche non applicate?", "Discover targets on portal:": "Scopri target sul portale:", "Discovering iSCSI targets...": "Individuazione dei target iSCSI...", "Discovery successful": "Scoperta riuscita", @@ -4261,6 +4265,7 @@ "No password provided.": "Nessuna password fornita.", "No paths available": "Nessun percorso disponibile", "No paths selected. Select at least one path.": "Nessun percorso selezionato. Seleziona almeno un percorso.", + "No pending changes": "Nessuna modifica in sospeso", "No pending paths to schedule for reboot.": "Nessun percorso in sospeso da pianificare per il riavvio.", "No permanent NFS mounts configured.": "Nessun montaggio NFS permanente configurato.", "No permanent Samba mounts configured.": "Nessun montaggio Samba permanente configurato.", @@ -4402,6 +4407,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM rimosso (gli artefatti di Puppet potrebbero richiedere una pulizia manuale)", "OVH server detected": "Rilevato server OVH", "OVH server detection and RTM installation process completed": "Processo di rilevamento dei server OVH e installazione RTM completato", + "Object detector": "Rilevatore di oggetti", "Object detector for Frigate": "Rilevatore di oggetti per Frigate", "Observer is not responding on port 4357": "Observer non risponde sulla porta 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian è un’app per creare, collegare e organizzare le note sul tuo dispositivo, con centinaia di plugin e temi per personalizzare il flusso di lavoro. Puoi pubblicare le note online, consultarle offline e sincronizzarle in modo sicuro con crittografia end-to-end.", @@ -5328,6 +5334,7 @@ "Reverting ZFS ARC tuning...": "Rimozione delle ottimizzazioni di ZFS ARC…", "Reverting pigz wrapper...": "Rimozione del wrapper pigz e ripristino della configurazione precedente...", "Reverting vzdump speed tuning...": "Rimozione ottimizzazioni velocità vzdump…", + "Review and apply": "Verifica e applica", "Review external OCI changes": "Esamina le modifiche OCI esterne", "Review package upgrade": "Esamina l'aggiornamento dei pacchetti", "Review passthrough config files": "Esamina i file di configurazione passthrough", @@ -5787,6 +5794,7 @@ "ShareCoveX panel": "Pannello ShareCoveX", "Shared Directory Ready:": "Directory condivisa pronta:", "Shared Group": "Gruppo condiviso", + "Shared directories": "Cartelle condivise", "Shared directory created:": "Directory condivisa creata:", "Shared directory for consume and export": "Directory condivisa per consume ed export", "Shared directory for copy/sync operations": "Directory condivisa per copia e sincronizzazione", @@ -6997,6 +7005,7 @@ "Unable to identify the root filesystem. Aborting.": "Impossibile identificare il filesystem root. Interruzione.", "Unable to resolve system ZFS pool disks. Aborting.": "Impossibile individuare i dischi del pool ZFS di sistema. Interruzione.", "Unable to resolve system disk topology. Aborting.": "Impossibile determinare la topologia del disco di sistema. Interruzione.", + "Unapplied changes": "Modifiche non applicate", "Understand the security implications of privileged containers": "Comprendere le implicazioni sulla sicurezza dei container privilegiati", "Unexpected Proxmox inventory; recovery blocked": "Inventario Proxmox inatteso; ripristino bloccato", "Unexpected formatting directory in the new Valkey volume": "Directory di formattazione imprevista nel nuovo volume Valkey", diff --git a/lang/pt.json b/lang/pt.json index e95b4413..e9f79ec2 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Não foi possível verificar o disco reutilizado:", "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", "Category": "Categoria", "Caution: Maximum mode generates more heat.": "Cuidado: O modo máximo gera mais calor.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Verificação do Ceph ignorada pelo sinalizador do usuário (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Escolha uma programação (sintaxe de evento de calendário do systemd):", "Choose a script to execute:": "Escolha um script para executar:", "Choose a server:": "Escolha um servidor:", + "Choose a setting. Changes are applied only after the final review.": "Escolha o que configurar. As alterações só são aplicadas após a revisão final.", "Choose a share to mount:": "Escolha um compartilhamento para montar:", "Choose a specific supported GPU or USB node": "Escolha um GPU ou nó USB específico compatível", "Choose action for this controller/NVMe:": "Escolha a ação para este controlador/NVMe:", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Desativando e parando o serviço e soquete rpcbind...", "Disabling non-free firmware warnings...": "Desativando avisos de firmware não-livre...", "Disabling portmapper/rpcbind for security...": "Desativando portmapper/rpcbind por segurança...", + "Discard changes": "Descartar alterações", + "Discard unapplied changes?": "Descartar as alterações por aplicar?", "Discover targets on portal:": "Descubra alvos no portal:", "Discovering iSCSI targets...": "Descobrindo alvos iSCSI...", "Discovery successful": "Descoberta bem-sucedida", @@ -4261,6 +4265,7 @@ "No password provided.": "Nenhuma senha fornecida.", "No paths available": "Nenhum caminho disponível", "No paths selected. Select at least one path.": "Nenhum caminho selecionado. Selecione pelo menos um caminho.", + "No pending changes": "Sem alterações pendentes", "No pending paths to schedule for reboot.": "Não há caminhos pendentes para agendar a reinicialização.", "No permanent NFS mounts configured.": "Nenhuma montagem NFS permanente configurada.", "No permanent Samba mounts configured.": "Nenhuma montagem permanente do Samba configurada.", @@ -4402,6 +4407,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM removido (artefatos de marionetes podem precisar de limpeza manual)", "OVH server detected": "Servidor OVH detectado", "OVH server detection and RTM installation process completed": "Processo de detecção do servidor OVH e instalação RTM concluído", + "Object detector": "Detetor de objetos", "Object detector for Frigate": "Detector de objetos para Frigate", "Observer is not responding on port 4357": "Observador não está respondendo na porta 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian é um aplicativo de anotações que permite criar, vincular e organizar suas notas em seu dispositivo, com centenas de plugins e temas para personalizar seu fluxo de trabalho. Você também pode publicar suas notas online, acessá-las offline e sincronizá-las com segurança com criptografia de ponta a ponta.", @@ -5328,6 +5334,7 @@ "Reverting ZFS ARC tuning...": "Revertendo ajuste do ZFS ARC...", "Reverting pigz wrapper...": "Revertendo o wrapper pigz...", "Reverting vzdump speed tuning...": "Revertendo o ajuste de velocidade do vzdump...", + "Review and apply": "Rever e aplicar", "Review external OCI changes": "revisar alterações externas do OCI", "Review package upgrade": "Rever a atualização de pacotes", "Review passthrough config files": "Revise os arquivos de configuração de passagem", @@ -5787,6 +5794,7 @@ "ShareCoveX panel": "Painel ShareCoveX", "Shared Directory Ready:": "Diretório compartilhado pronto:", "Shared Group": "Grupo Compartilhado", + "Shared directories": "Diretórios partilhados", "Shared directory created:": "Directório partilhado criado:", "Shared directory for consume and export": "Diretório compartilhado para consumir e exportar", "Shared directory for copy/sync operations": "Diretório compartilhado para copiar/sincronizar operations", @@ -6997,6 +7005,7 @@ "Unable to identify the root filesystem. Aborting.": "Não foi possível identificar o sistema de arquivos raiz. Abortando.", "Unable to resolve system ZFS pool disks. Aborting.": "Não foi possível resolver os discos do pool ZFS do sistema. Abortando.", "Unable to resolve system disk topology. Aborting.": "Não é possível resolver a topologia do disco do sistema. Abortando.", + "Unapplied changes": "Alterações por aplicar", "Understand the security implications of privileged containers": "Entenda as implicações de segurança de contêineres privilegiados", "Unexpected Proxmox inventory; recovery blocked": "Inesperado inventário Proxmox; recuperação bloqueada", "Unexpected formatting directory in the new Valkey volume": "Directório de formatação inesperado no novo volume Valkey", diff --git a/lang/sk.json b/lang/sk.json index c29bb126..c72c1e92 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Nie je možné overiť znovu použitý disk:", "Cannot write the image archive to {storage}. Check storage access and mount status.": "Nie je možné zapísať archív obrázkov do {storage}. Skontrolujte prístup k úložisku a stav pripojenia.", "Capabilities cannot be kept and all dropped at the same time": "Nie je možné udržať si schopnosti a zároveň ich znížiť.", + "Catalog options": "Možnosti katalógu", "Category": "Kategória", "Caution: Maximum mode generates more heat.": "Pozor: maximálny režim vytvára viac tepla.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Kontrola Ceph preskočená používateľskou voľbou (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Vyberte plán (syntax systémového kalendára a udalosti):", "Choose a script to execute:": "Vyberte skript, ktorý sa má spustiť:", "Choose a server:": "Vyberte server:", + "Choose a setting. Changes are applied only after the final review.": "Vyberte nastavenie. Zmeny sa použijú až po záverečnej kontrole.", "Choose a share to mount:": "Vyberte zdieľanie na pripojenie:", "Choose a specific supported GPU or USB node": "Vyberte konkrétny podporovaný uzol GPU alebo USB", "Choose action for this controller/NVMe:": "Vyberte akciu pre tento Controller/NVMe:", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Zakázanie a zastavenie služby rpcbind a soketu...", "Disabling non-free firmware warnings...": "Vypínam upozornenia na non-free firmware...", "Disabling portmapper/rpcbind for security...": "Z bezpečnostných dôvodov vypínam portmapper/rpcbind...", + "Discard changes": "Zahodiť zmeny", + "Discard unapplied changes?": "Zahodiť nepoužité zmeny?", "Discover targets on portal:": "Vyhľadať targety na portáli:", "Discovering iSCSI targets...": "Vyhľadávam iSCSI targety...", "Discovery successful": "Vyhľadanie bolo úspešné", @@ -4261,6 +4265,7 @@ "No password provided.": "Nebolo zadané heslo.", "No paths available": "Nie sú dostupné žiadne cesty", "No paths selected. Select at least one path.": "Nie sú vybrané žiadne cesty. Vyberte aspoň jednu cestu.", + "No pending changes": "Žiadne čakajúce zmeny", "No pending paths to schedule for reboot.": "Nie sú žiadne čakajúce cesty, ktoré by sa mali naplánovať na reštart.", "No permanent NFS mounts configured.": "Nie sú nastavené žiadne trvalé NFS pripojenia.", "No permanent Samba mounts configured.": "Nie sú nastavené žiadne trvalé Samba pripojenia.", @@ -4404,6 +4409,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM bol odstránený (Puppet súbory môže byť potrebné vyčistiť ručne)", "OVH server detected": "Zistený OVH server", "OVH server detection and RTM installation process completed": "Kontrola OVH servera a inštalácia RTM sú dokončené", + "Object detector": "Detektor objektov", "Object detector for Frigate": "Detektor objektov pre Frigate", "Observer is not responding on port 4357": "Pozorovateľ nereaguje na prístav 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian je note-prijímajúce aplikácie, ktoré vám umožní vytvoriť, odkaz, a usporiadať poznámky na vašom zariadení, so stovkami pluginov a tém prispôsobiť svoje pracovné postupy. Môžete tiež zverejniť svoje poznámky on-line, prístup je offline, a synchronizovať ich bezpečne s end-to-end šifrovanie.", @@ -5330,6 +5336,7 @@ "Reverting ZFS ARC tuning...": "Vraciam späť ladenie ZFS ARC...", "Reverting pigz wrapper...": "Vraciam späť wrapper pigz...", "Reverting vzdump speed tuning...": "Vraciam späť ladenie rýchlosti vzdump...", + "Review and apply": "Skontrolovať a použiť", "Review external OCI changes": "Skontrolujte externé zmeny OCI", "Review package upgrade": "Skontrolovať aktualizáciu balíkov", "Review passthrough config files": "Skontrolovať konfiguračné súbory passthrough", @@ -5789,6 +5796,7 @@ "ShareCoveX panel": "Panel ShareCoveX", "Shared Directory Ready:": "Zdieľaný priečinok je pripravený:", "Shared Group": "Zdieľaná skupina", + "Shared directories": "Zdieľané priečinky", "Shared directory created:": "Vytvorený zdieľaný adresár:", "Shared directory for consume and export": "Zdieľaný adresár pre spotrebu a export", "Shared directory for copy/sync operations": "Zdieľaný adresár pre kopírovanie/sync operations", @@ -6998,6 +7006,7 @@ "Unable to identify the root filesystem. Aborting.": "Nepodarilo sa identifikovať koreňový súborový systém. Prerušuje sa.", "Unable to resolve system ZFS pool disks. Aborting.": "Nepodarilo sa vyriešiť systémové disky ZFS fondu. Prerušuje sa.", "Unable to resolve system disk topology. Aborting.": "Nepodarilo sa vyriešiť topológiu systémového disku. Prerušuje sa.", + "Unapplied changes": "Nepoužité zmeny", "Understand the security implications of privileged containers": "Uistite sa, že rozumiete bezpečnostným dôsledkom privilegovaných kontajnerov", "Unexpected Proxmox inventory; recovery blocked": "Neočakávaný inventár Proxmox; zablokovaná obnova", "Unexpected formatting directory in the new Valkey volume": "Neočakávaný formátovanie adresára v novom hlasitosti Valkey", diff --git a/lang/sv.json b/lang/sv.json index 377b3f16..5ee88288 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -749,6 +749,7 @@ "Cannot verify the reused disk:": "Kan inte verifiera den återanvända disken:", "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", "Category": "Kategori", "Caution: Maximum mode generates more heat.": "Varning: Maximalt läge genererar mer värme.", "Ceph check skipped by user flag (--ignore-ceph-check)": "Ceph-kontroll hoppas över av användarflaggan (--ignore-ceph-check)", @@ -883,6 +884,7 @@ "Choose a schedule (systemd calendar-event syntax):": "Välj ett schema (syntax för systemd kalenderhändelser):", "Choose a script to execute:": "Välj ett skript att köra:", "Choose a server:": "Välj en server:", + "Choose a setting. Changes are applied only after the final review.": "Välj en inställning. Ändringarna tillämpas först efter den slutliga granskningen.", "Choose a share to mount:": "Välj en resurs att montera:", "Choose a specific supported GPU or USB node": "Välj en specifik GPU- eller USB-nod som stöds", "Choose action for this controller/NVMe:": "Välj åtgärd för denna kontroller/NVMe:", @@ -1766,6 +1768,8 @@ "Disabling and stopping rpcbind service and socket...": "Inaktivera och stoppa rpcbind-tjänsten och -socket...", "Disabling non-free firmware warnings...": "Inaktiverar icke-fri firmware-varningar...", "Disabling portmapper/rpcbind for security...": "Inaktiverar portmapper/rpcbind för säkerhet...", + "Discard changes": "Kasta ändringar", + "Discard unapplied changes?": "Kasta ändringar som inte har tillämpats?", "Discover targets on portal:": "Upptäck mål på portalen:", "Discovering iSCSI targets...": "Upptäcker iSCSI-mål...", "Discovery successful": "Upptäckten lyckades", @@ -4261,6 +4265,7 @@ "No password provided.": "Inget lösenord angett.", "No paths available": "Inga vägar tillgängliga", "No paths selected. Select at least one path.": "Inga sökvägar har valts. Välj minst en väg.", + "No pending changes": "Inga väntande ändringar", "No pending paths to schedule for reboot.": "Inga väntande sökvägar att schemalägga för omstart.", "No permanent NFS mounts configured.": "Inga permanenta NFS-monteringar har konfigurerats.", "No permanent Samba mounts configured.": "Inga permanenta Samba-fästen har konfigurerats.", @@ -4402,6 +4407,7 @@ "OVH RTM removed (Puppet artefacts may need manual cleanup)": "OVH RTM har tagits bort (Puppet artefakter kan behöva manuell rengöring)", "OVH server detected": "OVH-server upptäckt", "OVH server detection and RTM installation process completed": "OVH-serverdetektering och RTM-installation har slutförts", + "Object detector": "Objektdetektor", "Object detector for Frigate": "Objektdetektor för Frigate", "Observer is not responding on port 4357": "Observer svarar inte på port 4357", "Obsidian is a note-taking app that lets you create, link, and organize your notes on your device, with hundreds of plugins and themes to customize your workflow. You can also publish your notes online, access them offline, and sync them securely with end-to-end encryption.": "Obsidian är en noterande app som låter dig skapa, länka och organisera dina anteckningar på din enhet, med hundratals plugins och teman för att anpassa ditt arbetsflöde. Du kan också publicera dina anteckningar online, komma åt dem offline och synkronisera dem säkert med end-to-end-kryptering.", @@ -5328,6 +5334,7 @@ "Reverting ZFS ARC tuning...": "Återställer ZFS ARC-inställning...", "Reverting pigz wrapper...": "Återställer pigz-omslag...", "Reverting vzdump speed tuning...": "Återställer vzdump-hastighetsinställning...", + "Review and apply": "Granska och tillämpa", "Review external OCI changes": "Granska externa OCI ändringar", "Review package upgrade": "Granska paketuppgraderingen", "Review passthrough config files": "Granska passthrough-konfigurationsfiler", @@ -5787,6 +5794,7 @@ "ShareCoveX panel": "ShareCoveX panel", "Shared Directory Ready:": "Redo för delad katalog:", "Shared Group": "Delad grupp", + "Shared directories": "Delade kataloger", "Shared directory created:": "Delad katalog skapad:", "Shared directory for consume and export": "Delad katalog för konsumering och export", "Shared directory for copy/sync operations": "Delad katalog för kopia/synkronisera operations", @@ -6997,6 +7005,7 @@ "Unable to identify the root filesystem. Aborting.": "Det går inte att identifiera rotfilsystemet. Avbryter.", "Unable to resolve system ZFS pool disks. Aborting.": "Det går inte att lösa system ZFS pooldiskar. Avbryter.", "Unable to resolve system disk topology. Aborting.": "Det gick inte att lösa systemdisktopologin. Avbryter.", + "Unapplied changes": "Ändringar som inte har tillämpats", "Understand the security implications of privileged containers": "Förstå säkerhetskonsekvenserna av privilegierade containrar", "Unexpected Proxmox inventory; recovery blocked": "Oväntad Proxmox inventering; återhämtning blockerad", "Unexpected formatting directory in the new Valkey volume": "Oväntad formateringskatalog i den nya Valkey-volymen", diff --git a/oci/src/proxmenux_oci/management.py b/oci/src/proxmenux_oci/management.py index cbf8e041..699a279a 100644 --- a/oci/src/proxmenux_oci/management.py +++ b/oci/src/proxmenux_oci/management.py @@ -423,25 +423,8 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): proposal = None if action == 'modify': from .recreation import edit_recreation - from .cli import _deployment_summary_text - from .ui import BacktrackUI, RestartWizard - wizard = BacktrackUI(ui) - try: - while True: - try: - proposal = edit_recreation(record, wizard) - approved = wizard.review(_deployment_summary_text(proposal['candidate']['template'], - proposal['candidate']['deployment']), translate('Modify OCI'), - question=translate('Apply these changes?'), default=True) - break - except RestartWizard: - wizard.restart() - except ValueError as error: - if not wizard.retry_last(error): - raise - finally: - wizard.close() - if not approved: + proposal = edit_recreation(record, ui) + if proposal is None: return False elif not ui.review(translate('If the image channel has a new version, the CT is stopped, backed up and verified, and replaced with the new image to update the container. If the update fails after the container was changed, the backup is restored. An update that stops halfway is recovered from the OCI management menu.'), translate('Update OCI'), question=translate('Update now?'), default=True): diff --git a/oci/src/proxmenux_oci/recreation.py b/oci/src/proxmenux_oci/recreation.py index d8ddf4b9..f0d6f691 100644 --- a/oci/src/proxmenux_oci/recreation.py +++ b/oci/src/proxmenux_oci/recreation.py @@ -4,7 +4,7 @@ import re from pathlib import Path, PurePosixPath from .i18n import translate -from .ui import UserCancelled +from .ui import BacktrackUI, RestartWizard, UserCancelled def positive_integer(ui, prompt, value, minimum=1): @@ -30,13 +30,13 @@ def edit_network(deployment, ui): network.update(bridge=bridge, ipv4=ipv4, gateway=gateway) -def edit_acceleration(candidate, ui): +def edit_acceleration(candidate, ui, confirm=True): from .installer import configure_acceleration deployment = candidate['deployment'] template = candidate.get('template', {}) installer = template.get('proxmox', {}).get('installer_profile', {}) hardware = installer.get('hardware_acceleration') - if not hardware or not ui.confirm(translate('Change GPU acceleration?'), False): + if not hardware or (confirm and not ui.confirm(translate('Change GPU acceleration?'), False)): return profiles = hardware.get('profiles', []) reference = template.get('container_contract', {}).get('image', {}).get('reference', '') @@ -104,44 +104,142 @@ def edit_environment(deployment, ui): environment.append(item) -def edit_recreation(record, ui): - candidate = copy.deepcopy(record) - refresh_template(candidate, ui) +def modification_options(candidate): + """Only offer supported settings; never put environment values in the menu.""" deployment = candidate['deployment'] resources = deployment['resources'] - resources['cores'] = positive_integer(ui, translate('Cores'), resources['cores']) - resources['memory_mb'] = positive_integer(ui, translate('RAM in MiB'), resources['memory_mb'], 128) + profile = candidate.get('template', {}).get('proxmox', {}).get('installer_profile', {}) + network = deployment.get('network', {}) + address = network.get('ipv4', 'dhcp') + + def row(label, value): + return f"{label:<26} {value}" + + options = [ + ('cores', row(translate('CPU cores'), resources['cores'])), + ('memory', row(translate('RAM in MiB'), resources['memory_mb'])), + ('network', row(translate('Network'), + f"{network.get('bridge', '-')} · {'DHCP' if address == 'dhcp' else address}")), + ('paths', row(translate('Extra paths'), len(deployment.get('mounts', [])))), + ] + if profile.get('shared_directories'): + options.append(('shares', translate('Shared directories'))) + if profile.get('hardware_acceleration'): + hardware = profile['hardware_acceleration'] + selected = next((p for p in hardware.get('profiles', []) + if p['id'] == deployment.get('hardware_profile')), {}) + options.append(('acceleration', row(translate('Acceleration'), + translate(selected.get('label') or deployment.get('hardware_profile') or 'CPU')))) + if profile.get('object_detector'): + options.append(('detector', translate('Object detector'))) + options += [ + ('devices', row(translate('Devices'), len(deployment.get('devices', [])))), + ('environment', row(translate('Variables'), len(deployment.get('environment', [])))), + ('catalog', translate('Catalog options')), + ('apply', translate('Review and apply')), + ('discard', translate('Discard changes')), + ] + return options + + +def edit_setting(candidate, ui, setting): + deployment = candidate['deployment'] template = candidate.get('template', {}) - if template.get('proxmox', {}).get('installer_profile', {}).get('shared_directories'): + profile = template.get('proxmox', {}).get('installer_profile', {}) + if setting == 'cores': + resources = deployment['resources'] + resources['cores'] = positive_integer(ui, translate('Cores'), resources['cores']) + elif setting == 'memory': + resources = deployment['resources'] + resources['memory_mb'] = positive_integer(ui, translate('RAM in MiB'), resources['memory_mb'], 128) + elif setting == 'network': + edit_network(deployment, ui) + elif setting == 'shares': from .installer import ask_shared_directories deployment['mounts'] += ask_shared_directories( ui, template, deployment['mounts'], deployment['rootfs']['storage'], True, optional=True, unprivileged=deployment.get('security', {}).get('unprivileged', True)) - from .custom_mounts import ask_custom_mounts - deployment['mounts'] = ask_custom_mounts( - ui, deployment['mounts'], deployment['rootfs']['storage']) - if ui.confirm(translate('Change the access network?'), False): - edit_network(deployment, ui) - edit_acceleration(candidate, ui) - from .installer import configure_detector - installer_profile = candidate.get('template', {}).get('proxmox', {}).get('installer_profile', {}) - deployment['devices'], notes = configure_detector(installer_profile, deployment.get('devices', []), ui) - if notes: - deployment['completion_notes'] = notes - from .extra_devices import ask_extra_devices - reference = candidate.get('template', {}).get('container_contract', {}).get('image', {}).get('reference', '') - repository = reference.split('@')[0].rsplit(':', 1)[0] - deployment['devices'] = ask_extra_devices( - ui, deployment.get('devices', []), deployment.get('security', {}).get('unprivileged', True), - allow_coral=repository in ('ghcr.io/blakeblackshear/frigate', 'codeproject/ai-server', 'docker.io/codeproject/ai-server')) - from .extra_devices import device_permissions - deployment['device_permissions'] = device_permissions( - reference, deployment['devices'], deployment.get('device_permissions')) - edit_environment(deployment, ui) - proposal = {'operation': 'recreate', 'candidate': candidate} - if record.get('observed', {}).get('config_sha256'): - proposal['base_config_sha256'] = record['observed']['config_sha256'] - return proposal + elif setting == 'paths': + from .custom_mounts import ask_custom_mounts + deployment['mounts'] = ask_custom_mounts(ui, deployment['mounts'], deployment['rootfs']['storage']) + elif setting == 'acceleration': + edit_acceleration(candidate, ui, confirm=False) + elif setting == 'detector': + from .installer import configure_detector + deployment['devices'], notes = configure_detector(profile, deployment.get('devices', []), ui) + if notes: + deployment['completion_notes'] = notes + elif setting == 'devices': + from .extra_devices import ask_extra_devices + reference = template.get('container_contract', {}).get('image', {}).get('reference', '') + repository = reference.split('@')[0].rsplit(':', 1)[0] + deployment['devices'] = ask_extra_devices( + ui, deployment.get('devices', []), deployment.get('security', {}).get('unprivileged', True), + allow_coral=repository in ('ghcr.io/blakeblackshear/frigate', 'codeproject/ai-server', 'docker.io/codeproject/ai-server')) + elif setting == 'environment': + edit_environment(deployment, ui) + elif setting == 'catalog': + refresh_template(candidate, ui) + else: + raise ValueError(setting) + if setting in ('acceleration', 'detector', 'devices'): + from .extra_devices import device_permissions + reference = candidate.get('template', {}).get('container_contract', {}).get('image', {}).get('reference', '') + permissions = device_permissions(reference, deployment.get('devices', []), deployment.get('device_permissions')) + if permissions is not None or 'device_permissions' in deployment: + deployment['device_permissions'] = permissions + + +def edit_section(candidate, ui, setting): + """A cancelled or invalid section cannot leave a half-edited draft.""" + wizard = BacktrackUI(ui) + try: + while True: + edited = copy.deepcopy(candidate) + try: + edit_setting(edited, wizard, setting) + return edited + except RestartWizard: + wizard.restart() + except UserCancelled: + return candidate + except ValueError as error: + if not wizard.retry_last(error): + ui.message(str(error), translate('Modify OCI')) + return candidate + finally: + wizard.close() + + +def edit_recreation(record, ui): + """Keep a draft across independently editable settings; apply it once.""" + from .cli import _deployment_summary_text + candidate = copy.deepcopy(record) + selected = 'cores' + while True: + pending = candidate != record + status = translate('Unapplied changes') if pending else translate('No pending changes') + text = (f"CT {record.get('vmid', '-')}: {candidate['deployment'].get('hostname', '')}\n" + f"{status}\n\n" + + translate('Choose a setting. Changes are applied only after the final review.')) + selected = ui.choose(text, modification_options(candidate), selected, + title=translate('Modify OCI'), show_tags=False) + if selected is None or selected == 'discard': + if not pending or ui.confirm(translate('Discard unapplied changes?'), False): + return None + selected = 'apply' + elif selected == 'apply': + if not pending: + ui.message(translate('Nothing was changed.'), translate('Modify OCI')) + return None + if ui.review(_deployment_summary_text(candidate['template'], candidate['deployment']), + translate('Modify OCI'), question=translate('Apply these changes?'), default=True): + proposal = {'operation': 'recreate', 'candidate': candidate} + if record.get('observed', {}).get('config_sha256'): + proposal['base_config_sha256'] = record['observed']['config_sha256'] + return proposal + else: + candidate = edit_section(candidate, ui, selected) def refresh_template(candidate, ui): diff --git a/oci/tests/test_modify_menu.py b/oci/tests/test_modify_menu.py new file mode 100644 index 00000000..2709da4f --- /dev/null +++ b/oci/tests/test_modify_menu.py @@ -0,0 +1,201 @@ +"""Selective OCI edits stay in a draft until reviewed, then use one transaction.""" +import copy +import json +from pathlib import Path +import stat +import sys +import unittest +from unittest.mock import Mock, patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'src')) +sys.path.insert(0, str(ROOT / 'remote')) + +from proxmenux_oci import management, recreation +from proxmenux_oci.ui import BackRequested, UserCancelled + + +def record(): + return {'vmid': 115, 'installation_id': 'test-installation', 'status': 'installed', + 'observed': {'config_sha256': 'native-config-fingerprint'}, + 'template': {'id': 'example-app', 'catalog_ui': {'title': 'Example'}, + 'container_contract': {'image': {'reference': 'example/app:latest'}}, + 'proxmox': {'installer_profile': {}}}, + 'deployment': {'vmid': 115, 'hostname': 'example', 'resources': {'cores': 2, 'memory_mb': 512}, + 'network': {'bridge': 'vmbr0', 'ipv4': 'dhcp', 'gateway': None}, + 'rootfs': {'storage': 'local-zfs', 'size_gb': 8}, 'mounts': [ + {'container_path': '/data', 'source': 'local-zfs', + 'type': 'managed-volume', 'size_gb': 8, 'backup': True}], + 'devices': [], 'security': {'unprivileged': True}, + 'environment': [{'name': 'PASSWORD', 'value': 'private-test-secret', + 'sensitive': True}]}} + + +class MenuUI: + def __init__(self, choices, answers=(), reviews=(True,), confirms=()): + self.choices, self.answers = iter(choices), iter(answers) + self.reviews, self.confirms = iter(reviews), iter(confirms) + self.events = [] + self.back_enabled = False + + def choose(self, text, options, default=None, **kwargs): + self.events.append(('menu', text, dict(options))) + value = next(self.choices) + if value is not None: + assert value in dict(options), value + return value + + def ask(self, text, default=None, **kwargs): + self.events.append(('ask', text, default)) + value = next(self.answers) + if isinstance(value, Exception): + raise value + return value + + def confirm(self, text, default=False): + self.events.append(('confirm', text)) + return next(self.confirms) + + def review(self, text, *args, **kwargs): + self.events.append(('review', text)) + return next(self.reviews) + + def message(self, text, *args): + self.events.append(('message', text)) + + +@patch('proxmenux_oci.i18n.language', return_value='en') +class ModifyMenuTests(unittest.TestCase): + def test_ram_only_does_not_ask_about_cpu_network_paths_devices_or_catalog(self, _language): + original = record() + saved = copy.deepcopy(original) + ui = MenuUI(['memory', 'apply'], ['768']) + with patch.object(recreation, 'refresh_template') as refresh: + result = recreation.edit_recreation(original, ui) + self.assertEqual(result['candidate']['deployment']['resources'], {'cores': 2, 'memory_mb': 768}) + self.assertEqual([e[1] for e in ui.events if e[0] == 'ask'], ['RAM in MiB']) + self.assertEqual(result['base_config_sha256'], 'native-config-fingerprint') + self.assertEqual(original, saved) + refresh.assert_not_called() + self.assertFalse(ui.back_enabled) + + def test_several_sections_and_revisits_keep_the_current_draft(self, _language): + ui = MenuUI(['memory', 'cores', 'memory', 'apply'], ['768', '3', '1024']) + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources'], {'cores': 3, 'memory_mb': 1024}) + self.assertEqual([e[2] for e in ui.events if e[0] == 'ask'], ['512', '2', '768']) + self.assertEqual(sum(e[0] == 'review' for e in ui.events), 1) + + def test_declining_review_returns_to_menu_without_losing_edits(self, _language): + ui = MenuUI(['cores', 'apply', 'memory', 'apply'], ['3', '768'], reviews=[False, True]) + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources'], {'cores': 3, 'memory_mb': 768}) + self.assertEqual(sum(e[0] == 'review' for e in ui.events), 2) + + def test_unchanged_apply_does_not_propose_recreating_the_guest(self, _language): + ui = MenuUI(['memory', 'apply'], ['512']) + self.assertIsNone(recreation.edit_recreation(record(), ui)) + self.assertFalse(any(e[0] == 'review' for e in ui.events)) + self.assertIn(('message', 'Nothing was changed.'), ui.events) + + def test_discard_leaves_the_saved_record_untouched(self, _language): + original = record() + saved = copy.deepcopy(original) + ui = MenuUI(['memory', 'discard'], ['768'], confirms=[True]) + self.assertIsNone(recreation.edit_recreation(original, ui)) + self.assertEqual(original, saved) + self.assertFalse(any(e[0] == 'review' for e in ui.events)) + + def test_declining_discard_preserves_the_draft(self, _language): + ui = MenuUI(['memory', None, 'apply'], ['768'], confirms=[False]) + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources']['memory_mb'], 768) + + def test_cancel_without_changes_asks_nothing_else(self, _language): + ui = MenuUI([None]) + self.assertIsNone(recreation.edit_recreation(record(), ui)) + self.assertEqual(len(ui.events), 1) + + def test_invalid_ram_is_retried_without_restarting_other_sections(self, _language): + ui = MenuUI(['cores', 'memory', 'apply'], ['3', 'invalid', '64', '768']) + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources'], {'cores': 3, 'memory_mb': 768}) + self.assertEqual(sum(e[0] == 'message' for e in ui.events), 2) + self.assertEqual(sum(e[:2] == ('ask', 'Cores') for e in ui.events), 1) + + def test_back_from_first_field_returns_to_menu_and_keeps_other_edits(self, _language): + ui = MenuUI(['cores', 'memory', 'apply'], ['3', BackRequested()]) + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources'], {'cores': 3, 'memory_mb': 512}) + self.assertFalse(ui.back_enabled) + + def test_cancelling_a_partly_edited_section_rolls_back_only_that_section(self, _language): + real_edit = recreation.edit_setting + def edit(candidate, ui, setting): + if setting == 'environment': + candidate['deployment']['environment'][0]['value'] = 'partial-value' + raise UserCancelled() + real_edit(candidate, ui, setting) + ui = MenuUI(['cores', 'environment', 'apply'], ['3']) + with patch.object(recreation, 'edit_setting', side_effect=edit): + result = recreation.edit_recreation(record(), ui) + self.assertEqual(result['candidate']['deployment']['resources']['cores'], 3) + self.assertEqual(result['candidate']['deployment']['environment'], record()['deployment']['environment']) + + def test_menu_and_review_do_not_expose_a_saved_secret(self, _language): + ui = MenuUI(['cores', 'apply'], ['3']) + recreation.edit_recreation(record(), ui) + self.assertNotIn('private-test-secret', json.dumps(ui.events)) + + def test_optional_categories_follow_the_application_profile(self, _language): + candidate = record() + options = dict(recreation.modification_options(candidate)) + self.assertTrue({'cores', 'memory', 'network', 'paths', 'devices', 'environment', 'catalog', 'apply'} <= options.keys()) + self.assertFalse({'shares', 'acceleration', 'detector'} & options.keys()) + candidate['template']['proxmox']['installer_profile'] = { + 'shared_directories': {'base_path': '/shares'}, 'object_detector': {'devices': []}, + 'hardware_acceleration': {'profiles': [{'id': 'intel', 'label': 'Intel GPU'}]}} + candidate['deployment']['hardware_profile'] = 'intel' + options = dict(recreation.modification_options(candidate)) + self.assertTrue({'shares', 'acceleration', 'detector'} <= options.keys()) + self.assertIn('Intel GPU', options['acceleration']) + + +class ModifyDispatchTests(unittest.TestCase): + def call_manager(self, proposal, lifecycle): + import oci_instances + import oci_instance_reconcile + saved = record() + row = {'vmid': 115, 'reason': 'matched', 'stack': False, 'pending': False, 'status': 'installed'} + with patch.object(management, 'carry_records', return_value={}), \ + patch.object(management, 'check_selected', return_value=row), \ + patch.object(management, '_work_backup_ready', return_value=True), \ + patch.object(oci_instances, 'read', return_value=saved), \ + patch.object(oci_instances, 'command', return_value='hostname: example'), \ + patch.object(oci_instance_reconcile, 'propose', return_value=None), \ + patch.object(recreation, 'edit_recreation', return_value=proposal), \ + patch.object(management, '_run_lifecycle', side_effect=lifecycle): + return management.manage_instance(ROOT, Mock(), row, action='modify') + + def test_cancel_does_not_start_a_lifecycle_transaction(self): + lifecycle = Mock() + self.assertFalse(self.call_manager(None, lifecycle)) + lifecycle.assert_not_called() + + def test_apply_sends_one_private_proposal_to_the_existing_backend(self): + proposal = {'operation': 'recreate', 'candidate': record(), 'base_config_sha256': 'fingerprint'} + seen = [] + def lifecycle(command, title): + path = Path(command[command.index('--proposal') + 1]) + seen.append(json.loads(path.read_text())) + self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o600) + self.assertNotIn('private-test-secret', ' '.join(command)) + self.assertIn('oci_update_current.py', command[1]) + self.assertIn('--acknowledge-external-data', command) + return True + self.assertTrue(self.call_manager(proposal, lifecycle)) + self.assertEqual(seen, [proposal]) + + +if __name__ == '__main__': + unittest.main() diff --git a/oci/tests/test_shared_directories.py b/oci/tests/test_shared_directories.py index d8204835..55545374 100644 --- a/oci/tests/test_shared_directories.py +++ b/oci/tests/test_shared_directories.py @@ -43,15 +43,18 @@ class ScriptedUI: def password(self, text, required=True): return self._answer(text, "") - def choose(self, text, options, default=None): + def choose(self, text, options, default=None, **_kwargs): return self._answer(text, default) def confirm(self, text, default=False): return self._answer(text, default) - def message(self, text): + def message(self, text, *_args): pass + def review(self, *_args, **_kwargs): + return True + @patch("proxmenux_oci.i18n.language", return_value="en") @patch("proxmenux_oci.installer.host.storages", side_effect=storages) @@ -223,14 +226,15 @@ class SharedDirectoriesTests(unittest.TestCase): patch("proxmenux_oci.recreation.edit_acceleration"), \ patch("proxmenux_oci.recreation.edit_environment"): ui = ScriptedUI({"Add a directory to share": [True], NAME: ["photos"], + "Choose a setting. Changes are applied only after the final review.": ["shares", "apply"], "Where to store /shares/photos": ["host-bind"], "Host path for /shares/photos": ["/tank/photos"]}) proposal = edit_recreation(record, ui) mounts = [m["container_path"] for m in proposal["candidate"]["deployment"]["mounts"]] self.assertEqual(mounts, ["/config", "/shares/media", "/shares/photos"]) - untouched = edit_recreation(record, ScriptedUI()) - self.assertEqual([m["container_path"] for m in untouched["candidate"]["deployment"]["mounts"]], - ["/config", "/shares/media"]) + untouched = edit_recreation(record, ScriptedUI({ + "Choose a setting. Changes are applied only after the final review.": ["discard"]})) + self.assertIsNone(untouched) self.assertEqual([m["container_path"] for m in plan["mounts"]], ["/config", "/shares/media"]) From 784ce13c1494820299b39c77ce43cbd608bbe02e Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:43:52 +0200 Subject: [PATCH 21/53] Reuse OCI images and apply resource edits without rebuilding --- .github/workflows/test-oci-registries.yml | 4 + lang/de.json | 2 + lang/es.json | 2 + lang/fr.json | 2 + lang/it.json | 2 + lang/pt.json | 2 + lang/sk.json | 2 + lang/sv.json | 2 + oci/remote/oci_resource_modify.py | 112 ++++++++ oci/remote/oci_update_current.py | 53 +++- oci/tests/test_modify_image_reuse.py | 312 ++++++++++++++++++++++ 11 files changed, 481 insertions(+), 14 deletions(-) create mode 100644 oci/remote/oci_resource_modify.py create mode 100644 oci/tests/test_modify_image_reuse.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..feb68709 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -28,6 +28,10 @@ jobs: python-version: '3.13' - name: Install OCI test dependencies run: python3 -m pip install -r oci/requirements.txt + - name: Run OCI image reuse and resource edit regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: python3 -m unittest discover -s oci/tests -p test_modify_image_reuse.py -v - name: Run offline registry account tests env: PYTHONDONTWRITEBYTECODE: '1' diff --git a/lang/de.json b/lang/de.json index 8d643f1a..6b0524e7 100644 --- a/lang/de.json +++ b/lang/de.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Die geprüfte Paketaktualisierung jetzt anwenden? Dienste können neu starten und zur Entfernung aufgeführte Pakete werden entfernt.", "Apply these changes?": "Diese Änderungen anwenden?", "Applying AMD-specific fixes...": "Anwenden von AMD-spezifischen Korrekturen...", + "Applying CPU and RAM changes without rebuilding the container...": "CPU- und RAM-Änderungen werden ohne Neuerstellung des Containers angewendet...", "Applying Changes": "Anwenden von Änderungen", "Applying Controller/NVMe passthrough to VM": "Anwenden von Controller/NVMe-Passthrough auf die VM", "Applying balanced memory optimization settings...": "Ausgewogene Einstellungen zur Speicheroptimierung werden angewendet...", @@ -6636,6 +6637,7 @@ "The repair must preserve the image dependencies:": "Die Reparatur muss die Bildabhängigkeiten bewahren:", "The requested VMID block is already in use": "Der angeforderte VMID-Block wird bereits verwendet", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "Das angeforderte Machine Learning-GPU-Profil funktioniert nicht; es wird nicht durch CPU ersetzt", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Die Ressourcenänderung konnte nicht vollständig rückgängig gemacht werden. Öffne die Verwaltung erneut, um die aktuellen Proxmox-Einstellungen zu prüfen und zu übernehmen.", "The restored service did not pass its health check": "Der wiederhergestellte Service bestand seinen Gesundheitscheck nicht", "The restored service stopped; the recovery is not confirmed": "Der wiederhergestellte Dienst wurde gestoppt; die Wiederherstellung wird nicht bestätigt", "The reviewed Tandoor stack does not require a privileged LXC.": "Der überprüfte Tandoor-Stack requi ist kein privilegierter LXC.", diff --git a/lang/es.json b/lang/es.json index de4d581e..4f8d1903 100644 --- a/lang/es.json +++ b/lang/es.json @@ -440,6 +440,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "¿Aplicar ahora la actualización de paquetes revisada? Los servicios podrían reiniciarse y se eliminarán los paquetes indicados para su eliminación.", "Apply these changes?": "¿Aplicar estos cambios?", "Applying AMD-specific fixes...": "Aplicando correcciones específicas de AMD...", + "Applying CPU and RAM changes without rebuilding the container...": "Aplicando los cambios de CPU y RAM sin recrear el contenedor...", "Applying Changes": "Aplicando cambios", "Applying Controller/NVMe passthrough to VM": "Aplicando el passthrough de controlador/NVMe a la VM", "Applying balanced memory optimization settings...": "Aplicando configuraciones equilibradas de optimización de memoria...", @@ -6612,6 +6613,7 @@ "The repair must preserve the image dependencies:": "La reparación debe preservar las dependencias de la imagen:", "The requested VMID block is already in use": "El bloque VMID solicitado ya está en uso", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "El perfil de GPU solicitado para Machine learning no funciona; no se sustituye por CPU", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "El cambio de recursos no se ha podido revertir por completo. Vuelve al menú de gestión para revisar e incorporar la configuración actual de Proxmox.", "The restored service did not pass its health check": "El servicio restaurado no superó su comprobación de estado", "The restored service stopped; the recovery is not confirmed": "El servicio restaurado se detuvo; la recuperación no se confirma", "The reviewed Tandoor stack does not require a privileged LXC.": "La pila de Tandoor revisada no necesita un LXC privilegiado.", diff --git a/lang/fr.json b/lang/fr.json index 2d3be751..c552a567 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Appliquer maintenant la mise à niveau des paquets examinée ? Des services peuvent redémarrer et les paquets indiqués pour suppression seront supprimés.", "Apply these changes?": "Appliquer ces modifications ?", "Applying AMD-specific fixes...": "Application de correctifs spécifiques à AMD...", + "Applying CPU and RAM changes without rebuilding the container...": "Application des modifications de CPU et de RAM sans recréer le conteneur...", "Applying Changes": "Application des modifications", "Applying Controller/NVMe passthrough to VM": "Application du relais Controller/NVMe à la VM", "Applying balanced memory optimization settings...": "Application des paramètres d'optimisation de la mémoire équilibrée...", @@ -6635,6 +6636,7 @@ "The repair must preserve the image dependencies:": "La réparation doit préserver les dépendances de l'image:", "The requested VMID block is already in use": "Le bloc VMID demandé est déjà utilisé", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "Le profil GPU d'apprentissage automatique demandé ne fonctionne pas; il n'est pas remplacé par CPU", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Impossible d’annuler entièrement la modification des ressources. Rouvrez le menu de gestion pour vérifier et importer les réglages actuels de Proxmox.", "The restored service did not pass its health check": "Le service restauré n'a pas réussi son bilan de santé", "The restored service stopped; the recovery is not confirmed": "Le service restauré s'est arrêté; la récupération n'est pas confirmée", "The reviewed Tandoor stack does not require a privileged LXC.": "La pile Tandoor revue ne require un LXC privilégié.", diff --git a/lang/it.json b/lang/it.json index 13e3c788..2be35ba8 100644 --- a/lang/it.json +++ b/lang/it.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Applicare ora l'aggiornamento dei pacchetti esaminato? I servizi potrebbero riavviarsi e i pacchetti indicati per la rimozione verranno rimossi.", "Apply these changes?": "Applicare queste modifiche?", "Applying AMD-specific fixes...": "Applicazione delle correzioni specifiche AMD...", + "Applying CPU and RAM changes without rebuilding the container...": "Applicazione delle modifiche a CPU e RAM senza ricreare il container...", "Applying Changes": "Applicazione delle modifiche", "Applying Controller/NVMe passthrough to VM": "Applicazione del passthrough Controller/NVMe alla VM", "Applying balanced memory optimization settings...": "Applicazione delle impostazioni bilanciate di ottimizzazione della memoria...", @@ -6636,6 +6637,7 @@ "The repair must preserve the image dependencies:": "La riparazione deve conservare le dipendenze dell'immagine:", "The requested VMID block is already in use": "Il blocco di VMID richiesto è già in uso", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "Il profilo GPU richiesto per il machine learning non funziona; non viene sostituito dalla CPU", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Non è stato possibile annullare completamente la modifica delle risorse. Riapri il menu di gestione per verificare e importare le impostazioni attuali di Proxmox.", "The restored service did not pass its health check": "Il servizio ripristinato non ha superato il controllo dello stato", "The restored service stopped; the recovery is not confirmed": "Il servizio ripristinato si è arrestato; il recupero non è confermato", "The reviewed Tandoor stack does not require a privileged LXC.": "Lo stack Tandoor esaminato non richiede un container LXC privilegiato.", diff --git a/lang/pt.json b/lang/pt.json index e95b4413..1c0a3622 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Aplicar agora a atualização de pacotes revista? Os serviços podem reiniciar e os pacotes indicados para remoção serão removidos.", "Apply these changes?": "Aplicar essas alterações?", "Applying AMD-specific fixes...": "Aplicando correções específicas da AMD...", + "Applying CPU and RAM changes without rebuilding the container...": "A aplicar as alterações de CPU e RAM sem recriar o contentor...", "Applying Changes": "Aplicando alterações", "Applying Controller/NVMe passthrough to VM": "Aplicando passagem de controlador/NVMe à VM", "Applying balanced memory optimization settings...": "Aplicando configurações de otimização de memória balanceadas...", @@ -6636,6 +6637,7 @@ "The repair must preserve the image dependencies:": "O reparo deve preservar as dependências da imagem:", "The requested VMID block is already in use": "O bloco VMID solicitado já está em uso", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "O perfil de aprendizado de máquina solicitado GPU não está funcionando; não é substituído por CPU", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Não foi possível reverter totalmente a alteração dos recursos. Volte a abrir o menu de gestão para rever e importar as definições atuais do Proxmox.", "The restored service did not pass its health check": "O serviço restaurado não passou no seu exame de saúde", "The restored service stopped; the recovery is not confirmed": "O serviço restaurado parou; a recuperação não é confirmada", "The reviewed Tandoor stack does not require a privileged LXC.": "A pilha Tandoor revisada não require um LXC privilegiado.", diff --git a/lang/sk.json b/lang/sk.json index c29bb126..5117ff23 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Použiť teraz skontrolovanú aktualizáciu balíkov? Služby sa môžu reštartovať a balíky označené na odstránenie budú odstránené.", "Apply these changes?": "Použiť tieto zmeny?", "Applying AMD-specific fixes...": "Používam opravy špecifické pre AMD...", + "Applying CPU and RAM changes without rebuilding the container...": "Používajú sa zmeny CPU a RAM bez opätovného vytvorenia kontajnera...", "Applying Changes": "Používam zmeny", "Applying Controller/NVMe passthrough to VM": "Používam priame priradenie Controller/NVMe do VM", "Applying balanced memory optimization settings...": "Používam vyvážené nastavenia optimalizácie pamäte...", @@ -6637,6 +6638,7 @@ "The repair must preserve the image dependencies:": "Oprava musí zachovať závislosť obrazu:", "The requested VMID block is already in use": "Požadovaný blok VMID sa už používa", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "Požadovaný profil strojového učenia sa GPU nefunguje; nie je nahradený CPU", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Zmenu prostriedkov sa nepodarilo úplne vrátiť späť. Znova otvorte správu aplikácií, skontrolujte aktuálne nastavenia systému Proxmox a prevezmite ich.", "The restored service did not pass its health check": "Obnovená služba neprešla kontrolou stavu", "The restored service stopped; the recovery is not confirmed": "Obnovená služba sa zastavila; obnova sa nepotvrdila", "The reviewed Tandoor stack does not require a privileged LXC.": "Preskúmaný Tandoor stack require privilegovaný LXC.", diff --git a/lang/sv.json b/lang/sv.json index 377b3f16..45d309ff 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -443,6 +443,7 @@ "Apply the reviewed package upgrade now? Services may restart and packages listed for removal will be removed.": "Tillämpa den granskade paketuppgraderingen nu? Tjänster kan startas om och paket som anges för borttagning kommer att tas bort.", "Apply these changes?": "Tillämpa dessa ändringar?", "Applying AMD-specific fixes...": "Tillämpar AMD-specifika korrigeringar...", + "Applying CPU and RAM changes without rebuilding the container...": "Tillämpar ändringarna av CPU och RAM utan att återskapa containern...", "Applying Changes": "Tillämpa ändringar", "Applying Controller/NVMe passthrough to VM": "Tillämpar styrenhets-/NVMe-passthrough på VM", "Applying balanced memory optimization settings...": "Tillämpar inställningar för balanserad minnesoptimering...", @@ -6636,6 +6637,7 @@ "The repair must preserve the image dependencies:": "Reparationen måste bevara bildberoenden:", "The requested VMID block is already in use": "Det begärda VMID-blocket används redan", "The requested machine learning GPU profile is not working; it is not replaced by CPU": "Den begärda maskininlärningen GPU-profilen fungerar inte; den ersätts inte av CPU.", + "The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.": "Det gick inte att helt återställa resursändringen. Öppna hanteringsmenyn igen för att granska och importera de aktuella Proxmox-inställningarna.", "The restored service did not pass its health check": "Den återställda tjänsten passerade inte hälsokontrollen", "The restored service stopped; the recovery is not confirmed": "Den återställda tjänsten stannade; återhämtningen bekräftas inte", "The reviewed Tandoor stack does not require a privileged LXC.": "Den granskade Tandoor stack inte require en privilegierad LXC.", diff --git a/oci/remote/oci_resource_modify.py b/oci/remote/oci_resource_modify.py new file mode 100644 index 00000000..6ae818bc --- /dev/null +++ b/oci/remote/oci_resource_modify.py @@ -0,0 +1,112 @@ +"""Apply CPU/RAM-only proposals to the existing guest, without an image archive.""" +import copy +import hashlib +from pathlib import Path +import uuid + +import oci_instances as instances +import oci_instance_transaction as transaction +from oci_installation_state import parse_config, sha +from oci_ui import translate, msg_info, msg_ok, msg_warn + +CONFIG_DIR = Path('/etc/pve/lxc') + + +def changes(record, candidate, config): + """Return a narrow native edit, or None when the normal installer is needed.""" + previous = transaction.with_default_healthcheck(copy.deepcopy(record)) + candidate = transaction.with_default_healthcheck(copy.deepcopy(candidate)) + old, new = previous['deployment'], candidate['deployment'] + if previous['template'] != candidate['template'] or old.get('host_monitor'): + return None + if {k: v for k, v in old.items() if k != 'resources'} != { + k: v for k, v in new.items() if k != 'resources'}: + return None + original, desired = old.get('resources', {}), new.get('resources', {}) + changed = {key for key in original.keys() | desired.keys() if original.get(key) != desired.get(key)} + if not changed or changed - {'cores', 'memory_mb'}: + return None + cfg = parse_config(config) + cpu = 'cpulimit' if 'cpulimit' in cfg and 'cores' not in cfg else 'cores' + parameters = {} + for key in sorted(changed): + value = desired.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value < (128 if key == 'memory_mb' else 1): + return None + native = 'memory' if key == 'memory_mb' else cpu + if native not in cfg: + return None + parameters[native] = str(value) + return parameters + + +def guarded_set(vmid, installation_id, expected, parameters): + # pct uses this SHA1 under its own configuration lock. Sample the native + # file before checking pct's view, so a concurrent edit cannot be accepted + # accidentally by taking a fresh digest after validation. + native = (CONFIG_DIR / f'{vmid}.conf').read_bytes() + current = instances.command('pct', 'config', str(vmid)) + if current != expected or instances.identity(current) != installation_id: + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + command = ['pct', 'set', str(vmid), '--digest', hashlib.sha1(native).hexdigest()] + for key, value in sorted(parameters.items()): + command += ['--' + key, value] + transaction.run(*command) + + +def expected_config(before, after, parameters): + def unrelated(config): + return b''.join(line for line in config.splitlines(keepends=True) + if line.split(b':', 1)[0].decode() not in parameters) + new = parse_config(after) + return unrelated(before) == unrelated(after) and all(new.get(key) == value for key, value in parameters.items()) + + +def apply(root, record, candidate, config, parameters): + """The caller holds the instance lock and has checked image identity/preflight.""" + if changes(record, candidate, config) != parameters: + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + vmid = record['vmid'] + directory = instances.location(root, vmid).parent / 'modifications' / uuid.uuid4().hex + instances.write(directory / 'before.json', {'record': record, 'config': config.decode()}) + transaction.open_log(directory) + msg_info(translate('Applying CPU and RAM changes without rebuilding the container...')) + applied = False + after = None + try: + guarded_set(vmid, record['installation_id'], config, parameters) + applied = True + after = instances.command('pct', 'config', str(vmid)) + if not expected_config(config, after, parameters): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + observed = record['observed'] + fresh = instances.observe(vmid, record['installation_id'], observed['archive_path'], + observed['resolved_registry_digest'], observed['image']) + if fresh['config_sha256'] != sha(after): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + updated = copy.deepcopy(record) + updated.update(deployment=copy.deepcopy(candidate['deployment']), observed=fresh) + instances.write(instances.location(root, vmid), updated) + except Exception: + if applied: + try: + current = instances.command('pct', 'config', str(vmid)) + if not expected_config(config, current, parameters): + raise ValueError('Concurrent configuration change; resource rollback refused') + previous = parse_config(config) + guarded_set(vmid, record['installation_id'], current, + {key: previous[key] for key in parameters}) + instances.write(instances.location(root, vmid), record) + except Exception as error: + transaction.log(f'resource rollback: {type(error).__name__}') + msg_warn(translate('The resource edit could not be fully rolled back. Open Manage again to review and import the current Proxmox settings.')) + raise + # Updating the carried record is best effort here, as it is after other + # management operations. The normal menu also refreshes it on return. + try: + import oci_carried_record + oci_carried_record.carry(root, vmid, mount_stopped=False) + except (OSError, ValueError, RuntimeError): + transaction.log('carried record will be refreshed on the next management entry') + msg_ok(translate('Changes applied. Data kept.')) + return directory diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index bba91c04..4ade01c1 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -8,6 +8,7 @@ import re import shlex import subprocess import sys +import tarfile import tempfile import oci_instances as instances @@ -56,7 +57,7 @@ def run_quiet(args, error, capture=False): process.wait() -def resolve_archive(desired, config, current=None, check=None, required_digest=None): +def resolve_archive(desired, config, current=None, check=None, required_digest=None, local_archives=()): # Shared by individual and coordinated operations; no guest mutation here. # When the registry still serves the current digest nothing is downloaded. reference = desired['template']['container_contract']['image']['reference'] @@ -83,6 +84,27 @@ def resolve_archive(desired, config, current=None, check=None, required_digest=N return None, digest if check: check() + verifier = Path(__file__).with_name('verify_oci_archive.py') + + def intact(path): + try: + run_quiet([sys.executable, str(verifier), str(path)], + translate('The image did not pass the integrity check')) + return same_image(candidate, image_from_archive(str(path))) + except (RuntimeError, OSError, ValueError, KeyError, tarfile.TarError): + return False + + # Initial installations and updates use different archive names. The + # instance record knows the actual archive; validate it before looking + # only for the deterministic update-cache name. Never delete a rejected + # recorded path: another instance may still refer to it. + for saved in local_archives: + path = Path(saved) if saved else None + if path and not path.is_symlink() and path.is_file(): + msg_info(translate('Verifying the image integrity...')) + if intact(path): + msg_ok(translate('Using the verified image from the cache')) + return path, digest storage = desired['deployment']['template_storage'] if not re.fullmatch(r'[A-Za-z0-9_-]+', storage): raise ValueError(translate('Invalid template storage')) @@ -91,16 +113,6 @@ def resolve_archive(desired, config, current=None, check=None, required_digest=N archive.parent.mkdir(parents=True, exist_ok=True) if archive.is_symlink(): raise ValueError(translate('Unsafe OCI archive path')) - verifier = Path(__file__).with_name('verify_oci_archive.py') - - def intact(path): - try: - run_quiet([sys.executable, str(verifier), str(path)], - translate('The image did not pass the integrity check')) - except RuntimeError: - return False - return same_image(candidate, image_from_archive(str(path))) - if archive.exists(): msg_info(translate('Verifying the image integrity...')) if intact(archive): @@ -181,10 +193,23 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non changes = transaction.external_changes(record, config) transaction.preflight(record, desired, config) msg_ok(translate('Container checked')) - current = record['observed']['image'] if operation == 'update' else None - archive, digest = resolve_archive(desired, config, current, lambda: work_backup.locate( - instances.location(instances.ROOT, vmid).parent / 'transactions' / 'check', [vmid], record, vmid)) + resource_changes = None + if operation == 'recreate' and not keep_backup: + import oci_resource_modify + resource_changes = oci_resource_modify.changes(record, desired, config) + current = record['observed']['image'] if operation == 'update' or resource_changes else None + check = lambda: work_backup.locate( + instances.location(instances.ROOT, vmid).parent / 'transactions' / 'check', [vmid], record, vmid) + if operation == 'recreate': + archive, digest = resolve_archive(desired, config, current, check, + local_archives=[record['observed'].get('archive_path')]) + else: + archive, digest = resolve_archive(desired, config, current, check) if archive is None: + if resource_changes: + import oci_operation_notice + with oci_operation_notice.operation([vmid], 'modify', application_name(record, vmid)): + return oci_resource_modify.apply(instances.ROOT, record, desired, config, resource_changes) msg_ok(translate('The image is already up to date; nothing was changed.')) return file_storage = None diff --git a/oci/tests/test_modify_image_reuse.py b/oci/tests/test_modify_image_reuse.py new file mode 100644 index 00000000..03020ced --- /dev/null +++ b/oci/tests/test_modify_image_reuse.py @@ -0,0 +1,312 @@ +"""Unchanged OCI images permit native resource edits or verified local reuse.""" +import copy +import hashlib +import json +from pathlib import Path +import re +import stat +import sys +import tempfile +import unittest +from unittest.mock import Mock, patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT/'remote')) +import oci_carried_record +import oci_instances as instances +import oci_resource_modify as resources +import oci_update_current as update + +DIGEST = 'sha256:'+'a'*64 +NEW = 'sha256:'+'b'*64 +IDENTITY = '11111111-1111-4111-8111-111111111111' +CONFIG = (f'arch: amd64\ncores: 2\ndescription: proxmenux-instance={IDENTITY}\n' + 'memory: 512\nrootfs: local-zfs:subvol-115-disk-0,size=8G\n' + 'lxc.environment.runtime: A=one\nlxc.environment.runtime: B=two\n').encode() + + +def record(): + return {'schema_version': 1, 'vmid': 115, 'installation_id': IDENTITY, 'status': 'installed', + 'template': {'id': 'example-app', 'catalog_ui': {'title': 'Example'}, + 'container_contract': {'image': {'reference': 'example/app:latest'}}, + 'proxmox': {'installer_profile': {'startup_healthcheck': {'type': 'running'}}}}, + 'deployment': {'vmid': 115, 'hostname': 'example', 'template_storage': 'local', + 'resources': {'cores': 2, 'memory_mb': 512, 'swap_mb': 512}, + 'network': {'bridge': 'vmbr0', 'ipv4': 'dhcp'}, 'mounts': [], 'devices': [], + 'environment': [{'name': 'PASSWORD', 'value': 'private-test-value', 'sensitive': True}]}, + 'observed': {'config': CONFIG.decode(), 'config_sha256': resources.sha(CONFIG), + 'image': {'manifest_digest': DIGEST, 'architecture': 'amd64', 'os': 'linux'}, + 'resolved_registry_digest': DIGEST, 'archive_path': '/missing/deleted-image.tar'}} + + +class ResourceModifyTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name)/'instances' + self.configs = Path(self.temp.name)/'configs' + self.configs.mkdir() + self.file = self.configs/'115.conf' + self.file.write_bytes(CONFIG) + self.saved = record() + self.candidate = copy.deepcopy(self.saved) + self.candidate['deployment']['resources'].update(cores=3, memory_mb=768) + self.params = {'cores': '3', 'memory': '768'} + instances.write(instances.location(self.root, 115), self.saved) + self.calls = [] + self.native_failure = None + def run(*args): + self.calls.append(args) + self.assertEqual(args[:3], ('pct', 'set', '115')) + expected = args[args.index('--digest')+1] + self.assertEqual(expected, hashlib.sha1(self.file.read_bytes()).hexdigest()) + if self.native_failure: + raise self.native_failure + text = self.file.read_text() + for index in range(5, len(args), 2): + text = re.sub(r'^'+args[index][2:]+r': .*$', args[index][2:]+': '+args[index+1], text, flags=re.M) + self.file.write_text(text) + return b'' + def observe(*args): + result = copy.deepcopy(self.saved['observed']) + result.update(config=self.file.read_text(), config_sha256=resources.sha(self.file.read_bytes())) + return result + patches = [patch.object(resources, 'CONFIG_DIR', self.configs), + patch.object(instances, 'command', side_effect=lambda *args: self.file.read_bytes()), + patch.object(resources.transaction, 'run', side_effect=run), + patch.object(resources.transaction, 'open_log'), patch.object(resources.transaction, 'log'), + patch.object(instances, 'observe', side_effect=observe), + patch.object(oci_carried_record, 'carry', return_value='carried')] + for p in patches: + p.start() + self.addCleanup(p.stop) + + def apply(self): + return resources.apply(self.root, self.saved, self.candidate, CONFIG, self.params) + + def test_resource_only_change_needs_no_archive_and_preserves_every_other_field(self): + directory = self.apply() + after = instances.read(self.root, 115) + self.assertEqual(after['deployment']['resources'], {'cores': 3, 'memory_mb': 768, 'swap_mb': 512}) + for key in ('network', 'mounts', 'devices', 'environment'): + self.assertEqual(after['deployment'][key], self.saved['deployment'][key]) + self.assertEqual(after['observed']['image'], self.saved['observed']['image']) + self.assertEqual(after['observed']['archive_path'], '/missing/deleted-image.tar') + self.assertEqual(after['observed']['config_sha256'], resources.sha(self.file.read_bytes())) + self.assertNotIn('pending_transaction', after) + self.assertEqual(len(self.calls), 1) + self.assertEqual(stat.S_IMODE((directory/'before.json').stat().st_mode), 0o600) + self.assertEqual(json.loads((directory/'before.json').read_text())['record'], self.saved) + self.assertNotIn('private-test-value', str(self.calls)) + + def test_network_environment_template_and_extra_device_edits_use_the_normal_installer(self): + for mutate in ( + lambda c: c['deployment']['network'].update(ipv4='192.0.2.2/24'), + lambda c: c['deployment']['environment'][0].update(value='changed'), + lambda c: c['template']['container_contract']['image'].update(reference='example/app:other'), + lambda c: c['deployment']['devices'].append({'host_path': '/dev/ttyACM0'}), + lambda c: c['deployment']['resources'].update(swap_mb=1024), + ): + candidate = copy.deepcopy(self.candidate) + mutate(candidate) + self.assertIsNone(resources.changes(self.saved, candidate, CONFIG)) + + def test_invalid_values_and_unchanged_proposal_do_not_use_the_native_path(self): + for value in (True, 0, -1, 64, 128.5, '768'): + candidate = copy.deepcopy(self.saved) + candidate['deployment']['resources']['memory_mb'] = value + self.assertIsNone(resources.changes(self.saved, candidate, CONFIG)) + self.assertIsNone(resources.changes(self.saved, copy.deepcopy(self.saved), CONFIG)) + + def test_cpu_limit_profile_stays_a_cpu_limit(self): + config = CONFIG.replace(b'cores: 2', b'cpulimit: 2') + self.assertEqual(resources.changes(self.saved, self.candidate, config), {'cpulimit': '3', 'memory': '768'}) + + def test_native_failure_leaves_saved_record_and_config_unchanged(self): + self.native_failure = RuntimeError('native API rejected edit') + with self.assertRaisesRegex(RuntimeError, 'rejected'): + self.apply() + self.assertEqual(self.file.read_bytes(), CONFIG) + self.assertEqual(instances.read(self.root, 115), self.saved) + + def test_observation_failure_rolls_back_resource_values(self): + with patch.object(instances, 'observe', side_effect=RuntimeError('observation failed')): + with self.assertRaisesRegex(RuntimeError, 'observation failed'): + self.apply() + self.assertEqual(self.file.read_bytes(), CONFIG) + self.assertEqual(instances.read(self.root, 115), self.saved) + self.assertEqual(len(self.calls), 2) + + def test_record_write_failure_rolls_back_resources(self): + real_write = instances.write + failed = False + def write(path, value): + nonlocal failed + if Path(path) == instances.location(self.root, 115) and not failed: + failed = True + raise OSError('record could not be saved') + real_write(path, value) + with patch.object(instances, 'write', side_effect=write): + with self.assertRaisesRegex(OSError, 'could not be saved'): + self.apply() + self.assertEqual(self.file.read_bytes(), CONFIG) + self.assertEqual(instances.read(self.root, 115), self.saved) + + def test_configuration_drift_is_rejected_before_native_set(self): + self.file.write_bytes(CONFIG.replace(b'memory: 512', b'memory: 1024')) + with self.assertRaisesRegex(ValueError, 'changed'): + self.apply() + self.assertEqual(self.calls, []) + + def test_repeated_environment_entries_are_all_preserved_by_the_guard(self): + changed = CONFIG.replace(b'cores: 2', b'cores: 3').replace(b'memory: 512', b'memory: 768') + self.assertTrue(resources.expected_config(CONFIG, changed, self.params)) + self.assertFalse(resources.expected_config(CONFIG, changed.replace(b'A=one', b'A=foreign'), self.params)) + + def test_a_concurrent_unrelated_change_is_not_overwritten_during_rollback(self): + def observe(*args): + self.file.write_bytes(self.file.read_bytes().replace(b'A=one', b'A=foreign')) + raise RuntimeError('concurrent edit') + with patch.object(instances, 'observe', side_effect=observe), patch.object(resources, 'msg_warn') as warn: + with self.assertRaisesRegex(RuntimeError, 'concurrent edit'): + self.apply() + self.assertIn(b'A=foreign', self.file.read_bytes()) + self.assertEqual(len(self.calls), 1) + warn.assert_called_once() + self.assertEqual(instances.read(self.root, 115), self.saved) + + def test_a_carried_record_error_does_not_undo_the_successful_edit(self): + with patch.object(oci_carried_record, 'carry', side_effect=RuntimeError('copy unavailable')): + self.apply() + self.assertEqual(instances.read(self.root, 115)['deployment']['resources']['memory_mb'], 768) + self.assertEqual(len(self.calls), 1) + + +class CachedArchiveTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.path = Path(self.temp.name)/'original-installation.tar' + self.path.write_bytes(b'fixture verified by stub') + self.desired = record() + self.image = {'manifest_digest': DIGEST} + self.candidate = dict(self.image, registry_digest=DIGEST) + + def resolve(self, paths, image=None): + def run(args, error, capture=False): + self.assertNotEqual(args[0], 'skopeo', 'No layers should be downloaded') + return json.dumps(self.candidate) if '-c' in args else b'' + with patch.object(update, 'run_quiet', side_effect=run), \ + patch.object(update, 'image_from_archive', return_value=image or self.image), \ + patch.object(instances, 'command') as command: + result = update.resolve_archive(self.desired, b'arch: amd64\n', local_archives=paths) + command.assert_not_called() + return result + + def test_original_installation_archive_is_reused_without_update_cache_lookup(self): + self.assertEqual(self.resolve([self.path]), (self.path, DIGEST)) + + def test_invalid_original_cache_is_kept_and_a_second_matching_archive_is_reused(self): + other = self.path.with_name('other.tar') + other.write_bytes(b'second fixture') + def image(path): + return {'manifest_digest': NEW if str(path) == str(self.path) else DIGEST} + with patch.object(update, 'run_quiet', side_effect=lambda args, *a, **kw: json.dumps(self.candidate) if '-c' in args else b''), \ + patch.object(update, 'image_from_archive', side_effect=image), \ + patch.object(instances, 'command') as command: + result = update.resolve_archive(self.desired, b'arch: amd64\n', local_archives=[self.path, other]) + self.assertEqual(result, (other, DIGEST)) + self.assertTrue(self.path.exists()) + command.assert_not_called() + + def test_a_new_digest_downloads_once_instead_of_using_an_old_archive(self): + candidate = {'manifest_digest': NEW, 'registry_digest': NEW} + cached = self.path.with_name('update.tar') + copies = [] + def run(args, *a, **kwargs): + if '-c' in args: + return json.dumps(candidate) + if args[0] == 'skopeo': + copies.append(args) + Path(args[-1].removeprefix('oci-archive:')).write_bytes(b'new verified fixture') + return b'' + def image(path): + return {'manifest_digest': DIGEST if str(path) == str(self.path) else NEW} + with patch.object(update, 'run_quiet', side_effect=run), \ + patch.object(update, 'image_from_archive', side_effect=image), \ + patch.object(instances, 'command', return_value=str(cached).encode()), \ + patch.object(update.work_backup, 'available', return_value=1024**4), \ + patch.object(update.transaction, 'log'): + result = update.resolve_archive(self.desired, b'arch: amd64\n', local_archives=[self.path]) + self.assertEqual(result, (cached, NEW)) + self.assertEqual(len(copies), 1) + self.assertIn('docker://example/app@'+NEW, copies[0]) + self.assertEqual(self.path.read_bytes(), b'fixture verified by stub') + + def test_a_broken_or_symlink_original_cache_does_not_hide_a_valid_update_cache(self): + cached = self.path.with_name('update.tar') + cached.write_bytes(b'valid update fixture') + link = self.path.with_name('link.tar') + link.symlink_to(self.path) + verified = [] + def run(args, *a, **kwargs): + if '-c' in args: + return json.dumps(self.candidate) + self.assertNotEqual(args[0], 'skopeo') + verified.append(Path(args[-1])) + if Path(args[-1]) == self.path: + raise RuntimeError('corrupt archive') + return b'' + with patch.object(update, 'run_quiet', side_effect=run), \ + patch.object(update, 'image_from_archive', return_value=self.image), \ + patch.object(instances, 'command', return_value=str(cached).encode()): + result = update.resolve_archive(self.desired, b'arch: amd64\n', local_archives=[link, self.path]) + self.assertEqual(result, (cached, DIGEST)) + self.assertNotIn(link, verified) + self.assertTrue(link.is_symlink()) + self.assertTrue(self.path.exists()) + + +class UpdateRoutingTests(unittest.TestCase): + def run_update(self, new_image=False, resource_edit=True): + saved = record() + candidate = copy.deepcopy(saved) + candidate['deployment']['resources']['memory_mb'] = 768 + if not resource_edit: + candidate['deployment']['environment'][0]['value'] = 'changed' + proposal = {'operation': 'recreate', 'candidate': candidate} + import contextlib, oci_operation_notice + with patch.object(instances, 'locked', return_value=contextlib.nullcontext()), \ + patch.object(instances, 'read', return_value=saved), \ + patch.object(instances, 'command', return_value=CONFIG), \ + patch.object(update.transaction, 'candidate_contract', return_value=candidate), \ + patch.object(update.transaction, 'external_changes', return_value={}), \ + patch.object(update.transaction, 'preflight'), \ + patch.object(update, 'resolve_archive', return_value=(Path('/new.tar') if new_image or not resource_edit else None, NEW if new_image else DIGEST)) as resolve, \ + patch.object(resources, 'apply') as apply_resources, \ + patch.object(update.transaction, 'apply') as apply_image, \ + patch.object(oci_operation_notice, 'operation', return_value=contextlib.nullcontext()): + update.update(115, proposal=proposal) + return resolve, apply_resources, apply_image + + def test_unchanged_image_routes_cpu_ram_edits_to_the_existing_guest(self): + resolve, native, image = self.run_update() + self.assertEqual(resolve.call_args.args[2]['manifest_digest'], DIGEST) + native.assert_called_once() + image.assert_not_called() + + def test_a_new_image_still_uses_the_normal_verified_recreation(self): + _resolve, native, image = self.run_update(new_image=True) + native.assert_not_called() + image.assert_called_once() + + def test_other_modifications_offer_the_recorded_archive_to_the_resolver(self): + resolve, native, image = self.run_update(resource_edit=False) + self.assertIsNone(resolve.call_args.args[2]) + self.assertEqual(resolve.call_args.kwargs['local_archives'], ['/missing/deleted-image.tar']) + native.assert_not_called() + image.assert_called_once() + + +if __name__ == '__main__': + unittest.main() From d286b650d78ca1300222b95ef2d835a358350f2c Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 16:49:39 +0200 Subject: [PATCH 22/53] Keep native resource edits independent of OCI recreation checks --- oci/remote/oci_resource_modify.py | 6 +++++ oci/remote/oci_update_current.py | 20 +++++++++------- oci/tests/test_modify_image_reuse.py | 35 ++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/oci/remote/oci_resource_modify.py b/oci/remote/oci_resource_modify.py index 6ae818bc..9d4083cf 100644 --- a/oci/remote/oci_resource_modify.py +++ b/oci/remote/oci_resource_modify.py @@ -14,6 +14,10 @@ CONFIG_DIR = Path('/etc/pve/lxc') def changes(record, candidate, config): """Return a narrow native edit, or None when the normal installer is needed.""" + if (any(key in record for key in ('stack', 'stack_member', 'native_stack_intent')) + or record['deployment'].get('stack_managed') + or instances.identity(config) != record['installation_id']): + return None previous = transaction.with_default_healthcheck(copy.deepcopy(record)) candidate = transaction.with_default_healthcheck(copy.deepcopy(candidate)) old, new = previous['deployment'], candidate['deployment'] @@ -66,6 +70,8 @@ def apply(root, record, candidate, config, parameters): """The caller holds the instance lock and has checked image identity/preflight.""" if changes(record, candidate, config) != parameters: raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + if transaction.external_changes(record, config): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) vmid = record['vmid'] directory = instances.location(root, vmid).parent / 'modifications' / uuid.uuid4().hex instances.write(directory / 'before.json', {'record': record, 'config': config.decode()}) diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index 4ade01c1..b5101fc9 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -191,13 +191,21 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non config = instances.command('pct', 'config', str(vmid)) desired = transaction.candidate_contract(record, operation, proposal) changes = transaction.external_changes(record, config) - transaction.preflight(record, desired, config) - msg_ok(translate('Container checked')) resource_changes = None - if operation == 'recreate' and not keep_backup: + if operation == 'recreate' and not keep_backup and not changes: import oci_resource_modify resource_changes = oci_resource_modify.changes(record, desired, config) - current = record['observed']['image'] if operation == 'update' or resource_changes else None + # CPU/RAM edits retain the installed image and all unrelated native + # settings. They do not implicitly update the application or depend + # on the registry and the narrower recreation profile. + if resource_changes: + msg_ok(translate('Container checked')) + import oci_operation_notice + with oci_operation_notice.operation([vmid], 'modify', application_name(record, vmid)): + return oci_resource_modify.apply(instances.ROOT, record, desired, config, resource_changes) + transaction.preflight(record, desired, config) + msg_ok(translate('Container checked')) + current = record['observed']['image'] if operation == 'update' else None check = lambda: work_backup.locate( instances.location(instances.ROOT, vmid).parent / 'transactions' / 'check', [vmid], record, vmid) if operation == 'recreate': @@ -206,10 +214,6 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non else: archive, digest = resolve_archive(desired, config, current, check) if archive is None: - if resource_changes: - import oci_operation_notice - with oci_operation_notice.operation([vmid], 'modify', application_name(record, vmid)): - return oci_resource_modify.apply(instances.ROOT, record, desired, config, resource_changes) msg_ok(translate('The image is already up to date; nothing was changed.')) return file_storage = None diff --git a/oci/tests/test_modify_image_reuse.py b/oci/tests/test_modify_image_reuse.py index 03020ced..95c7339d 100644 --- a/oci/tests/test_modify_image_reuse.py +++ b/oci/tests/test_modify_image_reuse.py @@ -163,6 +163,19 @@ class ResourceModifyTests(unittest.TestCase): self.assertTrue(resources.expected_config(CONFIG, changed, self.params)) self.assertFalse(resources.expected_config(CONFIG, changed.replace(b'A=one', b'A=foreign'), self.params)) + def test_cpu_change_preserves_homepage_dns_timezone_and_cpu_priority(self): + extra = b'nameserver: 10.0.1.1\ntimezone: Europe/Madrid\ncpuunits: 100\n' + config = CONFIG + extra + self.file.write_bytes(config) + self.saved['observed'].update(config=config.decode(), config_sha256=resources.sha(config)) + self.saved['deployment']['resources']['cpu_units'] = 100 + self.candidate = copy.deepcopy(self.saved) + self.candidate['deployment']['resources']['cores'] = 3 + instances.write(instances.location(self.root,115),self.saved) + resources.apply(self.root,self.saved,self.candidate,config,{'cores':'3'}) + self.assertEqual(self.file.read_bytes(), config.replace(b'cores: 2',b'cores: 3')) + self.assertEqual(instances.read(self.root,115)['deployment']['resources']['cpu_units'],100) + def test_a_concurrent_unrelated_change_is_not_overwritten_during_rollback(self): def observe(*args): self.file.write_bytes(self.file.read_bytes().replace(b'A=one', b'A=foreign')) @@ -268,7 +281,7 @@ class CachedArchiveTests(unittest.TestCase): class UpdateRoutingTests(unittest.TestCase): - def run_update(self, new_image=False, resource_edit=True): + def run_update(self, new_image=False, resource_edit=True, reject_recreation=False): saved = record() candidate = copy.deepcopy(saved) candidate['deployment']['resources']['memory_mb'] = 768 @@ -281,7 +294,7 @@ class UpdateRoutingTests(unittest.TestCase): patch.object(instances, 'command', return_value=CONFIG), \ patch.object(update.transaction, 'candidate_contract', return_value=candidate), \ patch.object(update.transaction, 'external_changes', return_value={}), \ - patch.object(update.transaction, 'preflight'), \ + patch.object(update.transaction, 'preflight', side_effect=ValueError('advanced Proxmox settings') if reject_recreation else None), \ patch.object(update, 'resolve_archive', return_value=(Path('/new.tar') if new_image or not resource_edit else None, NEW if new_image else DIGEST)) as resolve, \ patch.object(resources, 'apply') as apply_resources, \ patch.object(update.transaction, 'apply') as apply_image, \ @@ -291,15 +304,27 @@ class UpdateRoutingTests(unittest.TestCase): def test_unchanged_image_routes_cpu_ram_edits_to_the_existing_guest(self): resolve, native, image = self.run_update() - self.assertEqual(resolve.call_args.args[2]['manifest_digest'], DIGEST) + resolve.assert_not_called() native.assert_called_once() image.assert_not_called() - def test_a_new_image_still_uses_the_normal_verified_recreation(self): - _resolve, native, image = self.run_update(new_image=True) + def test_a_new_image_for_other_configuration_changes_uses_verified_recreation(self): + _resolve, native, image = self.run_update(new_image=True,resource_edit=False) native.assert_not_called() image.assert_called_once() + def test_cpu_edits_do_not_implicitly_upgrade_the_image(self): + resolve,native,image=self.run_update(new_image=True) + resolve.assert_not_called() + native.assert_called_once() + image.assert_not_called() + + def test_homepage_resource_edit_does_not_run_the_recreation_preflight(self): + resolve,native,image=self.run_update(reject_recreation=True) + resolve.assert_not_called() + native.assert_called_once() + image.assert_not_called() + def test_other_modifications_offer_the_recorded_archive_to_the_resolver(self): resolve, native, image = self.run_update(resource_edit=False) self.assertIsNone(resolve.call_args.args[2]) From b70e968f731125be077f29485b78e1c66b5f19ca Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:10:57 +0200 Subject: [PATCH 23/53] Apply OCI runtime settings to existing containers with recoverable edits --- .github/workflows/test-oci-registries.yml | 4 +- lang/de.json | 13 + lang/es.json | 13 + lang/fr.json | 13 + lang/it.json | 13 + lang/pt.json | 13 + lang/sk.json | 13 + lang/sv.json | 13 + oci/README.md | 17 +- oci/remote/oci_instance_transaction.py | 13 +- oci/remote/oci_native_modify.py | 396 ++++++++++++++++++++++ oci/remote/oci_update_current.py | 27 +- oci/src/proxmenux_oci/installer.py | 4 +- oci/tests/test_modify_image_reuse.py | 1 + oci/tests/test_native_modify.py | 258 ++++++++++++++ 15 files changed, 802 insertions(+), 9 deletions(-) create mode 100644 oci/remote/oci_native_modify.py create mode 100644 oci/tests/test_native_modify.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index feb68709..ca96aed2 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -31,7 +31,9 @@ jobs: - name: Run OCI image reuse and resource edit regressions env: PYTHONDONTWRITEBYTECODE: '1' - run: python3 -m unittest discover -s oci/tests -p test_modify_image_reuse.py -v + run: | + python3 -m unittest discover -s oci/tests -p test_modify_image_reuse.py -v + python3 -m unittest discover -s oci/tests -p test_native_modify.py -v - name: Run offline registry account tests env: PYTHONDONTWRITEBYTECODE: '1' diff --git a/lang/de.json b/lang/de.json index 6b0524e7..562eca99 100644 --- a/lang/de.json +++ b/lang/de.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Anwenden von Controller/NVMe-Passthrough auf die VM", "Applying balanced memory optimization settings...": "Ausgewogene Einstellungen zur Speicheroptimierung werden angewendet...", "Applying changes safely...": "Änderungen sicher anwenden...", + "Applying container settings without rebuilding the container...": "Containereinstellungen werden ohne Neuerstellung des Containers angewendet...", "Applying custom restore": "Benutzerdefinierte Wiederherstellung anwenden", "Applying default VM configuration": "Anwenden der Standard-VM-Konfiguration", "Applying host permissions for unprivileged LXC bind-mounts...": "Anwenden von Hostberechtigungen für nicht privilegierte LXC-Bind-Mounts ...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Wert für ungültig ALLOWED HOSTs", "Invalid Home Assistant OS check timeout": "Invalid Home Assistant OS Check Timeout", "Invalid ID": "Ungültige ID", + "Invalid IPv4 address": "Ungültige IPv4-Adresse", + "Invalid IPv4 gateway": "Ungültiges IPv4-Gateway", "Invalid Intel render path": "Ungültiger Intel-Renderpfad", "Invalid Jellyfin path:": "Ungültige Jellyfin-Strecke:", "Invalid MAC address:": "Ungültige MAC-Adresse:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "E-Mail des ungültigen Administrators", "Invalid administrator user name": "Ungültiger Administrator Benutzername", "Invalid base VMID": "Ungültige Basis VMID", + "Invalid bridge": "Ungültige Bridge", "Invalid check package:": "Ungültiges Kontrollpaket:", "Invalid configuration path:": "Ungültiger Konfigurationspfad:", "Invalid consume/export volumes": "Ungültige Verbrauchs-/Ausfuhrmengen", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Name der ungültigen Fernbedienung", "Invalid remote path": "Ungültiger Fernpfad", "Invalid repair version:": "Ungültige Reparaturversion:", + "Invalid resource value": "Ungültiger Ressourcenwert", "Invalid resources": "Ungültige Mittel", "Invalid restored volume path": "Ungültiger wiederhergestellter Volumenpfad", "Invalid running state": "Invaliditätsstatus", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Ungültige Stapelmitglieder", "Invalid stack name": "Name des ungültigen Stacks", "Invalid stack operation": "Ungültiger Stapel operation", + "Invalid storage": "Ungültiger Speicher", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "Ungültige Speicher-ID. Verwenden Sie nur Buchstaben, Zahlen, Bindestriche und Unterstriche.", "Invalid storage name": "Ungültiger Speichername", "Invalid suite application": "Anwendung ungültiger Suiten", @@ -4888,6 +4894,7 @@ "Proxmox VE configuration completed.": "Proxmox VE-Konfiguration abgeschlossen.", "Proxmox VE safe update completed": "Sicheres Proxmox VE-Update abgeschlossen", "Proxmox auth logger service created and started": "Proxmox-Auth-Logger-Dienst erstellt und gestartet", + "Proxmox could not apply the container settings": "Proxmox konnte die Containereinstellungen nicht anwenden", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Das Proxmox-Enterprise-Repository fehlt möglicherweise oder ist nicht zugänglich. Ich versuche, auf kein Abonnement umzustellen...", "Proxmox filter configured": "Proxmox-Filter konfiguriert", "Proxmox iSCSI Storage Status:": "Proxmox iSCSI-Speicherstatus:", @@ -6327,6 +6334,7 @@ "The build could not be checked beforehand; continuing without that check.": "Der Build konnte vorher nicht überprüft werden;ohne diese Prüfung weitermachen.", "The cached image does not match the current digest": "Das zwischengespeicherte Bild entspricht nicht dem aktuellen Digest", "The cached image is damaged; it will be downloaded again.": "Das zwischengespeicherte Bild ist beschädigt; es wird erneut heruntergeladen.", + "The changes need the application installer": "Die Änderungen erfordern das Installationsprogramm der Anwendung", "The character device does not exist:": "Das Zeichengerät existiert nicht:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "Der Befehl fragt nach einem Passwort, das nicht wiederholt wird. Nach dem Erstellen der Benutzer fragt die Weboberfläche nach einem Benutzernamen und einem Passwort.", "The command does not name an image": "Der Befehl benennt kein Bild", @@ -6446,6 +6454,7 @@ "The host bind source does not exist:": "Die Host-Bind-Quelle existiert nicht:", "The host bind source is not a regular file or directory:": "Die Host-Bind-Quelle ist keine reguläre Datei oder ein Verzeichnis:", "The host directory changed before it was mounted": "Das Host-Verzeichnis wurde geändert, bevor es gemountet wurde", + "The host directory does not exist": "Das Verzeichnis auf dem Host ist nicht vorhanden", "The host directory may not be accessible from an unprivileged container.": "Auf das Hostverzeichnis kann von einem unprivilegierten Container aus möglicherweise nicht zugegriffen werden.", "The host has no IPv4 address on the selected bridge": "Der Host hat keine IPv4-Adresse auf der ausgewählten Brücke", "The host monitor does not see the real host memory": "Der Host-Monitor sieht den echten Host-Speicher nicht", @@ -6683,6 +6692,7 @@ "The selected VM": "Die ausgewählte VM", "The selected VM is running.": "Die ausgewählte VM läuft.", "The selected base folder does not exist and could not be created:": "Der ausgewählte Basisordner existiert nicht und konnte nicht erstellt werden:", + "The selected bridge does not exist": "Die ausgewählte Bridge ist nicht vorhanden", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "Die ausgewählte Bridge verfügt über kein IPv4-Subnetz für die Host-Monitor-Firewall", "The selected configuration needs to start the LXC during the installation": "Die ausgewählte Konfiguration muss den LXC während der Installation starten", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "Der ausgewählte Container ist nicht privilegiert. Für den direkten Geräte-Passthrough ist ein privilegierter Container erforderlich.", @@ -6705,6 +6715,7 @@ "The service builds its own image; only a published image can be installed": "Der Dienst erstellt ein eigenes Image; nur ein veröffentlichtes Image kann installiert werden", "The service declares no image:": "Der Dienst erklärt kein Bild:", "The setting has no final value:": "Die Einstellung hat keinen Endwert:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Die Einstellungen konnten nicht vollständig zurückgesetzt werden. Prüfe den Container und die gespeicherte Änderung, bevor du fortfährst.", "The share already exists in smb.conf:": "Die Freigabe existiert bereits in smb.conf:", "The shared destination is not a directory": "Das gemeinsame Ziel ist kein Verzeichnis", "The shared directory points to a protected host path": "Das Shared Directory zeigt auf einen geschützten Hostpfad", @@ -6779,6 +6790,7 @@ "These interface configurations will be removed": "Diese Schnittstellenkonfigurationen werden entfernt", "These manual changes cannot be adopted safely:": "Diese manuellen Änderungen können nicht sicher übernommen werden:", "These paths will not be restored live and will be extracted for manual recovery.": "Diese Pfade werden nicht live wiederhergestellt und zur manuellen Wiederherstellung extrahiert.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Diese Einstellungen erfordern eine anwendungsspezifische Anpassung. Das Image wurde nicht heruntergeladen und der Container nicht neu erstellt.", "These values are asked during the installation:": "Diese Werte werden während der Installation abgefragt:", "They are described in the image documentation:": "Sie sind in der Dokumentation des Images beschrieben:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Sie werden zu oci-compose hinzugefügt, bevor Sie fortfahren. Nicht unterstützte oder geänderte Ressourcen werden nicht importiert.", @@ -7114,6 +7126,7 @@ "Unsupported mount type": "Typ der nicht unterstützten Halterung", "Unsupported mount type:": "Typ der nicht unterstützten Halterung:", "Unsupported native device type:": "Nicht unterstützter nativer Gerätetyp:", + "Unsupported network settings": "Nicht unterstützte Netzwerkeinstellungen", "Unsupported operation": "Nicht unterstützte operation", "Unsupported or unknown Proxmox version; no repository changed.": "Nicht unterstützte oder unbekannte Proxmox-Version; es wurde kein Repository geändert.", "Unsupported output format:": "Nicht unterstütztes Ausgabeformat:", diff --git a/lang/es.json b/lang/es.json index 4f8d1903..57a6014b 100644 --- a/lang/es.json +++ b/lang/es.json @@ -445,6 +445,7 @@ "Applying Controller/NVMe passthrough to VM": "Aplicando el passthrough de controlador/NVMe a la VM", "Applying balanced memory optimization settings...": "Aplicando configuraciones equilibradas de optimización de memoria...", "Applying changes safely...": "Aplicando cambios de forma segura...", + "Applying container settings without rebuilding the container...": "Aplicando los ajustes sin recrear el contenedor...", "Applying custom restore": "Aplicar restauración personalizada", "Applying default VM configuration": "Aplicar la configuración predeterminada de VM", "Applying host permissions for unprivileged LXC bind-mounts...": "Aplicando permisos de host para montajes de enlace LXC sin privilegios...", @@ -3211,6 +3212,8 @@ "Invalid ALLOWED_HOSTS value": "Valor inválido ALLOWED_HOSTS", "Invalid Home Assistant OS check timeout": "Inválido Home Assistant OS check timeout", "Invalid ID": "ID no válida", + "Invalid IPv4 address": "Dirección IPv4 no válida", + "Invalid IPv4 gateway": "Puerta de enlace IPv4 no válida", "Invalid Intel render path": "Ruta de renderización Intel no válida", "Invalid Jellyfin path:": "Camino inválido Jellyfin:", "Invalid MAC address:": "Dirección MAC inválida:", @@ -3243,6 +3246,7 @@ "Invalid administrator email": "Correo electrónico del administrador inválido", "Invalid administrator user name": "Nombre del usuario del administrador inválido", "Invalid base VMID": "Base inválida VMID", + "Invalid bridge": "Puente no válido", "Invalid check package:": "Paquete de comprobación inválido:", "Invalid configuration path:": "Vía de configuración inválida:", "Invalid consume/export volumes": "Volumen de consumo/exportación inválidos", @@ -3306,6 +3310,7 @@ "Invalid remote name": "Nombre remoto inválido", "Invalid remote path": "Camino remoto inválido", "Invalid repair version:": "Versión de reparación inválida:", + "Invalid resource value": "Valor de recursos no válido", "Invalid resources": "Recursos inválidos", "Invalid restored volume path": "Camino de volumen restaurado inválido", "Invalid running state": "Estado de funcionamiento inválido", @@ -3324,6 +3329,7 @@ "Invalid stack members": "Miembros de pila no válidos", "Invalid stack name": "Nombre de pila no válido", "Invalid stack operation": "Operación de pila no válida", + "Invalid storage": "Almacenamiento no válido", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "ID de almacenamiento no válido. Utilice únicamente letras, números, guiones y guiones bajos.", "Invalid storage name": "Nombre de almacenamiento no válido", "Invalid suite application": "Aplicación de suite inválida", @@ -4879,6 +4885,7 @@ "Proxmox VE configuration completed.": "Configuración de Proxmox VE completada.", "Proxmox VE safe update completed": "Actualización segura de Proxmox VE completada", "Proxmox auth logger service created and started": "Servicio de registro de autenticación Proxmox creado e iniciado", + "Proxmox could not apply the container settings": "Proxmox no ha podido aplicar los ajustes del contenedor", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Es posible que falte el repositorio empresarial de Proxmox o que no se pueda acceder a él. Intentando cambiar a sin suscripción...", "Proxmox filter configured": "Filtro Proxmox configurado", "Proxmox iSCSI Storage Status:": "Estado de almacenamiento Proxmox iSCSI:", @@ -6310,6 +6317,7 @@ "The build could not be checked beforehand; continuing without that check.": "La compilación no se pudo comprobar de antemano; se continúa sin esa comprobación.", "The cached image does not match the current digest": "La imagen en caché no coincide con el digest actual", "The cached image is damaged; it will be downloaded again.": "La imagen en caché está dañada; se descargará de nuevo.", + "The changes need the application installer": "Los cambios requieren el instalador de la aplicación", "The character device does not exist:": "El dispositivo de carácter no existe:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "El comando pide una contraseña que no se muestra al escribirla. Tras crear los usuarios, la interfaz web pide un nombre de usuario y una contraseña.", "The command does not name an image": "El comando no nombra ninguna imagen", @@ -6428,6 +6436,7 @@ "The host bind source does not exist:": "La fuente de host bind no existe:", "The host bind source is not a regular file or directory:": "La fuente host bind no es un archivo o directorio regular:", "The host directory changed before it was mounted": "El directorio host cambió antes de montarlo", + "The host directory does not exist": "El directorio del host no existe", "The host directory may not be accessible from an unprivileged container.": "Es posible que no se pueda acceder al directorio del host desde un contenedor sin privilegios.", "The host has no IPv4 address on the selected bridge": "El host no tiene dirección IPv4 en el puente seleccionado", "The host monitor does not see the real host memory": "El monitor host no ve la memoria real del host", @@ -6659,6 +6668,7 @@ "The selected VM": "La máquina virtual seleccionada", "The selected VM is running.": "La VM seleccionada se está ejecutando.", "The selected base folder does not exist and could not be created:": "La carpeta base seleccionada no existe y no se pudo crear:", + "The selected bridge does not exist": "El puente seleccionado no existe", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "El bridge seleccionado no tiene subred IPv4 para el cortafuegos del monitor del host", "The selected configuration needs to start the LXC during the installation": "La configuración seleccionada debe iniciar el LXC durante la instalación", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "El contenedor seleccionado no tiene privilegios. Se requiere un contenedor privilegiado para el passthrough directo de dispositivos.", @@ -6681,6 +6691,7 @@ "The service builds its own image; only a published image can be installed": "El servicio construye su propia imagen; solo se puede instalar una imagen publicada", "The service declares no image:": "El servicio no declara imagen:", "The setting has no final value:": "El ajuste no tiene valor final:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "No se han podido revertir todos los ajustes. Revisa el contenedor y la modificación guardada antes de continuar.", "The share already exists in smb.conf:": "El recurso compartido ya existe en smb.conf:", "The shared destination is not a directory": "El destino compartido no es un directorio", "The shared directory points to a protected host path": "El directorio compartido apunta a una ruta de host protegida", @@ -6754,6 +6765,7 @@ "These interface configurations will be removed": "Estas configuraciones de interfaz serán eliminadas", "These manual changes cannot be adopted safely:": "Estos cambios manuales no se pueden incorporar con seguridad:", "These paths will not be restored live and will be extracted for manual recovery.": "Estas rutas no se restaurarán en vivo y se extraerán para su recuperación manual.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Estos ajustes requieren una adaptación específica de la aplicación. No se ha descargado la imagen ni se ha recreado el contenedor.", "These values are asked during the installation:": "Estos valores se preguntan durante la instalación:", "They are described in the image documentation:": "Están descritos en la documentación de la imagen:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Se añadirán al oci-compose antes de continuar. No se importarán recursos incompatibles o modificados.", @@ -7089,6 +7101,7 @@ "Unsupported mount type": "Tipo de montaje no compatible", "Unsupported mount type:": "Tipo de montaje no compatible:", "Unsupported native device type:": "Tipo de dispositivo nativo sin soporte:", + "Unsupported network settings": "Ajustes de red no compatibles", "Unsupported operation": "Operación sin soporte", "Unsupported or unknown Proxmox version; no repository changed.": "Versión de Proxmox no compatible o desconocida; no se ha modificado ningún repositorio.", "Unsupported output format:": "Formato de salida no admitido:", diff --git a/lang/fr.json b/lang/fr.json index c552a567..1f37b051 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Application du relais Controller/NVMe à la VM", "Applying balanced memory optimization settings...": "Application des paramètres d'optimisation de la mémoire équilibrée...", "Applying changes safely...": "Appliquer les modifications en toute sécurité...", + "Applying container settings without rebuilding the container...": "Application des paramètres sans recréer le conteneur...", "Applying custom restore": "Application d'une restauration personnalisée", "Applying default VM configuration": "Application de la configuration de VM par défaut", "Applying host permissions for unprivileged LXC bind-mounts...": "Application des autorisations d'hôte pour les montages de liaison LXC non privilégiés...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Valeur HÔTES INDÉSIRABLES", "Invalid Home Assistant OS check timeout": "Non valide Home Assistant OS check timeout", "Invalid ID": "Pièce d'identité invalide", + "Invalid IPv4 address": "Adresse IPv4 non valide", + "Invalid IPv4 gateway": "Passerelle IPv4 non valide", "Invalid Intel render path": "Chemin de rendu Intel incorrect", "Invalid Jellyfin path:": "Chemin Jellyfin non valide:", "Invalid MAC address:": "Adresse MAC non valide:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "Courriel administrateur non valide", "Invalid administrator user name": "Nom d'utilisateur non valide", "Invalid base VMID": "VMID de base non valide", + "Invalid bridge": "Pont réseau non valide", "Invalid check package:": "Dossier de contrôle non valide:", "Invalid configuration path:": "Chemin de configuration non valide & #160;:", "Invalid consume/export volumes": "Volumes de consommation/d'exportation non valides", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Nom distant non valide", "Invalid remote path": "Voie distante non valide", "Invalid repair version:": "Version de réparation non valide:", + "Invalid resource value": "Valeur de ressource non valide", "Invalid resources": "Ressources non valides", "Invalid restored volume path": "Voie de volume restaurée non valide", "Invalid running state": "État de fonctionnement non valide", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Membres de pile non valides", "Invalid stack name": "Nom de la pile non valide", "Invalid stack operation": "Pile non valide operation", + "Invalid storage": "Stockage non valide", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "ID de stockage invalide. Utilisez uniquement des lettres, des chiffres, des traits d'union et des traits de soulignement.", "Invalid storage name": "Nom de stockage invalide", "Invalid suite application": "Demande de suite non valide", @@ -4888,6 +4894,7 @@ "Proxmox VE configuration completed.": "Configuration de Proxmox VE terminée.", "Proxmox VE safe update completed": "Mise à jour sécurisée de Proxmox VE terminée", "Proxmox auth logger service created and started": "Service d'enregistrement d'authentification Proxmox créé et démarré", + "Proxmox could not apply the container settings": "Proxmox n’a pas pu appliquer les paramètres du conteneur", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Le référentiel d'entreprise Proxmox peut être manquant ou inaccessible. J'essaie de passer au sans abonnement...", "Proxmox filter configured": "Filtre Proxmox configuré", "Proxmox iSCSI Storage Status:": "État du stockage Proxmox iSCSI :", @@ -6326,6 +6333,7 @@ "The build could not be checked beforehand; continuing without that check.": "La build n'a pas pu être vérifiée au préalable ;continuer sans cette vérification.", "The cached image does not match the current digest": "L'image mise en cache ne correspond pas au digest actuel", "The cached image is damaged; it will be downloaded again.": "L'image mise en cache est endommagée ; elle sera à nouveau téléchargée.", + "The changes need the application installer": "Ces modifications nécessitent le programme d’installation de l’application", "The character device does not exist:": "Le dispositif de caractère n'existe pas :", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "La commande demande un mot de passe qui n'est pas repris. Après la création des utilisateurs, l'interface web demande un nom d'utilisateur et un mot de passe.", "The command does not name an image": "La commande ne nomme pas une image", @@ -6445,6 +6453,7 @@ "The host bind source does not exist:": "La source de liaison hôte n'existe pas:", "The host bind source is not a regular file or directory:": "La source de liaison hôte n'est pas un fichier ou un répertoire régulier :", "The host directory changed before it was mounted": "Le répertoire hôte a changé avant son montage", + "The host directory does not exist": "Le répertoire de l’hôte n’existe pas", "The host directory may not be accessible from an unprivileged container.": "Le répertoire hôte peut ne pas être accessible à partir d'un conteneur non privilégié.", "The host has no IPv4 address on the selected bridge": "L'hôte n'a pas d'adresse IPv4 sur le pont sélectionné", "The host monitor does not see the real host memory": "Le moniteur hôte ne voit pas la mémoire de l'hôte réel", @@ -6682,6 +6691,7 @@ "The selected VM": "La VM sélectionnée", "The selected VM is running.": "La VM sélectionnée est en cours d'exécution.", "The selected base folder does not exist and could not be created:": "Le dossier de base sélectionné n'existe pas et n'a pas pu être créé :", + "The selected bridge does not exist": "Le pont réseau sélectionné n’existe pas", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "Le pont sélectionné n'a pas de sous-réseau IPv4 pour le pare-feu du moniteur hôte", "The selected configuration needs to start the LXC during the installation": "La configuration sélectionnée doit démarrer le LXC pendant l'installation", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "Le conteneur sélectionné n'est pas privilégié. Un conteneur privilégié est requis pour le relais direct du périphérique.", @@ -6704,6 +6714,7 @@ "The service builds its own image; only a published image can be installed": "Le service construit sa propre image; seule une image publiée peut être installée", "The service declares no image:": "Le service ne déclare aucune image:", "The setting has no final value:": "Le réglage n'a pas de valeur finale:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Les paramètres n’ont pas pu être entièrement rétablis. Vérifiez le conteneur et la modification enregistrée avant de continuer.", "The share already exists in smb.conf:": "Le partage existe déjà dans smb.conf :", "The shared destination is not a directory": "La destination partagée n'est pas un répertoire", "The shared directory points to a protected host path": "Le répertoire partagé indique un chemin d'hôte protégé", @@ -6778,6 +6789,7 @@ "These interface configurations will be removed": "Ces configurations d'interface seront supprimées", "These manual changes cannot be adopted safely:": "Ces modifications manuelles ne peuvent pas être adoptées en toute sécurité :", "These paths will not be restored live and will be extracted for manual recovery.": "Ces chemins ne seront pas restaurés en direct et seront extraits pour une récupération manuelle.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Ces paramètres nécessitent une adaptation propre à l’application. L’image n’a pas été téléchargée et le conteneur n’a pas été recréé.", "These values are asked during the installation:": "Ces valeurs sont demandées lors de l'installation:", "They are described in the image documentation:": "Ils sont décrits dans la documentation de l'image :", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Ils seront ajoutés à oci-compose avant de continuer. Les ressources non prises en charge ou modifiées ne sont pas importées.", @@ -7113,6 +7125,7 @@ "Unsupported mount type": "Type de montage non supporté", "Unsupported mount type:": "Type de montage non pris en charge:", "Unsupported native device type:": "Type de périphérique natif non pris en charge:", + "Unsupported network settings": "Paramètres réseau non pris en charge", "Unsupported operation": "operation non prise en charge", "Unsupported or unknown Proxmox version; no repository changed.": "Version Proxmox non prise en charge ou inconnue ; aucun référentiel n'a été modifié.", "Unsupported output format:": "Format de sortie non pris en charge :", diff --git a/lang/it.json b/lang/it.json index 2be35ba8..cddb6f85 100644 --- a/lang/it.json +++ b/lang/it.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Applicazione del passthrough Controller/NVMe alla VM", "Applying balanced memory optimization settings...": "Applicazione delle impostazioni bilanciate di ottimizzazione della memoria...", "Applying changes safely...": "Applicazione sicura delle modifiche...", + "Applying container settings without rebuilding the container...": "Applicazione delle impostazioni senza ricreare il container...", "Applying custom restore": "Applicazione del ripristino personalizzato", "Applying default VM configuration": "Applicazione della configurazione predefinita della VM", "Applying host permissions for unprivileged LXC bind-mounts...": "Applicazione delle autorizzazioni sull’host per i bind mount dei container LXC non privilegiati...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Valore ALLOWED_HOSTS non valido", "Invalid Home Assistant OS check timeout": "Timeout controllo Home Assistant OS non valido", "Invalid ID": "ID non valido", + "Invalid IPv4 address": "Indirizzo IPv4 non valido", + "Invalid IPv4 gateway": "Gateway IPv4 non valido", "Invalid Intel render path": "Percorso di rendering Intel non valido", "Invalid Jellyfin path:": "Percorso Jellyfin non valido:", "Invalid MAC address:": "Indirizzo MAC non valido:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "Email amministratore non valida", "Invalid administrator user name": "Nome utente amministratore non valido", "Invalid base VMID": "VMID di base non valido", + "Invalid bridge": "Bridge non valido", "Invalid check package:": "Pacchetto controllo non valido:", "Invalid configuration path:": "Percorso configurazione non valido:", "Invalid consume/export volumes": "Volumi di acquisizione/esportazione non validi", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Nome del remote non valido", "Invalid remote path": "Percorso remoto non valido", "Invalid repair version:": "Versione di riparazione non valida:", + "Invalid resource value": "Valore della risorsa non valido", "Invalid resources": "Risorse non valide", "Invalid restored volume path": "Percorso volume ripristinato non valido", "Invalid running state": "Stato di esecuzione non valido", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Membri stack non validi", "Invalid stack name": "Nome stack non valido", "Invalid stack operation": "Operazione stack non valida", + "Invalid storage": "Archiviazione non valida", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "ID storage non valido. Usa solo lettere, numeri, trattini e trattini bassi.", "Invalid storage name": "Nome storage non valido", "Invalid suite application": "Applicazione suite non valida", @@ -4888,6 +4894,7 @@ "Proxmox VE configuration completed.": "Configurazione Proxmox VE completata.", "Proxmox VE safe update completed": "Aggiornamento sicuro di Proxmox VE completato", "Proxmox auth logger service created and started": "Servizio log autenticazione Proxmox creato e avviato", + "Proxmox could not apply the container settings": "Proxmox non è riuscito ad applicare le impostazioni del container", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Il repository enterprise Proxmox potrebbe essere mancante o inaccessibile. Tentativo di passare al repository no-subscription...", "Proxmox filter configured": "Filtro Proxmox configurato", "Proxmox iSCSI Storage Status:": "Stato storage iSCSI Proxmox:", @@ -6327,6 +6334,7 @@ "The build could not be checked beforehand; continuing without that check.": "Non è stato possibile verificare la build in anticipo; si prosegue senza tale verifica.", "The cached image does not match the current digest": "L'immagine nella cache non corrisponde al digest corrente", "The cached image is damaged; it will be downloaded again.": "L'immagine nella cache è danneggiata; verrà scaricata nuovamente.", + "The changes need the application installer": "Le modifiche richiedono il programma di installazione dell’applicazione", "The character device does not exist:": "Il dispositivo a caratteri non esiste:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "Il comando richiede una password non visualizzata durante la digitazione. Dopo la creazione degli utenti, l’interfaccia web richiede nome utente e password.", "The command does not name an image": "Il comando non specifica un’immagine", @@ -6446,6 +6454,7 @@ "The host bind source does not exist:": "La sorgente del bind mount sull’host non esiste:", "The host bind source is not a regular file or directory:": "La sorgente del bind mount sull'host non è un file regolare né una directory:", "The host directory changed before it was mounted": "La directory dell'host è cambiata prima del montaggio", + "The host directory does not exist": "La directory dell’host non esiste", "The host directory may not be accessible from an unprivileged container.": "La directory dell’host potrebbe non essere accessibile da un container non privilegiato.", "The host has no IPv4 address on the selected bridge": "L'host non ha un indirizzo IPv4 sul bridge selezionato", "The host monitor does not see the real host memory": "Il monitoraggio dell'host non rileva la memoria reale dell'host", @@ -6683,6 +6692,7 @@ "The selected VM": "La VM selezionata", "The selected VM is running.": "La VM selezionata è in esecuzione.", "The selected base folder does not exist and could not be created:": "La cartella di base selezionata non esiste e non è stato possibile crearla:", + "The selected bridge does not exist": "Il bridge selezionato non esiste", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "Il bridge selezionato non ha una sottorete IPv4 per il firewall del monitoraggio dell’host", "The selected configuration needs to start the LXC during the installation": "La configurazione selezionata richiede l'avvio del container LXC durante l'installazione", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "Il container selezionato non è privilegiato. È richiesto un container privilegiato per il passthrough diretto del dispositivo.", @@ -6705,6 +6715,7 @@ "The service builds its own image; only a published image can be installed": "Il servizio crea la propria immagine; si possono installare solo immagini pubblicate", "The service declares no image:": "Il servizio non dichiara alcuna immagine:", "The setting has no final value:": "L'impostazione non ha un valore finale:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Non è stato possibile ripristinare tutte le impostazioni. Controlla il container e la modifica salvata prima di continuare.", "The share already exists in smb.conf:": "La condivisione esiste già in smb.conf:", "The shared destination is not a directory": "La destinazione condivisa non è una directory", "The shared directory points to a protected host path": "La directory condivisa punta a un percorso protetto dell'host", @@ -6779,6 +6790,7 @@ "These interface configurations will be removed": "Queste configurazioni dell'interfaccia verranno rimosse", "These manual changes cannot be adopted safely:": "Queste modifiche manuali non possono essere acquisite in modo sicuro:", "These paths will not be restored live and will be extracted for manual recovery.": "Questi percorsi non verranno ripristinati sul sistema in esecuzione e verranno estratti per il ripristino manuale.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Queste impostazioni richiedono un adattamento specifico per l’applicazione. L’immagine non è stata scaricata e il container non è stato ricreato.", "These values are asked during the installation:": "Questi valori verranno richiesti durante l’installazione:", "They are described in the image documentation:": "Sono descritti nella documentazione dell'immagine:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Se confermi e la verifica finale riesce, queste risorse verranno registrate in oci-compose prima di proseguire. Le risorse non supportate o modificate non vengono acquisite.", @@ -7114,6 +7126,7 @@ "Unsupported mount type": "Tipo mount non supportato", "Unsupported mount type:": "Tipo mount non supportato:", "Unsupported native device type:": "Tipo dispositivo nativo non supportato:", + "Unsupported network settings": "Impostazioni di rete non supportate", "Unsupported operation": "Operazione non supportata", "Unsupported or unknown Proxmox version; no repository changed.": "Versione Proxmox non supportata o sconosciuta; nessun repository è stato modificato.", "Unsupported output format:": "Formato output non supportato:", diff --git a/lang/pt.json b/lang/pt.json index 1c0a3622..8e0abe42 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Aplicando passagem de controlador/NVMe à VM", "Applying balanced memory optimization settings...": "Aplicando configurações de otimização de memória balanceadas...", "Applying changes safely...": "Aplicando alterações com segurança...", + "Applying container settings without rebuilding the container...": "A aplicar as definições sem recriar o contentor...", "Applying custom restore": "Aplicando restauração personalizada", "Applying default VM configuration": "Aplicando configuração de VM padrão", "Applying host permissions for unprivileged LXC bind-mounts...": "Aplicando permissões de host para montagens de ligação LXC sem privilégios...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Valor Allowed hosts inválido", "Invalid Home Assistant OS check timeout": "Tempo de verificação do sistema operacional Home Assistant inválido", "Invalid ID": "ID inválido", + "Invalid IPv4 address": "Endereço IPv4 inválido", + "Invalid IPv4 gateway": "Gateway IPv4 inválido", "Invalid Intel render path": "Caminho de renderização Intel inválido", "Invalid Jellyfin path:": "Caminho Jellyfin inválido:", "Invalid MAC address:": "Endereço MAC inválido:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "E- mail de administrador inválido", "Invalid administrator user name": "Nome de usuário do administrador inválido", "Invalid base VMID": "VMID base inválida", + "Invalid bridge": "Bridge inválida", "Invalid check package:": "Pacote de verificação inválido:", "Invalid configuration path:": "Caminho de configuração inválido:", "Invalid consume/export volumes": "Volumes de consumo/exportação inválidos", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Nome remoto inválido", "Invalid remote path": "Caminho remoto inválido", "Invalid repair version:": "Versão de reparação inválida:", + "Invalid resource value": "Valor de recurso inválido", "Invalid resources": "Recursos inválidos", "Invalid restored volume path": "Caminho de volume restaurado inválido", "Invalid running state": "Estado de execução inválido", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Membros inválidos da pilha", "Invalid stack name": "Nome da pilha inválido", "Invalid stack operation": "Pilha inválida operation", + "Invalid storage": "Armazenamento inválido", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "ID de armazenamento inválido. Use apenas letras, números, hífens e sublinhados.", "Invalid storage name": "Nome de armazenamento inválido", "Invalid suite application": "Aplicativo de suite inválido", @@ -4888,6 +4894,7 @@ "Proxmox VE configuration completed.": "Configuração do Proxmox VE concluída.", "Proxmox VE safe update completed": "Atualização segura do Proxmox VE concluída", "Proxmox auth logger service created and started": "Serviço de registrador de autenticação Proxmox criado e iniciado", + "Proxmox could not apply the container settings": "O Proxmox não conseguiu aplicar as definições do contentor", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "O repositório corporativo Proxmox pode estar ausente ou inacessível. Tentando mudar para sem assinatura...", "Proxmox filter configured": "Filtro Proxmox configurado", "Proxmox iSCSI Storage Status:": "Status de armazenamento iSCSI do Proxmox:", @@ -6327,6 +6334,7 @@ "The build could not be checked beforehand; continuing without that check.": "A compilação não pôde ser verificada antecipadamente;continuando sem essa verificação.", "The cached image does not match the current digest": "A imagem em cache não corresponde à digest atual", "The cached image is damaged; it will be downloaded again.": "A imagem em cache está danificada; será transferida novamente.", + "The changes need the application installer": "As alterações requerem o instalador da aplicação", "The character device does not exist:": "O dispositivo de caracteres não existe:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "O comando pede uma senha que não seja ecoada. Depois de criar os usuários, a interface web pede um nome de usuário e uma senha.", "The command does not name an image": "O comando não nomeia uma imagem", @@ -6446,6 +6454,7 @@ "The host bind source does not exist:": "O código de ligação da máquina não existe:", "The host bind source is not a regular file or directory:": "O código de ligação da máquina não é um ficheiro ou directório regular:", "The host directory changed before it was mounted": "A pasta da máquina mudou antes de ser montada", + "The host directory does not exist": "O diretório do anfitrião não existe", "The host directory may not be accessible from an unprivileged container.": "O diretório host pode não estar acessível a partir de um contêiner sem privilégios.", "The host has no IPv4 address on the selected bridge": "A máquina não tem endereço IPv4 na ponte seleccionada", "The host monitor does not see the real host memory": "O monitor da máquina não vê a memória verdadeira da máquina", @@ -6683,6 +6692,7 @@ "The selected VM": "A VM selecionada", "The selected VM is running.": "A VM selecionada está em execução.", "The selected base folder does not exist and could not be created:": "A pasta base selecionada não existe e não pôde ser criada:", + "The selected bridge does not exist": "A bridge selecionada não existe", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "A ponte selecionada não possui sub-rede IPv4 para o firewall do monitor host", "The selected configuration needs to start the LXC during the installation": "A configuração selecionada precisa iniciar o LXC durante a instalação", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "O contêiner selecionado não tem privilégios. Um contêiner privilegiado é necessário para passagem direta do dispositivo.", @@ -6705,6 +6715,7 @@ "The service builds its own image; only a published image can be installed": "O serviço constrói sua própria imagem; somente uma imagem publicada pode ser instalada", "The service declares no image:": "O serviço não declara imagem:", "The setting has no final value:": "A configuração não tem valor final:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Não foi possível repor todas as definições. Reveja o contentor e a alteração guardada antes de continuar.", "The share already exists in smb.conf:": "O compartilhamento já existe em smb.conf:", "The shared destination is not a directory": "O destino compartilhado não é um diretório", "The shared directory points to a protected host path": "A pasta partilhada aponta para uma localização protegida da máquina", @@ -6779,6 +6790,7 @@ "These interface configurations will be removed": "Essas configurações de interface serão removidas", "These manual changes cannot be adopted safely:": "Estas alterações manuais não podem ser adotadas com segurança:", "These paths will not be restored live and will be extracted for manual recovery.": "Esses caminhos não serão restaurados ao vivo e serão extraídos para recuperação manual.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Estas definições requerem uma adaptação específica da aplicação. A imagem não foi transferida e o contentor não foi recriado.", "These values are asked during the installation:": "Estes valores são questionados durante a instalação:", "They are described in the image documentation:": "Estão descritos na documentação da imagem:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Eles serão adicionados ao oci-compose antes de continuar. Recursos não suportados ou alterados não são importados.", @@ -7114,6 +7126,7 @@ "Unsupported mount type": "Tipo de montagem não suportado", "Unsupported mount type:": "Tipo de montagem não suportado:", "Unsupported native device type:": "Tipo de dispositivo nativo não suportado:", + "Unsupported network settings": "Definições de rede não suportadas", "Unsupported operation": "operation não suportado", "Unsupported or unknown Proxmox version; no repository changed.": "Versão Proxmox não suportada ou desconhecida; nenhum repositório foi alterado.", "Unsupported output format:": "Formato de saída não suportado:", diff --git a/lang/sk.json b/lang/sk.json index 5117ff23..3d6cd693 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Používam priame priradenie Controller/NVMe do VM", "Applying balanced memory optimization settings...": "Používam vyvážené nastavenia optimalizácie pamäte...", "Applying changes safely...": "Bezpečne používam zmeny...", + "Applying container settings without rebuilding the container...": "Aplikujú sa nastavenia bez opätovného vytvorenia kontajnera...", "Applying custom restore": "Používam vlastnú obnovu", "Applying default VM configuration": "Používam predvolené nastavenie VM", "Applying host permissions for unprivileged LXC bind-mounts...": "Nastavujem oprávnenia hosta pre LXC bind mounty v neprivilegovaných kontajneroch...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Neplatná hodnota UDELENÁ HOSTS", "Invalid Home Assistant OS check timeout": "Neplatný čas odhlásenia Home Assistant OS", "Invalid ID": "Neplatné ID", + "Invalid IPv4 address": "Neplatná adresa IPv4", + "Invalid IPv4 gateway": "Neplatná brána IPv4", "Invalid Intel render path": "Neplatná cesta k zobrazeniu informácií", "Invalid Jellyfin path:": "Neplatná cesta Jellyfin:", "Invalid MAC address:": "Neplatná adresa MAC:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "Neplatný administrátorský mail", "Invalid administrator user name": "Neplatné meno správcu", "Invalid base VMID": "Neplatná základňa VMID", + "Invalid bridge": "Neplatný sieťový most", "Invalid check package:": "Neplatný kontrolný balík:", "Invalid configuration path:": "Neplatná konfiguračná dráha:", "Invalid consume/export volumes": "Neplatné objemy spotreby/vývozu", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Neplatné vzdialené meno", "Invalid remote path": "Neplatná vzdialená cesta", "Invalid repair version:": "Neplatná verzia opravy:", + "Invalid resource value": "Neplatná hodnota prostriedkov", "Invalid resources": "Neplatné zdroje", "Invalid restored volume path": "Neplatná cesta obnoveného objemu", "Invalid running state": "Neplatný jazdný stav", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Neplatní členovia stohu", "Invalid stack name": "Neplatný názov stohu", "Invalid stack operation": "Neplatný zásobník operation", + "Invalid storage": "Neplatné úložisko", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "Neplatné ID úložiska. Použite iba písmená, čísla, pomlčky a podčiarkovníky.", "Invalid storage name": "Neplatný názov úložiska", "Invalid suite application": "Neplatná aplikácia", @@ -4890,6 +4896,7 @@ "Proxmox VE configuration completed.": "Nastavenie Proxmox VE je dokončené.", "Proxmox VE safe update completed": "Bezpečná aktualizácia Proxmox VE je dokončená", "Proxmox auth logger service created and started": "Služba na logovanie Proxmox prihlásení bola vytvorená a spustená", + "Proxmox could not apply the container settings": "Proxmox nedokázal použiť nastavenia kontajnera", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Proxmox enterprise repozitár môže chýbať alebo byť nedostupný. Skúšam prepnúť na no-subscription...", "Proxmox filter configured": "Proxmox filter nastavený", "Proxmox iSCSI Storage Status:": "Stav iSCSI úložiska v Proxmoxe:", @@ -6328,6 +6335,7 @@ "The build could not be checked beforehand; continuing without that check.": "Zostavenie nebolo možné vopred skontrolovať;pokračovanie bez tejto kontroly.", "The cached image does not match the current digest": "Comment", "The cached image is damaged; it will be downloaded again.": "Comment", + "The changes need the application installer": "Zmeny vyžadujú inštalačný program aplikácie", "The character device does not exist:": "Znakové zariadenie neexistuje:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "Príkaz vyžaduje heslo, ktoré nie je ozvene. Po vytvorení užívateľov, webové rozhranie žiada o užívateľské meno a heslo.", "The command does not name an image": "Príkaz nepomenuje obrázok", @@ -6447,6 +6455,7 @@ "The host bind source does not exist:": "Zdroj hostiteľskej väzby neexistuje:", "The host bind source is not a regular file or directory:": "Zdroj odkazu hostiteľa nie je bežný súbor alebo adresár:", "The host directory changed before it was mounted": "Adresár hostiteľa sa zmenil predtým, ako bol pripojený", + "The host directory does not exist": "Adresár na hostiteľovi neexistuje", "The host directory may not be accessible from an unprivileged container.": "Priečinok na hostovi nemusí byť dostupný z neprivilegovaného kontajnera.", "The host has no IPv4 address on the selected bridge": "Hostiteľ nemá IPv4 adresu na vybranom moste", "The host monitor does not see the real host memory": "Monitor hostiteľa nevidí skutočnú pamäť hostiteľa", @@ -6684,6 +6693,7 @@ "The selected VM": "Vybraná VM", "The selected VM is running.": "Vybraná VM beží.", "The selected base folder does not exist and could not be created:": "Vybraný základný priečinok neexistuje a nepodarilo sa ho vytvoriť:", + "The selected bridge does not exist": "Vybraný sieťový most neexistuje", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "Vybraný bridge nemá IPv4 podsieť pre firewall monitora hostiteľa", "The selected configuration needs to start the LXC during the installation": "Vybraná konfigurácia musí spustiť LXC počas inštalácie", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "Vybraný kontajner je neprivilegovaný. Na priame priradenie zariadenia je potrebný privilegovaný kontajner.", @@ -6706,6 +6716,7 @@ "The service builds its own image; only a published image can be installed": "Služba buduje svoj vlastný obraz; môže byť nainštalovaný iba zverejnený obrázok", "The service declares no image:": "Služba neprehlasuje žiadny obrázok:", "The setting has no final value:": "Nastavenie nemá žiadnu konečnú hodnotu:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Nepodarilo sa vrátiť všetky nastavenia. Pred pokračovaním skontrolujte kontajner a uloženú zmenu.", "The share already exists in smb.conf:": "Zdieľanie už existuje v smb.conf:", "The shared destination is not a directory": "Zdieľaná destinácia nie je adresár", "The shared directory points to a protected host path": "Zdieľaný adresár ukazuje na chránenú cestu hostiteľa", @@ -6780,6 +6791,7 @@ "These interface configurations will be removed": "Tieto nastavenia rozhraní sa odstránia", "These manual changes cannot be adopted safely:": "Tieto manuálne zmeny nie je možné bezpečne prijať:", "These paths will not be restored live and will be extracted for manual recovery.": "Tieto cesty sa neobnovia za behu a rozbalia sa na ručnú obnovu.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Tieto nastavenia vyžadujú úpravu špecifickú pre aplikáciu. Obraz sa nestiahol a kontajner sa nevytvoril znova.", "These values are asked during the installation:": "Tieto hodnoty sa požadujú počas inštalácie:", "They are described in the image documentation:": "Ich popis nájdete v dokumentácii image:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Pred pokračovaním budú pridané do oci-compose. Nepodporované alebo zmenené zdroje sa neimportujú.", @@ -7115,6 +7127,7 @@ "Unsupported mount type": "Nepodporovaný typ pripojenia", "Unsupported mount type:": "Nepodporovaný typ pripojenia:", "Unsupported native device type:": "Nepodporovaný natívne typ zariadenia:", + "Unsupported network settings": "Nepodporované sieťové nastavenia", "Unsupported operation": "Nepodporovaná operation", "Unsupported or unknown Proxmox version; no repository changed.": "Nepodporovaná alebo neznáma verzia Proxmox; žiadne úložisko sa nezmenilo.", "Unsupported output format:": "Nepodporovaný výstupný formát:", diff --git a/lang/sv.json b/lang/sv.json index 45d309ff..51c60d2c 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -448,6 +448,7 @@ "Applying Controller/NVMe passthrough to VM": "Tillämpar styrenhets-/NVMe-passthrough på VM", "Applying balanced memory optimization settings...": "Tillämpar inställningar för balanserad minnesoptimering...", "Applying changes safely...": "Tillämpar ändringar på ett säkert sätt...", + "Applying container settings without rebuilding the container...": "Tillämpar inställningarna utan att återskapa containern...", "Applying custom restore": "Använder anpassad återställning", "Applying default VM configuration": "Tillämpar standard VM-konfiguration", "Applying host permissions for unprivileged LXC bind-mounts...": "Tillämpar värdbehörigheter för oprivilegierade LXC bind-mounts...", @@ -3214,6 +3215,8 @@ "Invalid ALLOWED_HOSTS value": "Invalid ALLOWED_HOSTS värde", "Invalid Home Assistant OS check timeout": "Ogiltig Home Assistant OS check timeout", "Invalid ID": "Ogiltigt ID", + "Invalid IPv4 address": "Ogiltig IPv4-adress", + "Invalid IPv4 gateway": "Ogiltig IPv4-gateway", "Invalid Intel render path": "Invalid Intel render bana", "Invalid Jellyfin path:": "Invalid Jellyfin väg:", "Invalid MAC address:": "Ogiltig MAC-adress:", @@ -3246,6 +3249,7 @@ "Invalid administrator email": "Invalid administratör e-post", "Invalid administrator user name": "Invalid administratör användarnamn", "Invalid base VMID": "Ogiltig bas VMID", + "Invalid bridge": "Ogiltig brygga", "Invalid check package:": "Ogiltigt checkpaket:", "Invalid configuration path:": "Ogiltig konfigurationsväg:", "Invalid consume/export volumes": "Ogiltiga konsum/exportvolymer", @@ -3309,6 +3313,7 @@ "Invalid remote name": "Ogiltigt fjärrnamn", "Invalid remote path": "Invalid fjärrväg", "Invalid repair version:": "Ogiltig reparationsversion:", + "Invalid resource value": "Ogiltigt resursvärde", "Invalid resources": "Ogiltiga resurser", "Invalid restored volume path": "Invalid återställd volymväg", "Invalid running state": "Ogiltig körstatus", @@ -3327,6 +3332,7 @@ "Invalid stack members": "Ogiltiga stackmedlemmar", "Invalid stack name": "Invalid stack namn", "Invalid stack operation": "Ogiltig stack operation", + "Invalid storage": "Ogiltig lagring", "Invalid storage ID. Use only letters, numbers, hyphens and underscores.": "Ogiltigt lagrings-ID. Använd endast bokstäver, siffror, bindestreck och understreck.", "Invalid storage name": "Ogiltigt lagringsnamn", "Invalid suite application": "Invalid suite applikation", @@ -4888,6 +4894,7 @@ "Proxmox VE configuration completed.": "Proxmox VE-konfigurationen slutförd.", "Proxmox VE safe update completed": "Proxmox VE säker uppdatering slutförd", "Proxmox auth logger service created and started": "Proxmox authlogger-tjänst skapad och startade", + "Proxmox could not apply the container settings": "Proxmox kunde inte tillämpa containerns inställningar", "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription...": "Proxmox företagsrepo kan saknas eller är otillgänglig. Försöker byta till ingen prenumeration...", "Proxmox filter configured": "Proxmox-filter konfigurerat", "Proxmox iSCSI Storage Status:": "Proxmox iSCSI-lagringsstatus:", @@ -6327,6 +6334,7 @@ "The build could not be checked beforehand; continuing without that check.": "Bygget kunde inte kontrolleras i förväg;fortsätter utan den kontrollen.", "The cached image does not match the current digest": "Den cachade bilden matchar inte den nuvarande smältningen", "The cached image is damaged; it will be downloaded again.": "Den cachade bilden är skadad; den kommer att laddas ner igen.", + "The changes need the application installer": "Ändringarna kräver programmets installationsverktyg", "The character device does not exist:": "Karaktärenheten existerar inte:", "The command asks for a password that is not echoed. After creating the users, the web interface asks for a user name and a password.": "Kommandot begär ett lösenord som inte echoed. Efter att ha skapat användarna ber webbgränssnittet om ett användarnamn och ett lösenord.", "The command does not name an image": "Kommandot heter inte en bild", @@ -6446,6 +6454,7 @@ "The host bind source does not exist:": "Värdbindningskällan finns inte:", "The host bind source is not a regular file or directory:": "Värdbindningskällan är inte en vanlig fil eller katalog:", "The host directory changed before it was mounted": "Värdkatalogen ändrades innan den monterades", + "The host directory does not exist": "Katalogen på värden finns inte", "The host directory may not be accessible from an unprivileged container.": "Värdkatalogen kanske inte är tillgänglig från en oprivilegierad behållare.", "The host has no IPv4 address on the selected bridge": "Värden har ingen IPv4-adress på den valda bron", "The host monitor does not see the real host memory": "Värdskärmen ser inte det verkliga värdminnet", @@ -6683,6 +6692,7 @@ "The selected VM": "Den valda virtuella datorn", "The selected VM is running.": "Den valda virtuella datorn körs.", "The selected base folder does not exist and could not be created:": "Den valda basmappen finns inte och kunde inte skapas:", + "The selected bridge does not exist": "Den valda bryggan finns inte", "The selected bridge has no IPv4 subnet for the host-monitor firewall": "Den valda bryggan har inget IPv4-undernät för värd-monitorns brandvägg", "The selected configuration needs to start the LXC during the installation": "Den valda konfigurationen måste starta LXC under installationen", "The selected container is unprivileged. A privileged container is required for direct device passthrough.": "Den valda behållaren är oprivilegierad. En privilegierad behållare krävs för direkt enhetsgenomföring.", @@ -6705,6 +6715,7 @@ "The service builds its own image; only a published image can be installed": "Tjänsten bygger sin egen bild; endast en publicerad bild kan installeras.", "The service declares no image:": "Tjänsten förklarar ingen bild:", "The setting has no final value:": "Inställningen har inget slutvärde:", + "The settings could not be fully rolled back. Review the container and its saved modification before continuing.": "Det gick inte att återställa alla inställningar. Kontrollera containern och den sparade ändringen innan du fortsätter.", "The share already exists in smb.conf:": "Delningen finns redan i smb.conf:", "The shared destination is not a directory": "Den delade destinationen är inte en katalog", "The shared directory points to a protected host path": "Den delade katalogen pekar på en skyddad värdväg", @@ -6779,6 +6790,7 @@ "These interface configurations will be removed": "Dessa gränssnittskonfigurationer kommer att tas bort", "These manual changes cannot be adopted safely:": "Dessa manuella ändringar kan inte användas på ett säkert sätt:", "These paths will not be restored live and will be extracted for manual recovery.": "Dessa sökvägar kommer inte att återställas live och kommer att extraheras för manuell återställning.", + "These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.": "Dessa inställningar kräver en anpassning för just det här programmet. Avbildningen hämtades inte och containern återskapades inte.", "These values are asked during the installation:": "Dessa värden tillfrågas under installationen:", "They are described in the image documentation:": "De beskrivs i bilddokumentationen:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "De kommer att läggas till oci-compose innan du fortsätter. Resurser som inte stöds eller ändras importeras inte.", @@ -7114,6 +7126,7 @@ "Unsupported mount type": "Unsupported Mount Typ", "Unsupported mount type:": "Unsupported Mount Typ:", "Unsupported native device type:": "Inbyggd enhetstyp som inte stöds:", + "Unsupported network settings": "Nätverksinställningarna stöds inte", "Unsupported operation": "Unsupported operation", "Unsupported or unknown Proxmox version; no repository changed.": "Ostödd eller okänd version av Proxmox; inget arkiv har ändrats.", "Unsupported output format:": "Utdataformat som inte stöds:", diff --git a/oci/README.md b/oci/README.md index bde5cfb3..27ea49b3 100644 --- a/oci/README.md +++ b/oci/README.md @@ -142,7 +142,7 @@ An application made of several images is one entry of the catalog and one instal | Operation | What changes | What is kept | |---|---|---| | **Update** | The rootfs is replaced by the image the saved tag publishes today | Record, container disks, host directories, network and devices | -| **Modify** | An editor opens with the current resources, network, paths and GPU; the container is rebuilt with the changes | The data of container disks and host directories | +| **Modify** | Choose a setting, review it, then apply native LXC changes to the existing container; runtime settings may require a restart | Installed image, rootfs, persistent data and unrelated Proxmox settings | | **Recreate** (multi-container) | Every container is rebuilt from the image it was installed with | Data, network and devices | | **Watchdog** | Whether the application is started again when it stops on its own | Everything; only the choice is saved | | **Remove** | The LXC, or every container of the application, its disks and its record | Host directories, with their content | @@ -159,6 +159,21 @@ Settings changed in Proxmox VE after the installation, such as memory, cores or A rolling tag is not an unattended update: the digest changes only through an explicit update. +For standalone applications, CPU and RAM changes apply live. Network settings, +environment overrides, directory mounts, additional managed volumes and supported +native devices use a controlled stop/start when needed. Modify does not query the +registry or download an image for these edits, even if the cached archive has +already been cleaned. Removing a managed mount detaches its disk without deleting +its data. An optional retained backup does not force recreation. + +The native path checks the saved configuration and installation identity, keeps a +private before/after journal, and restores the original settings if applying them +or checking the restarted service fails. It refuses to overwrite concurrent edits. +Application-specific installer adaptations (such as changing an NVIDIA runtime or +post-start configuration) are not silently converted into a full recreation: +unsupported same-image changes stop with an explanation before modifying the CT. +Image changes and coordinated stack operations retain their separate update flow. + ## From a Compose file or a docker run command to an oci-compose The converter reads a Compose file with one service. A `docker run` command and an image reference alone are first written as a Compose file and follow the same path, so the three produce the same kind of oci-compose. diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index 59579967..25f9bf6c 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -453,7 +453,7 @@ def external_changes(record, config, adopt=True): return values -def preflight(record, candidate, config, coordinated=None): +def preflight(record, candidate, config, coordinated=None, in_place=False, baseline=True): deployment = record['deployment'] desired = candidate['deployment'] if coordinated and (record.get('native_stack_intent') or deployment.get('rootfs_adaptation_replay_required')): @@ -462,7 +462,7 @@ def preflight(record, candidate, config, coordinated=None): 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')) - for key, value in external_changes(record, config, adopt=not coordinated).items(): + 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 target = desired.setdefault(section, {}) if section else desired @@ -491,7 +491,8 @@ def preflight(record, candidate, config, coordinated=None): runtime_keys.add('lxc.mount.entry') runtime_settings.check(config, deployment, record['vmid']) coordinated_keys = {'hookscript', *KEPT_AS_IS} if coordinated else set(KEPT_AS_IS) - if '[' in config.decode() or keys - BASIC - runtime_keys - coordinated_keys - {k for k in keys if re.fullmatch(r'(mp|dev)[0-9]+', k)}: + preserved_keys = {'nameserver', 'searchdomain', 'timezone', 'protection'} if in_place else set() + if '[' in config.decode() or keys - BASIC - runtime_keys - coordinated_keys - preserved_keys - {k for k in keys if re.fullmatch(r'(mp|dev)[0-9]+', k) or (in_place and re.fullmatch(r'unused[0-9]+', k))}: raise ValueError(translate('The container has advanced Proxmox settings outside the supported profile')) unprivileged = deployment.get('security', {}).get('unprivileged', True) is not False if (desired.get('security', {}).get('unprivileged', True) is not False) != unprivileged \ @@ -532,7 +533,8 @@ def preflight(record, candidate, config, coordinated=None): raise ValueError(translate('Changing the rootfs or its storage requires a separate migration')) actual = mounts(config) gpu_devices.check(config, deployment) - gpu_devices.verify_baseline(record['observed'].get('gpu_devices', {}), deployment) + if baseline: + gpu_devices.verify_baseline(record['observed'].get('gpu_devices', {}), deployment) recorded_files = {m['source']: bool(m.get('read_only')) for m in deployment.get('mounts', []) if is_file_bind(m, files)} if files != recorded_files: raise ValueError(translate('A container mount has a source, backup or permission different from the saved record')) @@ -1273,6 +1275,9 @@ def complete_recovery(root, journal, state): def recover(root, journal): state = json.loads(journal.read_text()) + if state.get('kind') == 'native-modify': + import oci_native_modify + return oci_native_modify.recover(root, journal) vmid = state['vmid'] show = not state.get('coordinated') if show: diff --git a/oci/remote/oci_native_modify.py b/oci/remote/oci_native_modify.py new file mode 100644 index 00000000..8538995c --- /dev/null +++ b/oci/remote/oci_native_modify.py @@ -0,0 +1,396 @@ +"""Edit an installed application's LXC configuration, retaining its rootfs/data. + +Runtime changes use a controlled stop/start, not pct destroy/create. Values, +including secrets and NUL-separated environment, reach PVE over stdin only. +""" +import copy +import hashlib +import ipaddress +import json +from pathlib import Path +import re +import subprocess +import uuid + +import oci_instances as instances +import oci_instance_transaction as transaction +import oci_resource_modify as resources +import oci_gpu_devices as devices +import oci_host_mounts as host_mounts +from oci_installation_state import parse_config, sha +from oci_ui import translate, msg_info, msg_ok, msg_warn + +PERL_UPDATE = r''' +use strict; use warnings; use JSON::PP; +use PVE::RPCEnvironment; use PVE::INotify; use PVE::API2::LXC::Config; +PVE::RPCEnvironment->setup_default_cli_env(); +my $p = decode_json(do { local $/; }); +$p->{node} = PVE::INotify::nodename(); +PVE::API2::LXC::Config->update_vm($p); +''' + + +def environment(config): + result = {} + entries = [] + for line in config.decode().splitlines(): + if line.startswith('lxc.environment.runtime: '): + entries.append(line.split(': ', 1)[1]) + elif line.startswith('env: '): + # pct config exposes PVE's public env property, whereas older + # observations and native files contain repeated runtime lines. + entries.extend(line.split(': ', 1)[1].split('\0')) + for entry in entries: + name, sep, value = entry.partition('=') + if not sep or name in result: + raise ValueError(translate('Duplicate variable in the contract; review it before editing')) + result[name] = value + return result + + +def environment_values(items): + result = {} + for item in items: + name, value = item['name'], str(item['value']) + if (not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name) or name in result + or any(ord(c) < 32 or ord(c) == 127 for c in value)): + raise ValueError(translate('The value contains an unsupported character')) + result[name] = value + return result + + +def changes(record, candidate, config): + """Only unchanged recipes and native settings; adapters keep their installer.""" + old = transaction.with_default_healthcheck(copy.deepcopy(record)) + candidate = transaction.with_default_healthcheck(copy.deepcopy(candidate)) + if (old['template'] != candidate['template'] or old['deployment'].get('host_monitor') + or any(k in record for k in ('stack', 'stack_member', 'native_stack_intent')) + or old['deployment'].get('stack_managed')): + return None + before, after = old['deployment'], candidate['deployment'] + changed = {k for k in before.keys() | after.keys() if before.get(k) != after.get(k)} + allowed = {'resources', 'network', 'environment', 'devices', 'mounts', 'hardware_profile'} + if not changed or changed - allowed: + return None + resource_keys = {k for k in before.get('resources', {}).keys() | after.get('resources', {}).keys() + if before.get('resources', {}).get(k) != after.get('resources', {}).get(k)} + if resource_keys - {'cores', 'memory_mb', 'swap_mb', 'cpu_units'}: + return None + if 'devices' in changed: + if any(d.get('kind', 'character-device') not in ('character-device', 'block-device') + or d.get('append_host_device_gid_to_environment') + for plan in (before, after) for d in plan.get('devices', [])): + return None + if before.get('device_permissions') or after.get('device_permissions'): + return None + if 'mounts' in changed: + if any(transaction.is_file_bind(m) for plan in (before, after) for m in plan.get('mounts', [])): + return None + return changed + + +def plan(record, candidate, config): + changed = changes(record, candidate, config) + if not changed: + return None + transaction.preflight(record, candidate, config, in_place=True) + old, new = record['deployment'], candidate['deployment'] + cfg = parse_config(config) + values, delete = {}, [] + if 'resources' in changed: + for key, native, minimum in (('cores', 'cpulimit' if 'cpulimit' in cfg and 'cores' not in cfg else 'cores', 1), + ('memory_mb', 'memory', 128), ('swap_mb', 'swap', 0), ('cpu_units', 'cpuunits', 1)): + value = new['resources'].get(key) + if old['resources'].get(key) == value: + continue + if value is None and key == 'cpu_units': + if native in cfg: delete.append(native) + elif isinstance(value, bool) or not isinstance(value, int) or value < minimum: + raise ValueError(translate('Invalid resource value')) + else: + values[native] = str(value) + if 'network' in changed: + network = new['network'] + if set(network) - {'bridge', 'ipv4', 'gateway', 'firewall', 'host_managed', 'mac_address'}: + raise ValueError(translate('Unsupported network settings')) + if not re.fullmatch(r'[A-Za-z0-9_.:-]+', network['bridge']): + raise ValueError(translate('Invalid bridge')) + if not Path('/sys/class/net', network['bridge'], 'bridge').is_dir(): + raise ValueError(translate('The selected bridge does not exist')) + address = network['ipv4'] + if address not in ('dhcp', 'manual'): + if ipaddress.ip_interface(address).version != 4: + raise ValueError(translate('Invalid IPv4 address')) + gateway = network.get('gateway') + if gateway and ipaddress.ip_address(gateway).version != 4: + raise ValueError(translate('Invalid IPv4 gateway')) + fields = dict(p.split('=', 1) for p in cfg['net0'].split(',') if '=' in p) + fields.update(bridge=network['bridge'], ip=address) + if gateway: fields['gw'] = gateway + else: fields.pop('gw', None) + for key, native in (('firewall', 'firewall'), ('host_managed', 'host-managed')): + if old['network'].get(key) != network.get(key): + fields[native] = '1' if network.get(key) else '0' + if old['network'].get('mac_address') != network.get('mac_address'): + raise ValueError(translate('The MAC address of the container cannot be kept')) + values['net0'] = ','.join(k+'='+v for k, v in fields.items()) + if 'environment' in changed: + env = environment(config) + previous = environment_values(old.get('environment', [])) + wanted = environment_values(new.get('environment', [])) + defaults = dict(item.split('=', 1) for item in record['observed']['image'].get('defaults', {}).get('Env', []) if '=' in item) + for name in previous.keys() - wanted.keys(): + if name in defaults: env[name] = defaults[name] + else: env.pop(name, None) + env.update(wanted) + if env: values['env'] = '\0'.join(k+'='+v for k, v in env.items()) + else: delete.append('env') + if 'devices' in changed: + inventory = devices.planned(new) + existing = {v['path']: k for k, v in ((k, dict(p.split('=', 1) for p in v.split(','))) + for k, v in cfg.items() if re.fullmatch(r'dev[0-9]+', k))} + used = set(existing.values()) + for path, key in existing.items(): + if path not in inventory: delete.append(key) + for device in new.get('devices', []): + path = device['host_path'] + key = existing.get(path) + if not key: + key = next('dev'+str(n) for n in range(256) if 'dev'+str(n) not in used) + used.add(key) + info = inventory[path] + mode = info['mode'] if device.get('mode') == 'preserve-host' else int(device.get('mode', '0660'), 8) + values[key] = (f"path={path},mode={mode:04o},uid={int(device.get('uid', 0))}," + f"gid={info['gid'] if device['gid_strategy'] == 'host-device-gid' else 0}," + f"deny-write={1 if device.get('deny_write') else 0}") + if 'mounts' in changed: + actual = transaction.mounts(config) + used = {m['key'] for m in actual.values()} + wanted = {m['container_path']: m for m in new.get('mounts', [])} + for target, mount in actual.items(): + if target not in wanted: delete.append(mount['key']) + for target, mount in wanted.items(): + if target in actual: + original = next(m for m in old['mounts'] if m['container_path'] == target) + if mount == original: continue + fields = dict(p.split('=', 1) for p in actual[target]['value'].split(',')[1:] if '=' in p) + fields['ro'] = '1' if mount.get('read_only') else '0' + values[actual[target]['key']] = actual[target]['volume']+','+','.join(k+'='+v for k, v in fields.items()) + continue + key = next('mp'+str(n) for n in range(256) if 'mp'+str(n) not in used) + used.add(key) + source = mount['source'] + if mount['type'] == 'managed-volume': + if not re.fullmatch(r'[A-Za-z0-9_-]+', source): + raise ValueError(translate('Invalid storage')) + source += ':'+str(mount['size_gb']) + else: + source = host_mounts.valid_path(source) + if not Path(source).is_dir(): + if not mount.get('create_if_missing'): + raise ValueError(translate('The host directory does not exist')) + values[key] = (f"{source},mp={target},backup={1 if mount['backup'] else 0}," + f"ro={1 if mount.get('read_only') else 0}") + return {'values': values, 'delete': delete, 'restart': bool(changed - {'resources'}), + 'changed': sorted(changed)} + + +def verify_values(config, operation): + """PVE may canonicalize property strings; compare their actual fields.""" + cfg = parse_config(config) + for key, value in operation['values'].items(): + if key == 'env': + if environment(config) != dict(item.split('=', 1) for item in value.split('\0')): + raise ValueError(translate('Proxmox could not apply the container settings')) + elif re.fullmatch(r'(mp|dev|net)[0-9]+', key): + desired = dict(p.split('=', 1) for p in value.split(',') if '=' in p) + actual = dict(p.split('=', 1) for p in cfg.get(key, '').split(',') if '=' in p) + defaults = {'uid': '0', 'gid': '0', 'deny-write': '0', 'ro': '0', 'backup': '0', 'mode': '0660', 'firewall': '0'} + def matches(k, v): + observed = actual.get(k, defaults.get(k)) + if k == 'mode': return observed is not None and int(observed, 8) == int(v, 8) + return observed == v + if any(not matches(k, v) for k, v in desired.items()): + raise ValueError(translate('Proxmox could not apply the container settings')) + elif cfg.get(key) != value: + raise ValueError(translate('Proxmox could not apply the container settings')) + if any(key in cfg or (key == 'env' and environment(config)) for key in operation['delete']): + raise ValueError(translate('Proxmox could not apply the container settings')) + + +def set_config(vmid, installation_id, expected, values, delete=()): + raw = (resources.CONFIG_DIR / f'{vmid}.conf').read_bytes() + if instances.command('pct', 'config', str(vmid)) != expected or instances.identity(expected) != installation_id: + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + payload = dict(values, vmid=vmid, digest=hashlib.sha1(raw).hexdigest()) + if delete: payload['delete'] = ','.join(delete) + transaction.log('Applying native settings: '+', '.join(sorted(set(values) | set(delete)))) + process = subprocess.run(['perl', '-e', PERL_UPDATE], input=json.dumps(payload).encode(), + capture_output=True, timeout=120) + # API errors may include secret values. Only keep the exit status. + if process.returncode: + raise RuntimeError(translate('Proxmox could not apply the container settings')) + + +def untouched(before, after, keys): + keys = set(keys) + if 'env' in keys: keys.add('lxc.environment.runtime') + def keep(config): + return b''.join(line for line in config.splitlines(keepends=True) + if line.split(b':', 1)[0].decode() not in keys + and not re.match(rb'unused[0-9]+:', line)) + return keep(before) == keep(after) + + +def restore_settings(root, record, before, now, operation): + vmid = record['vmid'] + keys = set(operation['values']) | set(operation['delete']) + if operation['restart']: transaction.stop(vmid) + original = parse_config(before) + restore = {k: original[k] for k in keys if k in original and k != 'env'} + remove = [k for k in keys if k not in original and k != 'env' and k in parse_config(now)] + if 'env' in keys: + env = environment(before) + if env: restore['env'] = '\0'.join(k+'='+v for k, v in env.items()) + elif environment(now): remove.append('env') + if restore or remove: + set_config(vmid, record['installation_id'], now, restore, remove) + restored_config = instances.command('pct', 'config', str(vmid)) + verify_values(restored_config, {'values': restore, 'delete': remove}) + if not untouched(before, restored_config, keys): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + restored = copy.deepcopy(record) + obs = record['observed'] + restored['observed'] = instances.observe(vmid, record['installation_id'], + obs['archive_path'], obs['resolved_registry_digest'], obs['image']) + if restored['observed']['config_sha256'] != sha(restored_config): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + return restored + + +def recover(root, journal): + """Recover through the normal Manage interrupted-operation menu.""" + state = json.loads(journal.read_text()) + record, operation = state['record'], state['operation_plan'] + vmid = record['vmid'] + current = instances.read(root, vmid) + if (state['kind'] != 'native-modify' or state['phase'] in ('committed', 'rolled-back') + or current.get('pending_transaction') != str(journal) + or current.get('transaction_id') != state['id'] + or current['installation_id'] != record['installation_id']): + raise ValueError(translate('The record does not belong to this operation')) + before = state['config'].encode() + now = instances.command('pct', 'config', str(vmid)) + keys = set(operation['values']) | set(operation['delete']) + if instances.identity(now) != record['installation_id'] or not untouched(before, now, keys): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + if state.get('after_config'): + if now not in (before, state['after_config'].encode()): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + elif now != before: + # A crash during the PVE API call is recoverable only when the full + # planned edit is present. Refuse unknown partial/concurrent changes. + verify_values(now, operation) + host_mounts.verify_sources(state.get('original_host_sources', {})) + tx_record = restore_settings(root, record, before, now, operation) if now != before else copy.deepcopy(record) + if state['was_running']: + if transaction.run('pct', 'status', str(vmid)).strip() != b'status: running': + transaction.run('pct', 'start', str(vmid)) + transaction.healthcheck(vmid, record['template']) + instances.write(instances.location(root, vmid), tx_record) + state['phase'] = 'rolled-back' + instances.write(journal, state) + msg_ok(translate('Previous installation restored')) + + +def apply(root, record, candidate, config, acknowledge_external_data=False): + operation = plan(record, candidate, config) + if not operation: + raise ValueError(translate('The changes need the application installer')) + original_sources, desired_sources = transaction.freeze_host_sources( + record, candidate, acknowledge_external_data) + vmid = record['vmid'] + folder = instances.location(root, vmid).parent / 'modifications' / uuid.uuid4().hex + running = transaction.run('pct', 'status', str(vmid)).strip() == b'status: running' + journal = {'kind': 'native-modify', 'id': folder.name, 'vmid': vmid, 'operation': 'recreate', + 'record': record, 'config': config.decode(), 'operation_plan': operation, + 'phase': 'prepared', 'was_running': running, 'original_host_sources': original_sources} + instances.write(folder / 'before.json', journal) + transaction.open_log(folder) + transaction._run['journal'] = folder / 'before.json' + pending = copy.deepcopy(record) + pending.update(status='updating', transaction_id=folder.name, pending_transaction=str(folder / 'before.json')) + instances.write(instances.location(root, vmid), pending) + updated_config = None + changed = False + try: + if operation['restart'] and running: + transaction.stop(vmid) + # Do not accept a concurrent edit merely because the service stopped. + if instances.command('pct', 'config', str(vmid)) != config: + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + host_mounts.verify_sources(desired_sources) + for mount in candidate['deployment'].get('mounts', []): + if mount['type'] == 'host-bind' and mount.get('create_if_missing'): + Path(mount['source']).mkdir(parents=True, exist_ok=True) + host_mounts.verify_sources(original_sources) + # Capture newly-created directories before installing their mounts. + pinned = {p: host_mounts.validate_source(p) for p in desired_sources} + for path, previous in desired_sources.items(): + if previous['exists'] and not host_mounts.same_source(previous, pinned[path]): + raise ValueError(translate('A shared source does not match its recorded identity')) + host_mounts.verify_sources(pinned) + msg_info(translate('Applying container settings without rebuilding the container...')) + if operation['values'] or operation['delete']: + changed = True # API updates may partially succeed before an error. + set_config(vmid, record['installation_id'], config, operation['values'], operation['delete']) + updated_config = instances.command('pct', 'config', str(vmid)) + verify_values(updated_config, operation) + keys = set(operation['values']) | set(operation['delete']) + if not untouched(config, updated_config, keys): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + transaction.preflight(candidate, candidate, updated_config, in_place=True, baseline=False) + journal.update(phase='configured', after_config=updated_config.decode()) + instances.write(folder / 'before.json', journal) + if operation['restart'] and running: + transaction.run('pct', 'start', str(vmid)) + transaction.healthcheck(vmid, candidate['template']) + observed = record['observed'] + fresh = instances.observe(vmid, record['installation_id'], observed['archive_path'], + observed['resolved_registry_digest'], observed['image']) + host_mounts.verify_observation(pinned, fresh) + if fresh['config_sha256'] != sha(updated_config): + raise ValueError(translate('The instance changed while it was being edited; open Modify again')) + result = copy.deepcopy(record) + result.update(deployment=copy.deepcopy(candidate['deployment']), observed=fresh) + instances.write(instances.location(root, vmid), result) + journal['phase'] = 'committed' + instances.write(folder / 'before.json', journal) + except BaseException: + try: + now = instances.command('pct', 'config', str(vmid)) + keys = set(operation['values']) | set(operation['delete']) + if changed: + if not untouched(config, now, keys) or (updated_config is not None and now != updated_config): + raise ValueError('Concurrent edit; rollback refused') + restored = restore_settings(root, record, config, now, operation) + else: + restored = copy.deepcopy(record) + if operation['restart'] and running: + if transaction.run('pct', 'status', str(vmid)).strip() != b'status: running': + transaction.run('pct', 'start', str(vmid)) + transaction.healthcheck(vmid, record['template']) + instances.write(instances.location(root, vmid), restored) + journal['phase'] = 'rolled-back' + instances.write(folder / 'before.json', journal) + except Exception as error: + transaction.log('native rollback failed: '+type(error).__name__) + msg_warn(translate('The settings could not be fully rolled back. Review the container and its saved modification before continuing.')) + raise + try: + import oci_carried_record + oci_carried_record.carry(root, vmid, mount_stopped=False) + except (OSError, ValueError, RuntimeError): + transaction.log('carried record will be refreshed on the next management entry') + msg_ok(translate('Changes applied. Data kept.')) + return folder diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index b5101fc9..ca2bb142 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -180,6 +180,16 @@ def application_name(record, vmid): return f'CT {vmid}' +def backup_native_settings(vmid, storage): + """An optional retained backup does not require rebuilding the guest.""" + if storage: + import oci_keep_backup + oci_keep_backup.validate(storage) + transaction.run('vzdump', str(vmid), '--storage', storage, '--mode', 'snapshot', + '--compress', 'zstd', '--notes-template', + 'ProxMenux OCI: before modifying container settings') + + def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=None): operation = 'recreate' if proposal is not None else 'update' msg_info(translate('Checking the container before the update...') if operation == 'update' @@ -192,7 +202,7 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non desired = transaction.candidate_contract(record, operation, proposal) changes = transaction.external_changes(record, config) resource_changes = None - if operation == 'recreate' and not keep_backup and not changes: + if operation == 'recreate' and not changes: import oci_resource_modify resource_changes = oci_resource_modify.changes(record, desired, config) # CPU/RAM edits retain the installed image and all unrelated native @@ -202,7 +212,22 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non msg_ok(translate('Container checked')) import oci_operation_notice with oci_operation_notice.operation([vmid], 'modify', application_name(record, vmid)): + backup_native_settings(vmid, keep_backup) return oci_resource_modify.apply(instances.ROOT, record, desired, config, resource_changes) + if operation == 'recreate' and not changes: + import oci_native_modify + if oci_native_modify.changes(record, desired, config): + import oci_operation_notice + with oci_operation_notice.operation([vmid], 'modify', application_name(record, vmid)): + # Validate the native plan before an optional full backup. + oci_native_modify.plan(record, desired, config) + backup_native_settings(vmid, keep_backup) + return oci_native_modify.apply(instances.ROOT, record, desired, config, + acknowledge_external_data=acknowledge_external_data) + if (operation == 'recreate' + and record['template']['container_contract']['image']['reference'] + == desired['template']['container_contract']['image']['reference']): + raise ValueError(translate('These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.')) transaction.preflight(record, desired, config) msg_ok(translate('Container checked')) current = record['observed']['image'] if operation == 'update' else None diff --git a/oci/src/proxmenux_oci/installer.py b/oci/src/proxmenux_oci/installer.py index a10f92c5..f8e88d43 100644 --- a/oci/src/proxmenux_oci/installer.py +++ b/oci/src/proxmenux_oci/installer.py @@ -1588,7 +1588,7 @@ def _run_remote_install( shutil.copy2(project_root / 'remote' / 'haos_healthcheck.py', temporary / 'haos_healthcheck.py') shutil.copy2(project_root / 'remote' / 'oci_installation_state.py', temporary / 'oci_installation_state.py') shutil.copy2(project_root / 'remote' / 'oci_instances.py', temporary / 'oci_instances.py') - for helper in ('oci_ui.sh', 'oci_ui.py', 'oci_description.py', 'oci_console.py', 'oci_native_stack.py', 'oci_native_stack.sh', 'oci_instance_transaction.py', 'oci_host_mounts.py', 'oci_nested_mounts.py', 'oci_runtime_settings.py', 'oci_gpu_devices.py', 'oci_accelerators.py', 'oci_nvidia_runtime.py', 'oci_nvidia_refresh.py', 'oci_nvidia_dynamic.py', 'oci_update_current.py', 'oci_stack_replay.py', 'oci_stack_plan.py', 'oci_stack_transaction.py', 'oci_stack_native.py', 'oci_image_cache.py', 'nvidia_lxc_mount_lab.sh', 'oci_nvidia_setup.sh', 'oci_immich_ml.sh', 'oci_rocm_check.py'): + for helper in ('oci_ui.sh', 'oci_ui.py', 'oci_description.py', 'oci_console.py', 'oci_native_stack.py', 'oci_native_stack.sh', 'oci_instance_transaction.py', 'oci_host_mounts.py', 'oci_nested_mounts.py', 'oci_runtime_settings.py', 'oci_gpu_devices.py', 'oci_accelerators.py', 'oci_nvidia_runtime.py', 'oci_nvidia_refresh.py', 'oci_nvidia_dynamic.py', 'oci_update_current.py', 'oci_resource_modify.py', 'oci_native_modify.py', 'oci_stack_replay.py', 'oci_stack_plan.py', 'oci_stack_transaction.py', 'oci_stack_native.py', 'oci_image_cache.py', 'nvidia_lxc_mount_lab.sh', 'oci_nvidia_setup.sh', 'oci_immich_ml.sh', 'oci_rocm_check.py'): shutil.copy2(project_root / 'remote' / helper, temporary / helper) if deployment_kind == 'generic-multi-lxc-stack': shutil.copy2(project_root / 'remote' / 'install_generic_stack.py', temporary / 'install_generic_stack.py') @@ -1625,7 +1625,7 @@ def _run_remote_install( archive.add(project_root / 'remote' / 'haos_healthcheck.py', arcname='haos_healthcheck.py') archive.add(project_root / 'remote' / 'oci_installation_state.py', arcname='oci_installation_state.py') archive.add(project_root / 'remote' / 'oci_instances.py', arcname='oci_instances.py') - for helper in ('oci_ui.sh', 'oci_ui.py', 'oci_description.py', 'oci_console.py', 'oci_native_stack.py', 'oci_native_stack.sh', 'oci_instance_transaction.py', 'oci_host_mounts.py', 'oci_nested_mounts.py', 'oci_runtime_settings.py', 'oci_gpu_devices.py', 'oci_accelerators.py', 'oci_nvidia_runtime.py', 'oci_nvidia_refresh.py', 'oci_nvidia_dynamic.py', 'oci_update_current.py', 'oci_stack_replay.py', 'oci_stack_plan.py', 'oci_stack_transaction.py', 'oci_stack_native.py', 'oci_image_cache.py', 'nvidia_lxc_mount_lab.sh', 'oci_nvidia_setup.sh', 'oci_immich_ml.sh', 'oci_rocm_check.py'): + for helper in ('oci_ui.sh', 'oci_ui.py', 'oci_description.py', 'oci_console.py', 'oci_native_stack.py', 'oci_native_stack.sh', 'oci_instance_transaction.py', 'oci_host_mounts.py', 'oci_nested_mounts.py', 'oci_runtime_settings.py', 'oci_gpu_devices.py', 'oci_accelerators.py', 'oci_nvidia_runtime.py', 'oci_nvidia_refresh.py', 'oci_nvidia_dynamic.py', 'oci_update_current.py', 'oci_resource_modify.py', 'oci_native_modify.py', 'oci_stack_replay.py', 'oci_stack_plan.py', 'oci_stack_transaction.py', 'oci_stack_native.py', 'oci_image_cache.py', 'nvidia_lxc_mount_lab.sh', 'oci_nvidia_setup.sh', 'oci_immich_ml.sh', 'oci_rocm_check.py'): archive.add(project_root / 'remote' / helper, arcname=helper) if deployment_kind == 'generic-multi-lxc-stack': archive.add(project_root / 'remote' / 'install_generic_stack.py', arcname='install_generic_stack.py') diff --git a/oci/tests/test_modify_image_reuse.py b/oci/tests/test_modify_image_reuse.py index 95c7339d..01d475c0 100644 --- a/oci/tests/test_modify_image_reuse.py +++ b/oci/tests/test_modify_image_reuse.py @@ -287,6 +287,7 @@ class UpdateRoutingTests(unittest.TestCase): candidate['deployment']['resources']['memory_mb'] = 768 if not resource_edit: candidate['deployment']['environment'][0]['value'] = 'changed' + candidate['template']['container_contract']['image']['reference'] = 'example/app:other' proposal = {'operation': 'recreate', 'candidate': candidate} import contextlib, oci_operation_notice with patch.object(instances, 'locked', return_value=contextlib.nullcontext()), \ diff --git a/oci/tests/test_native_modify.py b/oci/tests/test_native_modify.py new file mode 100644 index 00000000..62e37885 --- /dev/null +++ b/oci/tests/test_native_modify.py @@ -0,0 +1,258 @@ +"""Native runtime edits keep rootfs, defaults and secrets; failures roll back.""" +import contextlib +import copy +import json +from pathlib import Path +import re +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch, Mock + +sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'remote')) +import oci_native_modify as native +import oci_instances as instances +import oci_update_current as update +import oci_operation_notice +from test_modify_image_reuse import record, CONFIG + + +class NativePlanTests(unittest.TestCase): + def setUp(self): + self.record = record() + self.record['observed']['image']['defaults'] = {'Env': ['A=image-default']} + self.record['deployment']['environment'] = [{'name': 'A', 'value': 'one'}] + self.config = CONFIG+b'net0: name=eth0,bridge=vmbr0,ip=dhcp,hwaddr=AA:BB:CC:DD:EE:FF,tag=20,ip6=auto\n' + self.candidate = copy.deepcopy(self.record) + p = patch.object(native.transaction, 'preflight') + self.preflight = p.start(); self.addCleanup(p.stop) + + def test_environment_preserves_image_defaults_and_other_runtime_values(self): + self.candidate['deployment']['environment'] = [{'name': 'SECRET', 'value': "a' $x \" ; = value", 'sensitive': True}] + result = native.plan(self.record, self.candidate, self.config) + env = dict(x.split('=', 1) for x in result['values']['env'].split('\0')) + self.assertEqual(env, {'A': 'image-default', 'B': 'two', 'SECRET': "a' $x \" ; = value"}) + self.assertTrue(result['restart']) + + def test_remove_environment_without_default_restores_image_environment(self): + self.record['observed']['image']['defaults'] = {} + self.candidate['deployment']['environment'] = [] + self.assertEqual(native.plan(self.record, self.candidate, self.config)['values']['env'], 'B=two') + + def test_public_pct_environment_and_native_lines_have_the_same_values(self): + raw=b'lxc.environment.runtime: A=one\nlxc.environment.runtime: B=two\n' + public=b'env: A=one\0B=two\n' + self.assertEqual(native.environment(raw),native.environment(public)) + self.assertEqual(native.environment(public),{'A':'one','B':'two'}) + + def test_duplicate_invalid_and_control_character_values_are_refused(self): + for items in ([{'name':'X','value':'a'},{'name':'X','value':'b'}], + [{'name':'X','value':'a\nb'}],[{'name':'BAD NAME','value':'a'}]): + self.candidate['deployment']['environment'] = items + with self.assertRaises(ValueError): native.plan(self.record, self.candidate, self.config) + + def test_network_preserves_mac_vlan_ipv6_and_interface_identity(self): + self.candidate['deployment']['network'].update(ipv4='192.0.2.2/24', gateway='192.0.2.1') + with patch.object(Path, 'is_dir', return_value=True): + result = native.plan(self.record, self.candidate, self.config) + fields = dict(x.split('=',1) for x in result['values']['net0'].split(',')) + self.assertEqual(fields['hwaddr'], 'AA:BB:CC:DD:EE:FF') + self.assertEqual(fields['tag'], '20'); self.assertEqual(fields['ip6'], 'auto') + self.assertEqual(fields['ip'], '192.0.2.2/24'); self.assertEqual(fields['gw'], '192.0.2.1') + + def test_invalid_network_is_rejected_before_stopping(self): + for value in ('nonsense', '2001:db8::1/64'): + self.candidate['deployment']['network']['ipv4'] = value + with patch.object(Path, 'is_dir', return_value=True), self.assertRaises(ValueError): + native.plan(self.record,self.candidate,self.config) + + def test_new_managed_disk_uses_native_allocation_and_existing_disk_is_untouched(self): + self.candidate['deployment']['mounts'] = [{'type':'managed-volume','container_path':'/extra', + 'source':'local-zfs','size_gb':1,'backup':True,'read_only':False}] + result = native.plan(self.record,self.candidate,self.config) + self.assertEqual(result['values'], {'mp0':'local-zfs:1,mp=/extra,backup=1,ro=0'}) + self.assertNotIn('rootfs', result['values']) + + def test_removing_mount_detaches_it_without_freeing_its_volume(self): + mount={'type':'managed-volume','container_path':'/extra','source':'local-zfs','size_gb':1,'backup':True} + self.record['deployment']['mounts']=[mount] + result=native.plan(self.record,self.candidate,self.config+b'mp4: local-zfs:subvol-115-disk-1,mp=/extra,backup=1,size=1G\n') + self.assertEqual(result['delete'],['mp4']) + + def test_image_or_installer_adapter_changes_do_not_take_native_path(self): + for key in ('image', 'adapter'): + candidate=copy.deepcopy(self.record) + if key=='image':candidate['template']['container_contract']['image']['reference']='new/image' + else:candidate['deployment']['post_start_configurations']=[{'id':'rootfs-adapter'}] + self.assertIsNone(native.changes(self.record,candidate,self.config)) + + def test_secrets_use_stdin_never_arguments_or_logs(self): + secret="super-secret with 'quotes' and $shell" + with tempfile.TemporaryDirectory() as directory: + root=Path(directory);(root/'115.conf').write_bytes(self.config) + with patch.object(native.resources,'CONFIG_DIR',root), \ + patch.object(instances,'command',return_value=self.config), \ + patch.object(native.transaction,'log') as log, \ + patch.object(native.subprocess,'run',return_value=Mock(returncode=0)) as run: + native.set_config(115,self.record['installation_id'],self.config,{'env':'SECRET='+secret+'\0B=two'}) + self.assertNotIn(secret,str(run.call_args.args)) + self.assertNotIn(secret,str(log.call_args_list)) + self.assertIn(secret,json.loads(run.call_args.kwargs['input'])['env']) + + def test_native_api_failure_does_not_echo_secret_stderr(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory);(root/'115.conf').write_bytes(self.config) + with patch.object(native.resources,'CONFIG_DIR',root),patch.object(instances,'command',return_value=self.config), \ + patch.object(native.subprocess,'run',return_value=Mock(returncode=1,stderr=b'secret')): + with self.assertRaisesRegex(RuntimeError,'Proxmox could not apply') as error: + native.set_config(115,self.record['installation_id'],self.config,{'env':'SECRET=secret'}) + self.assertNotIn('secret',str(error.exception)) + + def test_verifies_all_environment_entries_and_native_values(self): + native.verify_values(CONFIG,{'values':{'env':'A=one\0B=two','cores':'2'},'delete':[]}) + for values in ({'env':'A=wrong\0B=two'},{'cores':'3'}): + with self.assertRaises(ValueError):native.verify_values(CONFIG,{'values':values,'delete':[]}) + with self.assertRaises(ValueError):native.verify_values(CONFIG,{'values':{},'delete':['env']}) + + def test_native_property_canonicalization_keeps_modes_and_default_flags(self): + cfg=CONFIG+b'dev0: path=/dev/fuse,mode=660\n' + native.verify_values(cfg,{'values':{'dev0':'path=/dev/fuse,mode=0660,uid=0,gid=0,deny-write=0'},'delete':[]}) + + +class NativeApplyTests(unittest.TestCase): + def setUp(self): + self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup) + self.root=Path(self.temp.name) + self.record=record();self.candidate=copy.deepcopy(self.record) + self.candidate['deployment']['environment']=[{'name':'A','value':'changed'}] + self.config=CONFIG + self.state={'config':CONFIG,'running':True} + self.commands=[] + def run(*args): + 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 + return b'' + def stop(vmid):self.state['running']=False + def setter(vmid,identity,expected,values,delete=()): + self.assertEqual(expected,self.state['config']) + text=self.state['config'].decode() + if 'env' in values or 'env' in delete: + public='env: ' in text + text=''.join(x for x in text.splitlines(keepends=True) if not x.startswith(('lxc.environment.runtime: ','env: '))) + if public and values.get('env'):text+='env: '+values['env']+'\n' + else: + for item in values.get('env','').split('\0'): + if item:text+='lxc.environment.runtime: '+item+'\n' + self.state['config']=text.encode() + def observe(*args): + observed=copy.deepcopy(self.record['observed']) + observed.update(config=self.state['config'].decode(),config_sha256=native.sha(self.state['config'])) + return observed + patches=[patch.object(native,'plan',return_value={'values':{'env':'A=changed\0B=two'},'delete':[],'restart':True,'changed':['environment']}), + patch.object(native,'set_config',side_effect=setter),patch.object(instances,'command',side_effect=lambda *a:self.state['config']), + patch.object(native.transaction,'run',side_effect=run),patch.object(native.transaction,'stop',side_effect=stop), + patch.object(native.transaction,'healthcheck'),patch.object(native.transaction,'preflight'), + patch.object(native.transaction,'freeze_host_sources',return_value=({},{})), + patch.object(native.transaction,'open_log'),patch.object(native.transaction,'log'), + patch.object(instances,'observe',side_effect=observe),patch('oci_carried_record.carry')] + for p in patches:p.start();self.addCleanup(p.stop) + instances.write(instances.location(self.root,115),self.record) + + def apply(self):return native.apply(self.root,self.record,self.candidate,self.config) + + def test_runtime_edit_restarts_existing_guest_and_keeps_rootfs_image_and_data(self): + folder=self.apply() + saved=instances.read(self.root,115) + self.assertEqual(saved['observed']['image'],self.record['observed']['image']) + self.assertIn(b'rootfs: local-zfs:subvol-115-disk-0',self.state['config']) + self.assertEqual(saved['deployment'],self.candidate['deployment']) + self.assertTrue(self.state['running']) + self.assertEqual(json.loads((folder/'before.json').read_text())['phase'],'committed') + self.assertFalse(any(c[:2] in (('pct','create'),('pct','destroy')) for c in self.commands)) + + def test_failed_healthcheck_rolls_back_and_starts_original_service(self): + with patch.object(native.transaction,'healthcheck',side_effect=[RuntimeError('not healthy'),None]): + with self.assertRaisesRegex(RuntimeError,'not healthy'):self.apply() + self.assertEqual(self.state['config'],CONFIG) + self.assertTrue(self.state['running']) + self.assertEqual(instances.read(self.root,115)['deployment'],self.record['deployment']) + + def test_health_failure_restores_public_nul_separated_environment(self): + self.config=CONFIG.replace(b'lxc.environment.runtime: A=one\nlxc.environment.runtime: B=two\n',b'env: A=one\0B=two\n') + self.state['config']=self.config + self.record['observed'].update(config=self.config.decode(),config_sha256=native.sha(self.config)) + instances.write(instances.location(self.root,115),self.record) + with patch.object(native.transaction,'healthcheck',side_effect=[RuntimeError('not healthy'),None]): + with self.assertRaises(RuntimeError):self.apply() + self.assertEqual(native.environment(self.state['config']),{'A':'one','B':'two'}) + self.assertTrue(self.state['running']) + + def test_stopped_guest_stays_stopped(self): + self.state['running']=False + self.apply() + self.assertFalse(self.state['running']) + self.assertNotIn(('pct','start','115'),self.commands) + + def test_interrupted_edit_is_recovered_through_transaction_dispatch(self): + folder=self.apply() + path=folder/'before.json';state=json.loads(path.read_text());state['phase']='configured' + instances.write(path,state) + pending=copy.deepcopy(self.record) + pending.update(status='updating',transaction_id=folder.name,pending_transaction=str(path)) + instances.write(instances.location(self.root,115),pending) + native.transaction.recover(self.root,path) + self.assertEqual(self.state['config'],CONFIG) + saved=instances.read(self.root,115) + self.assertEqual(saved['status'],'installed');self.assertNotIn('pending_transaction',saved) + self.assertEqual(json.loads(path.read_text())['phase'],'rolled-back') + + def test_recovery_refuses_a_concurrent_environment_edit(self): + folder=self.apply();path=folder/'before.json';state=json.loads(path.read_text());state['phase']='configured' + instances.write(path,state) + pending=copy.deepcopy(self.record) + pending.update(status='updating',transaction_id=folder.name,pending_transaction=str(path)) + instances.write(instances.location(self.root,115),pending) + self.state['config']=self.state['config'].replace(b'A=changed',b'A=concurrent') + with self.assertRaisesRegex(ValueError,'changed'):native.transaction.recover(self.root,path) + self.assertIn(b'A=concurrent',self.state['config']) + + def test_concurrent_change_before_write_is_not_overwritten(self): + def stop(vmid): + self.state['running']=False + self.state['config']+=b'timezone: host\n' + with patch.object(native.transaction,'stop',side_effect=stop): + with self.assertRaisesRegex(ValueError,'changed'):self.apply() + self.assertIn(b'timezone: host',self.state['config']) + self.assertTrue(self.state['running']) + + def test_router_does_not_resolve_download_or_recreate_for_environment_edit(self): + with patch.object(instances,'ROOT',self.root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(native.transaction,'external_changes',return_value={}), \ + patch.object(update,'resolve_archive',side_effect=AssertionError('registry path')), \ + patch.object(native.transaction,'apply',side_effect=AssertionError('recreation')), \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + update.update(115,proposal={'operation':'recreate','candidate':self.candidate}) + self.assertEqual(instances.read(self.root,115)['deployment']['environment'],self.candidate['deployment']['environment']) + + def test_unsupported_same_image_adaptation_never_silently_recreates(self): + self.candidate['deployment']['post_start_configurations']=[{'id':'unsupported-adapter'}] + with patch.object(instances,'ROOT',self.root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(native.transaction,'external_changes',return_value={}), \ + patch.object(update,'resolve_archive',side_effect=AssertionError('registry path')), \ + patch.object(native.transaction,'apply',side_effect=AssertionError('recreation')): + with self.assertRaisesRegex(ValueError,'application-specific adaptation'): + update.update(115,proposal={'operation':'recreate','candidate':self.candidate}) + + def test_optional_backup_does_not_force_recreation(self): + with patch.object(instances,'ROOT',self.root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(native.transaction,'external_changes',return_value={}), \ + patch.object(update,'resolve_archive',side_effect=AssertionError('registry path')), \ + patch.object(update,'backup_native_settings') as backup, \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + update.update(115,proposal={'operation':'recreate','candidate':self.candidate},keep_backup='test-storage') + backup.assert_called_once_with(115,'test-storage') + + +if __name__=='__main__':unittest.main() From 14513cc898a84e797d9d562b8f6ce6602fba2210 Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:19:24 +0200 Subject: [PATCH 24/53] Speed up OCI Modify entry by deferring backup probes and reusing verified config --- .../test_oci_update_preservation_wording.py | 1 + .github/workflows/test-oci-registries.yml | 4 + oci/src/proxmenux_oci/management.py | 29 ++++- oci/tests/test_modify_latency.py | 100 ++++++++++++++++++ 4 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 oci/tests/test_modify_latency.py diff --git a/.github/scripts/tests/test_oci_update_preservation_wording.py b/.github/scripts/tests/test_oci_update_preservation_wording.py index 44146970..477196e9 100644 --- a/.github/scripts/tests/test_oci_update_preservation_wording.py +++ b/.github/scripts/tests/test_oci_update_preservation_wording.py @@ -130,6 +130,7 @@ class UpdateWording(unittest.TestCase): reconcile.propose = lambda *args: None scope = {'sys': SimpleNamespace(path=[], executable='python3'), 'translate': lambda text: text, 'check_selected': lambda project, row: row, + '_selected_configuration': lambda vmid: instances.command('pct', 'config', str(vmid)), 'images': SimpleNamespace(offer_removal=lambda *args: None), 'carry_records': lambda project, **options: {}, '_work_backup_ready': lambda project, ui, vmid: True, diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 9612c0d3..7625fe1c 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -32,3 +32,7 @@ jobs: env: PYTHONDONTWRITEBYTECODE: '1' run: python3 -m unittest discover -s oci/tests -p 'test_registry_*.py' -v + - name: Run OCI Modify latency regressions + env: + PYTHONDONTWRITEBYTECODE: '1' + run: python3 -m unittest discover -s oci/tests -p test_modify_latency.py -v diff --git a/oci/src/proxmenux_oci/management.py b/oci/src/proxmenux_oci/management.py index cbf8e041..9b71d1b1 100644 --- a/oci/src/proxmenux_oci/management.py +++ b/oci/src/proxmenux_oci/management.py @@ -106,13 +106,32 @@ def _config_hostname(vmid): return '' +def _selected_configuration(vmid): + """Reuse pct's output only while its complete native source is unchanged. + + The forced carried-record verification has just fetched a fresh config. + Older installations without that source-keyed cache still query pct. + The lifecycle backend independently validates again before any mutation. + """ + import oci_instances as instances + try: + import oci_carried_record + reader = getattr(oci_carried_record, 'configuration', None) + config = reader(instances.ROOT, vmid) if reader else None + if isinstance(config, str) and config: + return config.encode() + except (ImportError, OSError, ValueError, RuntimeError, subprocess.SubprocessError): + pass + return instances.command('pct', 'config', str(vmid)) + + def check_selected(project, row): """Validate only the chosen container; lifecycle backends validate its stack.""" sys.path.insert(0, str(project / 'remote')) import oci_instances as instances with instances.locked(instances.ROOT): record = instances.read(instances.ROOT, row['vmid']) - config = instances.command('pct', 'config', str(row['vmid'])) + config = _selected_configuration(row['vmid']) marker = instances.identity(config) reason = 'matched' if marker == record['installation_id'] else 'identity-unconfirmed' return public_row(record, {'reason': reason}) @@ -397,12 +416,12 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): return _toggle_watchdog(project, ui, row['vmid'], watched) if action == 'remove': return _remove(project, ui, row['vmid']) - if not _work_backup_ready(project, ui, row['vmid']): + if action != 'modify' and not _work_backup_ready(project, ui, row['vmid']): return False record = instances.read(instances.ROOT, row['vmid']) import oci_instance_reconcile as reconcile try: - current_config = instances.command('pct', 'config', str(row['vmid'])) + current_config = _selected_configuration(row['vmid']) adoption = reconcile.propose(record, current_config) except (OSError, ValueError, RuntimeError) as error: ui.message(str(error), translate('Review external OCI changes')) @@ -443,6 +462,10 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): wizard.close() if not approved: return False + # Opening or cancelling the editor needs no disk/storage probes. + # Keep the existing backup checks after the user confirms edits. + if not _work_backup_ready(project, ui, row['vmid']): + return False elif not ui.review(translate('If the image channel has a new version, the CT is stopped, backed up and verified, and replaced with the new image to update the container. If the update fails after the container was changed, the backup is restored. An update that stops halfway is recovered from the OCI management menu.'), translate('Update OCI'), question=translate('Update now?'), default=True): return False diff --git a/oci/tests/test_modify_latency.py b/oci/tests/test_modify_latency.py new file mode 100644 index 00000000..788215ad --- /dev/null +++ b/oci/tests/test_modify_latency.py @@ -0,0 +1,100 @@ +"""Opening Modify avoids backup probes while preserving operation safeguards.""" +from pathlib import Path +import sys +import unittest +from unittest.mock import Mock,patch +ROOT=Path(__file__).resolve().parents[1] +sys.path[:0]=[str(ROOT/'src'),str(ROOT/'remote')] +from proxmenux_oci import management as menu,recreation +import oci_carried_record as carried +import oci_instances as instances +import oci_instance_reconcile as reconcile +from proxmenux_oci.ui import UserCancelled + + +def record(): + return {'vmid': 115, 'installation_id': 'test-installation', 'status': 'installed', + 'observed': {'config_sha256': 'native-config-fingerprint'}, + 'template': {'id': 'example-app', 'catalog_ui': {'title': 'Example'}, + 'container_contract': {'image': {'reference': 'example/app:latest'}}, + 'proxmox': {'installer_profile': {}}}, + 'deployment': {'vmid': 115, 'hostname': 'example', 'resources': {'cores': 2, 'memory_mb': 512}, + 'network': {'bridge': 'vmbr0', 'ipv4': 'dhcp', 'gateway': None}, + 'rootfs': {'storage': 'local-zfs', 'size_gb': 8}, 'mounts': [ + {'container_path': '/data', 'source': 'local-zfs', + 'type': 'managed-volume', 'size_gb': 8, 'backup': True}], + 'devices': [], 'security': {'unprivileged': True}, + 'environment': [{'name': 'PASSWORD', 'value': 'private-test-secret', + 'sensitive': True}]}} + + +class SelectedConfigTests(unittest.TestCase): + def test_valid_source_keyed_cache_avoids_another_pct_process(self): + with patch.object(carried,'configuration',create=True,return_value='description: current\n') as reader, \ + patch.object(instances,'command') as command: + self.assertEqual(menu._selected_configuration(115),b'description: current\n') + reader.assert_called_once_with(instances.ROOT,115) + command.assert_not_called() + + def test_old_or_unavailable_cache_falls_back_to_pct(self): + for reader in (None,Mock(return_value=None),Mock(return_value={}),Mock(side_effect=OSError('cache unavailable'))): + with self.subTest(reader=reader),patch.object(carried,'configuration',reader,create=True), \ + patch.object(instances,'command',return_value=b'live config') as command: + self.assertEqual(menu._selected_configuration(115),b'live config') + command.assert_called_once_with('pct','config','115') + + def test_identity_check_is_still_performed_on_the_selected_config(self): + r=record();r['installation_id']='11111111-1111-4111-8111-111111111111' + import contextlib + with patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(instances,'read',return_value=r), \ + patch.object(menu,'_selected_configuration',return_value=b'description: another container\n'): + self.assertEqual(menu.check_selected(ROOT,{'vmid':115})['reason'],'identity-unconfirmed') + + +class EditorLatencyTests(unittest.TestCase): + def run_editor(self,proposal=None,backup_ready=True,stale=False,adoption_error=False,approved=True): + events=[];saved=record() + row={'vmid':115,'reason':'matched','stack':False,'pending':False,'status':'installed'} + def edit(*a,**kw): + events.append('editor') + if proposal is None:raise UserCancelled() + return proposal + def backup(*a,**kw):events.append('backup');return backup_ready + ui=Mock();ui.unattended=True;ui.review.return_value=approved + with patch.object(menu,'carry_records',return_value={115:'stale'} if stale else {}) as verify, \ + patch.object(menu,'check_selected',return_value=row), \ + patch.object(menu,'_selected_configuration',return_value=b'current config'), \ + patch.object(instances,'read',return_value=saved), \ + patch.object(reconcile,'propose',side_effect=ValueError('external changes') if adoption_error else None,return_value=None), \ + patch.object(recreation,'edit_recreation',side_effect=edit), \ + patch.object(menu,'_work_backup_ready',side_effect=backup), \ + patch.object(menu,'_run_lifecycle',side_effect=lambda *a:events.append('lifecycle') or True): + try:result=menu.manage_instance(ROOT,ui,row,action='modify') + except UserCancelled:result=False + verify.assert_called_once_with(ROOT,vmids=[115],verify=True) + return result,events + + def test_open_and_cancel_has_no_backup_or_storage_probes(self): + result,events=self.run_editor() + self.assertFalse(result);self.assertEqual(events,['editor']) + + def test_declining_the_review_has_no_backup_or_storage_probes(self): + result,events=self.run_editor({'operation':'recreate','candidate':record()},approved=False) + self.assertFalse(result);self.assertEqual(events,['editor']) + + def test_confirmed_edit_keeps_backup_check_before_the_lifecycle(self): + result,events=self.run_editor({'operation':'recreate','candidate':record()}) + self.assertTrue(result);self.assertEqual(events,['editor','backup','lifecycle']) + + def test_missing_backup_space_still_prevents_the_lifecycle(self): + result,events=self.run_editor({'operation':'recreate','candidate':record()},backup_ready=False) + self.assertFalse(result);self.assertEqual(events,['editor','backup']) + + def test_restored_or_conflicting_instance_cannot_enter_editor(self): + for options in ({'stale':True},{'adoption_error':True}): + result,events=self.run_editor(**options) + self.assertFalse(result);self.assertEqual(events,[]) + + +if __name__=='__main__':unittest.main() From 98ae1f36260c1a541d58656524588f921efeecc8 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 17 Oct 2026 18:13:44 +0200 Subject: [PATCH 25/53] feat(pbs): post-install menu for Proxmox Backup Server - Open the post-install menu from the PBS menu - List only the customizable options that apply to PBS - Offer the PBS no-subscription repository through the native repository API - Upgrade PBS with the package list shown before applying it - Remove and restore the subscription notice on the PBS web interface - Install Figurine on PBS without the Proxmox VE aliases - Offer the Helper-Scripts PBS post-install and microcode scripts - Tune firewall bridges after the systemd udev rules in the automated post-install - Add PBS repository policy tests and syntax checks - Document the PBS differences on the post-install page - Use backups in the Spanish PBS update message --- .github/workflows/test-pbs.yml | 5 +- AppImage/components/health-status-modal.tsx | 19 +- AppImage/components/monitor-switcher.tsx | 283 +++++++++++++++ AppImage/components/notification-settings.tsx | 14 +- AppImage/components/pbs-dashboard.tsx | 44 --- AppImage/components/pbs-datastore-usage.tsx | 112 ++++++ AppImage/components/pbs-panel.tsx | 202 ++++++++--- AppImage/components/proxmox-dashboard.tsx | 79 +++-- AppImage/components/settings.tsx | 19 +- AppImage/components/system-overview.tsx | 80 ++++- AppImage/lib/use-platform.ts | 8 + AppImage/messages/en/common.json | 49 ++- AppImage/messages/es/common.json | 49 ++- AppImage/scripts/build_appimage.sh | 2 + .../scripts/flask_monitor_links_routes.py | 58 ++++ AppImage/scripts/flask_notification_routes.py | 15 + AppImage/scripts/flask_server.py | 38 ++- AppImage/scripts/monitor_links.py | 321 ++++++++++++++++++ .../tests/test_fwbr_udev_rule_order.py | 12 +- AppImage/scripts/tests/test_monitor_links.py | 150 ++++++++ lang/es.json | 19 +- scripts/global/remove-banner-pve-v3.sh | 13 +- scripts/global/repository-functions.sh | 45 +++ scripts/global/repository_policy.py | 83 +++-- scripts/global/update-pbs.sh | 188 ++++++++++ scripts/menus/menu_post_install.sh | 27 +- scripts/menus/pbs_menu.sh | 28 +- scripts/post_install/auto_post_install.sh | 46 ++- .../post_install/customizable_post_install.sh | 50 ++- scripts/post_install/uninstall-tools.sh | 10 +- tests/test_repository_policy_pbs.py | 103 ++++++ web/app/[locale]/docs/post-install/page.tsx | 10 + web/messages/en/docs/post-install/index.json | 11 + web/messages/es/docs/post-install/index.json | 11 + 34 files changed, 1976 insertions(+), 227 deletions(-) create mode 100644 AppImage/components/monitor-switcher.tsx delete mode 100644 AppImage/components/pbs-dashboard.tsx create mode 100644 AppImage/components/pbs-datastore-usage.tsx create mode 100644 AppImage/lib/use-platform.ts create mode 100644 AppImage/scripts/flask_monitor_links_routes.py create mode 100644 AppImage/scripts/monitor_links.py create mode 100644 AppImage/scripts/tests/test_monitor_links.py create mode 100644 scripts/global/update-pbs.sh create mode 100644 tests/test_repository_policy_pbs.py diff --git a/.github/workflows/test-pbs.yml b/.github/workflows/test-pbs.yml index 2d60623c..2dbc71b0 100644 --- a/.github/workflows/test-pbs.yml +++ b/.github/workflows/test-pbs.yml @@ -17,6 +17,9 @@ on: - 'scripts/global/repository-functions.sh' - 'scripts/menus/pbs_menu.sh' - 'scripts/utilities/pbs_update.sh' + - 'scripts/global/update-pbs.sh' + - 'scripts/menus/menu_post_install.sh' + - 'scripts/post_install/*.sh' push: branches: [main, develop] paths: @@ -49,7 +52,7 @@ jobs: run: | python -B -m unittest discover -s AppImage/scripts/tests -p test_pbs.py -v python -B -m unittest discover -s AppImage/scripts/tests -p test_auth_manager_ssl_reload.py -v - for script in scripts/global/platform-functions.sh scripts/global/repository-functions.sh scripts/menus/pbs_menu.sh scripts/utilities/pbs_update.sh scripts/menus/main_menu.sh scripts/post_install/auto_post_install.sh scripts/post_install/customizable_post_install.sh; do + for script in scripts/global/platform-functions.sh scripts/global/repository-functions.sh scripts/menus/pbs_menu.sh scripts/utilities/pbs_update.sh scripts/menus/main_menu.sh scripts/post_install/auto_post_install.sh scripts/post_install/customizable_post_install.sh scripts/post_install/uninstall-tools.sh scripts/global/update-pbs.sh scripts/menus/menu_post_install.sh; do bash -n "$script" done - uses: actions/setup-node@v6 diff --git a/AppImage/components/health-status-modal.tsx b/AppImage/components/health-status-modal.tsx index c8be541e..090a51fb 100644 --- a/AppImage/components/health-status-modal.tsx +++ b/AppImage/components/health-status-modal.tsx @@ -42,6 +42,7 @@ import { } from "lucide-react" import { ScriptTerminalModal } from "./script-terminal-modal" import { useT } from "@/lib/i18n/provider" +import { useIsPbs } from "@/lib/use-platform" interface CategoryCheck { status: string @@ -119,6 +120,7 @@ const CATEGORIES = [ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatusModalProps) { const t = useT() + const isPbs = useIsPbs() const [loading, setLoading] = useState(true) const [healthData, setHealthData] = useState(null) const [dismissedItems, setDismissedItems] = useState([]) @@ -460,6 +462,11 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu return originalStatus?.toUpperCase() || "UNKNOWN" } + // Checks the host reports as not applicable (guests on PBS) are not listed or counted. + const visibleCategories = CATEGORIES.filter(({ key }) => + !(healthData?.details?.[key as keyof typeof healthData.details] as { not_applicable?: boolean } | undefined)?.not_applicable) + const categoryLabel = (key: string) => t(`healthStatus.categories.${isPbs && key === "services" ? "pbsServices" : key}`) + const getHealthStats = () => { if (!healthData?.details) return { total: 0, healthy: 0, info: 0, warnings: 0, critical: 0, unknown: 0 } @@ -469,7 +476,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu let critical = 0 let unknown = 0 - CATEGORIES.forEach(({ key }) => { + visibleCategories.forEach(({ key }) => { const categoryData = healthData.details[key as keyof typeof healthData.details] if (categoryData) { const effectiveStatus = getEffectiveStatus(key, categoryData.status) @@ -481,7 +488,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu } }) - return { total: CATEGORIES.length, healthy, info, warnings, critical, unknown } + return { total: visibleCategories.length, healthy, info, warnings, critical, unknown } } const stats = getHealthStats() @@ -759,7 +766,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu {/* Category List */}
- {CATEGORIES.map(({ key, Icon }) => { + {visibleCategories.map(({ key, Icon }) => { const categoryData = healthData.details[key as keyof typeof healthData.details] const originalStatus = categoryData?.status || "UNKNOWN" const status = getEffectiveStatus(key, originalStatus) @@ -784,7 +791,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
-

{t(`healthStatus.categories.${key}`)}

+

{categoryLabel(key)}

{hasChecks && ( ({Object.values(checks).filter(c => c.installed !== false).length}) @@ -871,7 +878,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu {filteredDismissed.map((item) => { const catMeta = CATEGORIES.find(c => c.category === item.category || c.key === item.category) const CatIcon = catMeta?.Icon || BellOff - const catLabel = catMeta ? t(`healthStatus.categories.${catMeta.key}`) : item.category + const catLabel = catMeta ? categoryLabel(catMeta.key) : item.category const isPermanent = item.permanent || item.suppression_remaining_hours === -1 return ( @@ -936,7 +943,7 @@ export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatu
- {catMeta ? t(`healthStatus.categories.${catMeta.key}`) : cs.label} + {catMeta ? categoryLabel(catMeta.key) : cs.label}
{durationLabel} diff --git a/AppImage/components/monitor-switcher.tsx b/AppImage/components/monitor-switcher.tsx new file mode 100644 index 00000000..0cd2d34b --- /dev/null +++ b/AppImage/components/monitor-switcher.tsx @@ -0,0 +1,283 @@ +"use client" + +import { useEffect, useState } from "react" +import useSWR, { mutate as refreshCache } from "swr" +import { ArrowLeft, ChevronDown, DatabaseBackup, ExternalLink, Plus, Server, Trash2 } from "lucide-react" +import { Button } from "./ui/button" +import { Input } from "./ui/input" +import { Label } from "./ui/label" +import { Switch } from "./ui/switch" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog" +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, +} from "./ui/dropdown-menu" +import { fetchApi } from "../lib/api-config" +import { useT } from "../lib/i18n/provider" + +interface MonitorLink { + key: string + name: string + product: "pve" | "pbs" + group: "cluster" | "standalone" | "pbs" + url: string | null + status: "online" | "offline" | "unknown" + native_url?: string | null + manual: boolean + hidden: boolean + custom_url: boolean +} +interface MonitorLinks { self: { name: string; product: string }; links: MonitorLink[] } + +const GROUPS = ["cluster", "standalone", "pbs"] as const +const fetcher = (url: string) => fetchApi(url) +const ORIGIN_KEY = "proxmenux-came-from" + +interface Origin { url: string; name: string; product: "pve" | "pbs" } + +// The Monitor a jump came from, as the browser reached it. Only a bare http(s) +// origin is accepted, and the parameters leave the address at once. +function takeOrigin(): (Origin & { fresh?: boolean }) | null { + if (typeof window === "undefined") return null + const params = new URLSearchParams(window.location.search) + const from = params.get("pmx_from") + if (from !== null) { + const name = params.get("pmx_name") || "" + const product = params.get("pmx_product") === "pbs" ? "pbs" : "pve" + for (const key of ["pmx_from", "pmx_name", "pmx_product"]) params.delete(key) + const query = params.toString() + window.history.replaceState(null, "", window.location.pathname + (query ? `?${query}` : "") + window.location.hash) + try { + const parsed = new URL(from) + if (/^https?:$/.test(parsed.protocol) && parsed.origin === from && parsed.origin !== window.location.origin) { + const origin: Origin = { url: parsed.origin, name: (name || parsed.hostname).slice(0, 64), product } + try { sessionStorage.setItem(ORIGIN_KEY, JSON.stringify(origin)) } catch { /* private mode */ } + return { ...origin, fresh: true } + } + } catch { /* not an address */ } + } + try { + const stored = JSON.parse(sessionStorage.getItem(ORIGIN_KEY) || "null") + return stored && typeof stored.url === "string" && /^https?:\/\//.test(stored.url) ? stored : null + } catch { return null } +} + +// Where a link leads: its Monitor, or the native PBS interface when no Monitor answers there. +function destination(link: MonitorLink): { href: string; native: boolean } | null { + if (link.product === "pbs" && link.status !== "online" && link.native_url) return { href: link.native_url, native: true } + return link.url ? { href: link.url, native: false } : null +} + +function ProductIcon({ product, className }: { product: string; className?: string }) { + return product === "pbs" ? : +} + +export function MonitorSwitcher({ nodeName, compact = false }: { nodeName: string; compact?: boolean }) { + const t = useT() + const [manageOpen, setManageOpen] = useState(false) + // The backend keeps the list ready, so the menu opens already filled. + const { data, error, isLoading } = useSWR("/api/monitor-links", fetcher, { revalidateOnFocus: false, refreshInterval: 300000 }) + + const [origin, setOrigin] = useState<(Origin & { fresh?: boolean }) | null>(null) + const [offerOrigin, setOfferOrigin] = useState(false) + const [adding, setAdding] = useState(false) + const [addError, setAddError] = useState("") + useEffect(() => { setOrigin(takeOrigin()) }, []) + const originKnown = !!origin && !!data?.links.some(link => link.url === origin.url) + useEffect(() => { + // Offered once, right after arriving, when this Monitor has no way back yet. + if (origin?.fresh && data && !originKnown) setOfferOrigin(true) + }, [origin, data, originKnown]) + + // The address carries where the jump comes from, so the other Monitor can offer the way back. + const withOrigin = (href: string) => { + const params = new URLSearchParams({ pmx_from: window.location.origin, pmx_name: nodeName, pmx_product: data?.self.product === "pbs" ? "pbs" : "pve" }) + return `${href}/?${params.toString()}` + } + const open = (href: string, newTab: boolean) => { + if (newTab) window.open(href, "_blank", "noopener,noreferrer") + else window.location.assign(href) + } + const go = (link: MonitorLink, newTab: boolean) => { + const target = destination(link) + if (!target) return + if (target.native) open(target.href, true) + else open(withOrigin(target.href), newTab) + } + const addOrigin = async () => { + if (!origin) return + setAdding(true); setAddError("") + try { + await fetchApi("/api/monitor-links", { method: "POST", body: JSON.stringify({ name: origin.name, url: origin.url, product: origin.product }) }) + await refreshCache("/api/monitor-links") + setOfferOrigin(false) + } catch (err) { + setAddError(err instanceof Error ? err.message : t("monitorLinks.saveFailed")) + } finally { setAdding(false) } + } + + return
event.stopPropagation()}> + + + + + + {t("monitorLinks.thisNode")} + + + + {nodeName} + + {origin && !originKnown && open(withOrigin(origin.url), false)}> + + {t("monitorLinks.backTo", { name: origin.name })} + } + {isLoading &&

{t("monitorLinks.checking")}

} + {error &&

{t("monitorLinks.loadFailed")}

} + {data && data.links.length === 0 &&

{t("monitorLinks.empty")}

} + {data && GROUPS.map(group => { + const rows = data.links.filter(link => link.group === group) + if (rows.length === 0) return null + return
+ + {t(`monitorLinks.groups.${group}`)} + {rows.map(link => { + const target = destination(link) + return go(link, false)}> + + + {link.name} + {target?.native + ? {t("monitorLinks.pbsInterface")} + : link.status !== "online" && {t("monitorLinks.noResponse")}} + {target && } + + })} +
+ })} + + setManageOpen(true)}> + {t("monitorLinks.manage")} + +
+
+ {manageOpen && } + {origin && + event.stopPropagation()}> + + {t("monitorLinks.cameFromTitle")} + {t("monitorLinks.cameFromBody", { name: origin.name })} + +

{origin.url}

+ {addError &&

{addError}

} +
+ + +
+
+
} +
+} + +function ManageLinksDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) { + const t = useT() + const { data, mutate } = useSWR("/api/monitor-links?all=1", fetcher, { revalidateOnFocus: false }) + const [form, setForm] = useState({ name: "", url: "", product: "pve" }) + const [addresses, setAddresses] = useState>({}) + const [busy, setBusy] = useState(false) + const [error, setError] = useState("") + + const run = async (action: () => Promise) => { + setBusy(true); setError("") + try { + await action() + await mutate() + await refreshCache("/api/monitor-links") + } catch (err) { + setError(err instanceof Error ? err.message : t("monitorLinks.saveFailed")) + } finally { setBusy(false) } + } + const send = (path: string, method: string, body?: unknown) => + fetchApi(path, { method, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }) + + return + event.stopPropagation()}> + + {t("monitorLinks.dialogTitle")} + {t("monitorLinks.dialogDescription")} + + +
+

{t("monitorLinks.addTitle")}

+
+
+ + setForm({ ...form, name: event.target.value })} /> +
+
+ + setForm({ ...form, url: event.target.value })} /> +
+
+ + +
+
+ +
+ + {error &&

{error}

} + +
+ {data?.links.length === 0 &&

{t("monitorLinks.empty")}

} + {data?.links.map(link => { + const address = addresses[link.key] ?? (link.manual || link.custom_url ? link.url || "" : "") + return
+
+ + {link.name} + {link.manual ? t("monitorLinks.added") : t("monitorLinks.detected")} + {!link.manual && } + {link.manual && } +
+
+ setAddresses({ ...addresses, [link.key]: event.target.value })} /> + +
+
+ })} +
+
+
+} diff --git a/AppImage/components/notification-settings.tsx b/AppImage/components/notification-settings.tsx index e84590ee..194d5574 100644 --- a/AppImage/components/notification-settings.tsx +++ b/AppImage/components/notification-settings.tsx @@ -13,6 +13,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ". import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog" import { fetchApi } from "../lib/api-config" import { useT } from "../lib/i18n/provider" +import { useIsPbs } from "../lib/use-platform" import { Bell, BellOff, Send, CheckCircle2, XCircle, Loader2, AlertTriangle, Info, Settings2, Zap, Eye, EyeOff, @@ -154,6 +155,8 @@ function validateGotifyUrl(url: string): { error?: string; warning?: string } { } const EVENT_CATEGORIES = ["vm_ct", "backup", "resources", "storage", "network", "security", "cluster", "services", "health", "updates", "hardware", "system", "other"].map(key => ({ key })) +// Guests, vzdump backups and the cluster do not exist on Proxmox Backup Server. +const PVE_ONLY_CATEGORIES = new Set(["vm_ct", "backup", "cluster"]) const CHANNEL_TYPES = ["telegram", "gotify", "discord", "email", "pushover", "apprise"] as const @@ -330,6 +333,7 @@ const DEFAULT_CONFIG: NotificationConfig = { export function NotificationSettings() { const t = useT() + const isPbs = useIsPbs() const { resolvedTheme } = useTheme() const [config, setConfig] = useState(DEFAULT_CONFIG) const [status, setStatus] = useState(null) @@ -703,7 +707,7 @@ export function NotificationSettings() {
- {EVENT_CATEGORIES.filter(cat => cat.key !== "other").map(cat => { + {EVENT_CATEGORIES.filter(cat => cat.key !== "other" && !(isPbs && PVE_ONLY_CATEGORIES.has(cat.key))).map(cat => { const isEnabled = overrides.categories[cat.key] ?? true const isExpanded = expandedCategories.has(`${chName}.${cat.key}`) // Hide the LXC update toggle when the user has disabled the @@ -922,7 +926,7 @@ export function NotificationSettings() { const wasEnabled = originalConfig.enabled const isNowDisabled = !nextConfig.enabled - if (wasEnabled && isNowDisabled) { + if (wasEnabled && isNowDisabled && !isPbs) { try { await fetchApi("/api/notifications/proxmox/cleanup-webhook", { method: "POST" }) } catch { @@ -1217,8 +1221,10 @@ export function NotificationSettings() { setConfig(newConfig) setOriginalConfig(newConfig) - // 2) Auto-configure PVE webhook - try { + // 2) Auto-configure PVE webhook; PBS has no /etc/pve notifications + if (isPbs) { + setWebhookSetup({ status: "idle", fallback_commands: [], error: "" }) + } else try { const setup = await fetchApi<{ configured: boolean secret?: string diff --git a/AppImage/components/pbs-dashboard.tsx b/AppImage/components/pbs-dashboard.tsx deleted file mode 100644 index fd90b1b6..00000000 --- a/AppImage/components/pbs-dashboard.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client" - -import { useState } from "react" -import useSWR from "swr" -import { PBSPanel } from "./pbs-panel" -import { Security } from "./security" -import { SystemLogs } from "./system-logs" -import { TerminalPanel } from "./terminal-panel" -import { ThemeToggle } from "./theme-toggle" -import { Button } from "./ui/button" -import { Card, CardContent } from "./ui/card" -import { fetchApi } from "../lib/api-config" -import { useT } from "../lib/i18n/provider" - -interface Vitals { hostname: string; cpu_usage: number; memory_usage: number; memory_used: number; memory_total: number; uptime: string } - -export function PBSDashboard() { - const t = useT() - const [tab, setTab] = useState("overview") - const { data } = useSWR("/api/system", url => fetchApi(url), { refreshInterval: 10000 }) - return
-
-

ProxMenux · PBS

{data?.hostname || t("pbs.loading")}

- - -
- - {tab === "overview" && <> -
- {[[t("pbs.cpu"), data ? `${data.cpu_usage.toFixed(1)}%` : "—"], - [t("pbs.memory"), data ? `${data.memory_used.toFixed(1)} / ${data.memory_total.toFixed(1)} GiB (${data.memory_usage.toFixed(1)}%)` : "—"], - [t("pbs.uptime"), data?.uptime || "—"]].map(([label, value]) => -

{label}

{value}

)} -
- - } - {tab === "logs" && } - {tab === "terminal" && } - {tab === "security" && } -
-} diff --git a/AppImage/components/pbs-datastore-usage.tsx b/AppImage/components/pbs-datastore-usage.tsx new file mode 100644 index 00000000..944ba340 --- /dev/null +++ b/AppImage/components/pbs-datastore-usage.tsx @@ -0,0 +1,112 @@ +"use client" + +import { CalendarClock, Database } from "lucide-react" +import { Badge } from "./ui/badge" +import { Progress } from "./ui/progress" +import { formatStorage } from "../lib/utils" +import { useT } from "../lib/i18n/provider" + +export interface PBSDatastore { + name: string + path?: string + total?: number | null + used?: number | null + avail?: number | null + error?: string + "backend-type"?: string + "estimated-full-date"?: number + "gc-status"?: { upid?: string | null; "removed-bytes"?: number; "pending-bytes"?: number } +} + +export function datastorePercent(store: PBSDatastore): number | null { + if (store.total == null || store.used == null || store.total <= 0) return null + return (store.used / store.total) * 100 +} + +// Same thresholds and colours as the PVE storage cards. +export function usageTone(percent: number): { bar: string; text: string; used: string } { + if (percent > 90) return { bar: "[&>div]:bg-red-500", text: "text-red-500", used: "text-red-400" } + if (percent > 75) return { bar: "[&>div]:bg-yellow-500", text: "text-yellow-500", used: "text-yellow-400" } + return { bar: "[&>div]:bg-blue-500", text: "text-muted-foreground", used: "text-blue-400" } +} + +const gib = (bytes: number) => formatStorage(bytes / 1024 ** 3) + +// A datastore without a readable capacity keeps its message: it is never shown as empty. +export function DatastoreUsageRow({ store, compact = false }: { store: PBSDatastore; compact?: boolean }) { + const t = useT() + const percent = datastorePercent(store) + if (percent == null) { + return
+
+ {store.name} + — +
+

{store.error || t("pbs.capacityUnavailable")}

+
+ } + const tone = usageTone(percent) + return
+
+ {store.name} + {percent.toFixed(1)}% +
+ + {!compact &&
+ {gib(store.used!)} / {gib(store.total!)} + {t("pbs.free")}: {gib(store.avail ?? store.total! - store.used!)} +
} +
+} + +// Same layout as the "Proxmox storage" cards of the PVE Monitor. +export function PBSDatastoreCard({ store }: { store: PBSDatastore }) { + const t = useT() + const percent = datastorePercent(store) + const tone = percent == null ? null : usageTone(percent) + const fullDate = store["estimated-full-date"] + const type = store["backend-type"] || "datastore" + return
+
+
+ +

{store.name}

+ {type} +
+
+ + {store.error ? t("storage.status.error") : t("storage.status.active")} + + {percent != null && {percent.toFixed(2)}%} +
+
+ {percent != null && tone ? ( +
+ +
+
+

{t("storage.total")}

+

{gib(store.total!)}

+
+
+

{t("storage.used")}

+

{gib(store.used!)}

+
+
+

{t("storage.available")}

+

{gib(store.avail ?? store.total! - store.used!)}

+
+
+
+ ) : ( +

{store.error || t("storage.capacityNotReported")}

+ )} + {(store.path || fullDate !== undefined) &&
+ {store.path && {store.path}} + {fullDate !== undefined && + {t("pbs.estimatedFull")}: + {fullDate > 0 ? new Date(fullDate * 1000).toLocaleDateString() : t("pbs.estimatedFullUnknown")} + } +
} +
+} diff --git a/AppImage/components/pbs-panel.tsx b/AppImage/components/pbs-panel.tsx index fb1354eb..d71b30a9 100644 --- a/AppImage/components/pbs-panel.tsx +++ b/AppImage/components/pbs-panel.tsx @@ -2,29 +2,74 @@ import { useEffect, useState } from "react" import useSWR from "swr" +import { + Activity, ArchiveRestore, Database, DatabaseBackup, Download, ListChecks, + RefreshCw, Scissors, Server, Settings2, ShieldCheck, Terminal, Trash2, +} from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" +import { Badge } from "./ui/badge" import { Button } from "./ui/button" import { Input } from "./ui/input" import { Label } from "./ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { PBSDatastoreCard, type PBSDatastore } from "./pbs-datastore-usage" import { fetchApi } from "../lib/api-config" -import { formatBytes } from "../lib/utils" import { useT } from "../lib/i18n/provider" interface Connection { id: string; name: string; url: string; token_id: string; fingerprint: string; has_token: boolean } interface Connections { local_available: boolean; servers: Connection[] } -interface Datastore { name: string; path?: string; total?: number | null; used?: number | null; avail?: number | null; error?: string } interface Task { upid: string; worker_type?: string; worker_id?: string; starttime?: number; endtime?: number; status?: string } interface Job { id: string; store?: string; schedule?: string; "last-run-state"?: string } interface PBSStatus { name: string; version: string | null; fetched_at: number; services: Record; - datastores: Datastore[] | null; tasks: Task[] | null; + datastores: PBSDatastore[] | null; tasks: Task[] | null; sync: Job[] | null; verify: Job[] | null; prune: Job[] | null; errors: Record; } const emptyForm = { id: "", name: "", url: "", token_id: "", fingerprint: "", token_secret: "" } -const selectClass = "h-10 rounded-md border bg-background px-3 text-sm" +const NEW_CONNECTION = "__new__" +const badgeTone = { + ok: "bg-green-500/10 text-green-500 border-green-500/20", + warn: "bg-amber-500/10 text-amber-500 border-amber-500/20", + error: "bg-red-500/10 text-red-500 border-red-500/20", + running: "bg-blue-500/10 text-blue-500 border-blue-500/20", + muted: "bg-muted text-muted-foreground border-border", +} -export function PBSPanel() { +const TASK_ICONS: Record = { + backup: DatabaseBackup, reader: ArchiveRestore, verify: ShieldCheck, verificationjob: ShieldCheck, + verify_group: ShieldCheck, verify_snapshot: ShieldCheck, prune: Scissors, prunejob: Scissors, + garbage_collection: Trash2, sync: RefreshCw, syncjob: RefreshCw, aptupdate: Download, + termproxy: Terminal, dircreate: Database, "create-datastore": Database, +} + +// PBS reports "OK", "WARNINGS: n" or the error text; a task without one is still running. +function taskState(task: Task): keyof typeof badgeTone { + if (!task.status) return task.endtime ? "muted" : "running" + if (task.status === "OK") return "ok" + if (task.status.startsWith("WARNINGS")) return "warn" + return "error" +} + +function jobState(state?: string): keyof typeof badgeTone { + if (!state) return "muted" + if (state === "ok") return "ok" + if (state.toLowerCase().startsWith("warn")) return "warn" + return "error" +} + +function formatDuration(seconds: number) { + if (!Number.isFinite(seconds) || seconds < 0) return "—" + if (seconds < 60) return `${Math.round(seconds)}s` + const minutes = Math.floor(seconds / 60) + if (minutes < 60) return `${minutes}m ${Math.round(seconds % 60)}s` + const hours = Math.floor(minutes / 60) + return hours < 24 ? `${hours}h ${minutes % 60}m` : `${Math.floor(hours / 24)}d ${hours % 24}h` +} + +// `localOnly` is the PBS host's own Monitor: no connection picker, the +// header Refresh already reloads the page data. +export function PBSPanel({ localOnly = false }: { localOnly?: boolean } = {}) { const t = useT() const { data: connections, error: connectionError, mutate: reloadConnections } = useSWR( "/api/pbs/servers", url => fetchApi(url), { revalidateOnFocus: false }) @@ -69,36 +114,43 @@ export function PBSPanel() { } catch (err) { setError(err instanceof Error ? err.message : t("pbs.saveFailed")) } finally { setBusy(false) } } - const capacity = (value?: number | null) => value == null ? "—" : formatBytes(value) + const now = Date.now() / 1000 - return
-
-

{t("pbs.title")}

- - - -
+ return
+ {!localOnly &&
+

{t("pbs.title")}

+ + + +
} {(connectionError || statusError) &&

{String((connectionError || statusError)?.message || t("pbs.loadFailed"))}

} {isLoading &&

{t("pbs.loading")}

} {!selected && !connectionError && connections &&

{t("pbs.noServer")}

} - {manage && + {manage && !localOnly && {t("pbs.manage")}

{t("pbs.permissionsHelp")}

- +
{(["name", "url", "token_id", "token_secret", "fingerprint"] as const).map(field =>
@@ -118,32 +170,78 @@ export function PBSPanel() { } {data && <> -

{data.name} · PBS {data.version || "—"} · {t("pbs.sampled")} {new Date(data.fetched_at * 1000).toLocaleTimeString()}

- {Object.keys(data.services).length > 0 &&
- {Object.entries(data.services).map(([service, state]) => {service}: {state})} -
} +
+ {data.name} · PBS {data.version || "—"} · {t("pbs.sampled")} {new Date(data.fetched_at * 1000).toLocaleTimeString()} + {Object.entries(data.services).map(([service, state]) => + {service}: {state})} +
{Object.entries(data.errors).map(([section, detail]) =>

{t(`pbs.${section}`)}: {detail}

)} - {t("pbs.datastores")} - {data.datastores?.length === 0 &&

{t("pbs.noDatastores")}

} - {data.datastores && data.datastores.length > 0 &&
- - {data.datastores.map(row => )} -
{t("pbs.name")}{t("pbs.total")}{t("pbs.used")}{t("pbs.free")}
{row.name}{row.error &&

{row.error}

}
{capacity(row.total)}{capacity(row.used)}{capacity(row.avail)}
} -
- {t("pbs.tasks")} - {data.tasks?.length === 0 &&

{t("pbs.noTasks")}

} - {data.tasks && data.tasks.length > 0 &&
- - {data.tasks.map(row => )} -
{t("pbs.task")}{t("pbs.started")}{t("pbs.status")}
{row.worker_type} {row.worker_id}{row.starttime ? new Date(row.starttime * 1000).toLocaleString() : "—"}{row.status || (row.endtime ? "—" : t("pbs.running"))}
} -
- {t("pbs.jobs")} -
- - {(["sync", "verify", "prune"] as const).flatMap(kind => (data[kind] || []).map(row => - ))} -
{t("pbs.job")}{t("pbs.datastores")}{t("pbs.schedule")}{t("pbs.status")}
{t(`pbs.${kind}`)} · {row.id}{row.store || "—"}{row.schedule || "—"}{row["last-run-state"] || "—"}
-
+ + + {t("pbs.datastores")} + + {data.datastores?.length === 0 &&

{t("pbs.noDatastores")}

} + {data.datastores && data.datastores.length > 0 &&
+ {data.datastores.map(store => )} +
} +
+
+ + + {t("pbs.tasks")} + + {data.tasks?.length === 0 &&

{t("pbs.noTasks")}

} + {data.tasks && data.tasks.length > 0 &&
+ + + + + + + {data.tasks.map(row => { + const Icon = TASK_ICONS[row.worker_type || ""] || Activity + const state = taskState(row) + const end = row.endtime ?? (state === "running" ? now : undefined) + return + + + + + + })} +
{t("pbs.task")}{t("pbs.started")}{t("pbs.duration")}{t("pbs.status")}
+ + {row.worker_type} + {row.worker_id && {row.worker_id}} + {row.starttime ? new Date(row.starttime * 1000).toLocaleString() : "—"}{row.starttime && end ? formatDuration(end - row.starttime) : "—"} + {state === "running" ? t("pbs.running") : row.status || "—"} +
} +
+
+ + + {t("pbs.jobs")} + + {(() => { + const jobs = (["sync", "verify", "prune"] as const).flatMap(kind => (data[kind] || []).map(row => ({ kind, row }))) + if (jobs.length === 0) return

{t("pbs.noJobs")}

+ return
+ + + + + + + {jobs.map(({ kind, row }) => + + + + + )} +
{t("pbs.job")}{t("pbs.datastores")}{t("pbs.schedule")}{t("pbs.status")}
{t(`pbs.${kind}`)} · {row.id}{row.store || "—"}{row.schedule || "—"}{row["last-run-state"] || "—"}
+ })()} +
+
}
} diff --git a/AppImage/components/proxmox-dashboard.tsx b/AppImage/components/proxmox-dashboard.tsx index 679c9aaf..4b426082 100644 --- a/AppImage/components/proxmox-dashboard.tsx +++ b/AppImage/components/proxmox-dashboard.tsx @@ -18,7 +18,8 @@ import { Security } from "./security" import { Profile } from "./profile" import { About } from "./about" import { BackupTab } from "./backup-tab" -import { PBSDashboard } from "./pbs-dashboard" +import { PBSPanel } from "./pbs-panel" +import { MonitorSwitcher } from "./monitor-switcher" import { OnboardingCarousel } from "./onboarding-carousel" import { HealthStatusModal } from "./health-status-modal" import { ReleaseNotesModal, useVersionCheck } from "./release-notes-modal" @@ -46,7 +47,9 @@ import { ClipboardCheck, Info, DatabaseBackup, + Database, ChevronDown, + Clock, } from "lucide-react" import Image from "next/image" import { ThemeToggle } from "./theme-toggle" @@ -105,10 +108,27 @@ export function ProxmoxDashboard() { } function PVEDashboard() { + return +} + +function PBSDashboard() { + return +} + +type Product = "pve" | "pbs" +type TopTab = TabId | "datastores" + +// Proxmox Backup Server has no guests, apps or PVE storage: it keeps the +// same header, health and administration with its own top-level sections. +const PBS_TABS: TopTab[] = ["overview", "datastores", "terminal", "admin"] + +function MonitorDashboard({ product }: { product: Product }) { const t = useT() - useSWR("/api/apps/custom-links", _dashboardPrefetchFetcher, { revalidateOnFocus: false }) - useSWR("/api/apps/categories", _dashboardPrefetchFetcher, { revalidateOnFocus: false }) - const { order: tabOrder } = useTabOrder() + const isPbs = product === "pbs" + useSWR(isPbs ? null : "/api/apps/custom-links", _dashboardPrefetchFetcher, { revalidateOnFocus: false }) + useSWR(isPbs ? null : "/api/apps/categories", _dashboardPrefetchFetcher, { revalidateOnFocus: false }) + const { order: userTabOrder } = useTabOrder() + const tabOrder: TopTab[] = isPbs ? PBS_TABS : userTabOrder const [systemStatus, setSystemStatus] = useState({ status: "healthy", uptime: "Loading...", @@ -126,8 +146,8 @@ function PVEDashboard() { // runs post-hydration; the tiny flash is acceptable and matches the // pattern next-themes uses for the same reason. useEffect(() => { - setActiveTab(firstActualTab()) - }, []) + setActiveTab(isPbs ? "overview" : firstActualTab()) + }, [isPbs]) const [infoCount, setInfoCount] = useState(0) const [updateAvailable, setUpdateAvailable] = useState(false) const [showNavigation, setShowNavigation] = useState(true) @@ -400,6 +420,7 @@ function PVEDashboard() { const getActiveTabLabel = () => { switch (activeTab) { case "overview": return t("navigation.overview") + case "datastores": return t("pbs.datastores") case "apps": return t("navigation.apps") case "vms": return t("navigation.virtualMachines") case "storage": return t("navigation.storage") @@ -419,8 +440,9 @@ function PVEDashboard() { return (
- - setShowReleaseNotes(false)} /> + {/* Both describe the PVE Monitor. */} + {!isPbs && } + {!isPbs && setShowReleaseNotes(false)} />} {!isServerConnected && (
@@ -473,22 +495,17 @@ function PVEDashboard() {

{t("app.title")}

-

{t("app.description")}

+

{t(isPbs ? "app.descriptionPbs" : "app.description")}

- - {t("status.node", { node: displayServerName })} + + {t("status.uptime", { uptime: displayUptime })}
{/* Desktop Actions */}
-
- -
-
{t("status.node", { node: displayServerName })}
-
-
+
@@ -589,9 +606,7 @@ function PVEDashboard() { )}
- - {t("status.uptime", { uptime: displayUptime })} - +
@@ -632,7 +647,7 @@ function PVEDashboard() { { value: "audit", label: t("navigation.audit"), Icon: ClipboardCheck, default: false }, { value: "settings", label: t("navigation.settings"), Icon: SettingsIcon, default: false }, { value: "about", label: t("navigation.about"), Icon: Info, default: false }, - ] + ].filter(item => !isPbs || item.value !== "audit") const activeNodeItem = NODE_ITEMS.find(i => i.value === activeTab) const activeAdminItem = ADMIN_ITEMS.find(i => i.value === activeTab) const isNodeActive = activeNodeItem !== undefined @@ -698,9 +713,10 @@ function PVEDashboard() { ) - const renderTop = (id: TabId) => { + const renderTop = (id: TopTab) => { switch (id) { case "overview": return renderDirect("overview", LayoutDashboard, t("navigation.overview")) + case "datastores": return renderDirect("datastores", Database, t("pbs.datastores")) case "apps": return renderDirect("apps", Grid3x3, t("navigation.apps")) case "vms": return renderDirect("vms", Boxes, t("navigation.virtualMachines")) case "backup": return renderDirect("backup", DatabaseBackup, t("navigation.backup")) @@ -710,7 +726,7 @@ function PVEDashboard() { } } return ( - + {tabOrder.map(renderTop)} ) @@ -771,6 +787,7 @@ function PVEDashboard() { {tabOrder.map((id): React.ReactNode => { switch (id) { case "overview": return btn("overview", LayoutDashboard, t("navigation.overview")) + case "datastores": return btn("datastores", Database, t("pbs.datastores")) case "apps": return btn("apps", Grid3x3, t("navigation.apps")) case "vms": return btn("vms", Boxes, t("navigation.virtualMachines")) case "backup": return btn("backup", DatabaseBackup, t("navigation.backup")) @@ -786,7 +803,7 @@ function PVEDashboard() { {btn("logs", ScrollText, t("navigation.systemLogs"))} {btn("security", ShieldCheck, t("navigation.security"))} - {btn("audit", ClipboardCheck, t("navigation.audit"))} + {!isPbs && btn("audit", ClipboardCheck, t("navigation.audit"))} {btn("settings", SettingsIcon, t("navigation.settings"))} {btn("about", Info, t("navigation.about"))} @@ -820,9 +837,16 @@ function PVEDashboard() { 5 s / 59 s refresh intervals keep the data fresh in the background — reopening the tab is instant. */} - + + {isPbs && ( + + + + )} + + {!isPbs && <> @@ -848,11 +872,13 @@ function PVEDashboard() { + } + {!isPbs && <> @@ -860,13 +886,14 @@ function PVEDashboard() { + } - + {/* Profile tab — not surfaced in the top tabs nav. The only diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx index a49968f6..37e3edc8 100644 --- a/AppImage/components/settings.tsx +++ b/AppImage/components/settings.tsx @@ -16,6 +16,7 @@ import { Input } from "./ui/input" import { getNetworkUnit } from "../lib/format-network" import { fetchApi } from "../lib/api-config" import { SUPPORTED_LANGUAGES, useI18n } from "../lib/i18n/provider" +import { useIsPbs } from "../lib/use-platform" import type { LanguageCode } from "../lib/i18n/languages" import { NavTabOrderCard } from "./nav-tab-order-card" @@ -312,6 +313,7 @@ interface NetworkInterface { export function Settings() { const { language, setLanguage, t } = useI18n() + const isPbs = useIsPbs() const tFallback = (key: string, fallback: string) => { const translated = t(key) return translated === key ? fallback : translated @@ -1124,7 +1126,7 @@ export function Settings() {
{/* Navigation Tab Order — user-orderable top-level tabs */} - + {!isPbs && } {/* Network Units Settings */} @@ -1280,7 +1282,7 @@ export function Settings() { {/* Per-category rows */}
- {suppressionCategories.map((cat) => { + {suppressionCategories.filter(cat => !isPbs || healthCategoryKey(cat) !== "vms").map((cat) => { const IconComp = CATEGORY_ICONS[cat.icon] || HeartPulse const effectiveHours = getEffectiveHours(cat) const isCustomMode = effectiveHours === -2 || (cat.key in customValues) @@ -1288,7 +1290,8 @@ export function Settings() { const isLong = effectiveHours >= 720 && effectiveHours !== -1 && effectiveHours !== -2 const hasChanged = cat.key in pendingChanges && pendingChanges[cat.key] !== cat.hours const selectVal = isCustomMode ? "custom" : getSelectValue(effectiveHours, cat.key) - const catLabel = tFallback(`settings.healthMonitor.categories.${healthCategoryKey(cat)}`, cat.label) + const categoryKey = isPbs && healthCategoryKey(cat) === "services" ? "pbsServices" : healthCategoryKey(cat) + const catLabel = tFallback(`settings.healthMonitor.categories.${categoryKey}`, cat.label) return (
@@ -1496,6 +1499,7 @@ export function Settings() { + {!isPbs && <> {/* Remote Storage Exclusions */} @@ -1678,6 +1682,7 @@ export function Settings() { )} + } {/* Network Interface Exclusions */} @@ -1875,6 +1880,7 @@ export function Settings() { is re-enabled). */} + {!isPbs && <> {/* GitHub API — optional authentication for app upstream checks. */} @@ -1981,15 +1987,16 @@ export function Settings() { )} + } - {/* Notification Settings */} - + {/* Notification Settings (PVE only for now) */} + {!isPbs && } {/* Issue #195: snippets storage selector. Only renders when more than one storage advertises content=snippets — on a typical standalone host with just `local` there's nothing to choose, so showing an empty selector would be noise. */} - {snippetsCandidates.length > 1 && ( + {!isPbs && snippetsCandidates.length > 1 && (
diff --git a/AppImage/components/system-overview.tsx b/AppImage/components/system-overview.tsx index ab443d42..ecbeeef7 100644 --- a/AppImage/components/system-overview.tsx +++ b/AppImage/components/system-overview.tsx @@ -1,10 +1,12 @@ "use client" import { useState, useEffect } from "react" +import useSWR from "swr" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Progress } from "./ui/progress" import { Badge } from "./ui/badge" -import { Cpu, MemoryStick, Thermometer, Server, Zap, AlertCircle, HardDrive, Network, ChevronRight } from "lucide-react" +import { Cpu, MemoryStick, Thermometer, Server, Zap, AlertCircle, HardDrive, Network, ChevronRight, Database } from "lucide-react" +import { DatastoreUsageRow, type PBSDatastore } from "./pbs-datastore-usage" import { NodeMetricsCharts } from "./node-metrics-charts" import { NetworkTrafficChart } from "./network-traffic-chart" import { TemperatureDetailModal } from "./temperature-detail-modal" @@ -185,9 +187,17 @@ const getUnitsSettings = (): "Bytes" | "Bits" => { return raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes" } -export function SystemOverview() { +export function SystemOverview({ product = "pve" }: { product?: "pve" | "pbs" } = {}) { const t = useT() const { language } = useI18n() + const isPbs = product === "pbs" + // On Proxmox Backup Server the datastores take the place of guests and PVE storage. + const { data: pbsStatus, error: pbsError } = useSWR<{ datastores: PBSDatastore[] | null }>( + isPbs ? "/api/pbs/status?server=local" : null, + (url: string) => fetchApi<{ datastores: PBSDatastore[] | null }>(url), + { refreshInterval: 60000, revalidateOnFocus: false }, + ) + const datastores = pbsStatus?.datastores ?? null const [systemData, setSystemData] = useState(null) const [vmData, setVmData] = useState([]) const [storageData, setStorageData] = useState(null) @@ -212,8 +222,8 @@ export function SystemOverview() { const fetchAllData = async () => { const [systemResult, vmResult, storageResults, networkResult] = await Promise.all([ fetchSystemData().finally(() => setLoadingStates((prev) => ({ ...prev, system: false }))), - fetchVMData().finally(() => setLoadingStates((prev) => ({ ...prev, vms: false }))), - Promise.all([fetchStorageData(), fetchProxmoxStorageData()]).finally(() => + (isPbs ? Promise.resolve([]) : fetchVMData()).finally(() => setLoadingStates((prev) => ({ ...prev, vms: false }))), + Promise.all([fetchStorageData(), isPbs ? Promise.resolve(null) : fetchProxmoxStorageData()]).finally(() => setLoadingStates((prev) => ({ ...prev, storage: false })), ), fetchNetworkData().finally(() => setLoadingStates((prev) => ({ ...prev, network: false }))), @@ -248,12 +258,13 @@ export function SystemOverview() { }, 5000) const vmInterval = setInterval(async () => { + if (isPbs) return const data = await fetchVMData() setVmData(data) }, 59000) const storageInterval = setInterval(async () => { - const [storage, proxmoxStorage] = await Promise.all([fetchStorageData(), fetchProxmoxStorageData()]) + const [storage, proxmoxStorage] = await Promise.all([fetchStorageData(), isPbs ? Promise.resolve(null) : fetchProxmoxStorageData()]) if (storage) setStorageData(storage) if (proxmoxStorage) setProxmoxStorageData(proxmoxStorage) }, 59000) @@ -278,7 +289,7 @@ export function SystemOverview() { clearInterval(networkInterval) window.removeEventListener("networkUnitChanged" as any, handleUnitChange) } - }, [t]) + }, [t, isPbs]) if (!hasAttemptedLoad || loadingStates.system) { return ( @@ -527,7 +538,46 @@ export function SystemOverview() { - {/* ── Active VM & LXC (preview restyle v2: pills mismo tamaño que "X running") ── */} + {isPbs ? ( + + + {t("pbs.datastores")} + + + + {!datastores && !pbsError ? ( +
+
+
+
+ ) : !datastores ? ( +

{t("pbs.loadFailed")}

+ ) : datastores.length === 0 ? ( +

{t("pbs.noDatastores")}

+ ) : ( +
+
+ {datastores.length} + {datastores.some(store => store.error) ? ( + + {t("pbs.datastoresUnavailable", { count: datastores.filter(store => store.error).length })} + + ) : ( + + {t("pbs.datastoresOnline")} + + )} +
+ {datastores.slice(0, 2).map(store => )} + {datastores.length > 2 && ( +

{t("pbs.moreDatastores", { count: datastores.length - 2 })}

+ )} +
+ )} +
+
+ ) : ( + /* ── Active VM & LXC (preview restyle v2: pills mismo tamaño que "X running") ── */ {t("overview.activeVmLxc")} @@ -565,6 +615,7 @@ export function SystemOverview() { )} + )} 0 ? "cursor-pointer hover:bg-white/5 transition-colors" : ""}`} @@ -708,7 +759,16 @@ export function SystemOverview() {
- {vmLxcStorages && vmLxcStorages.length > 0 ? ( + {isPbs ? ( +
+
{t("pbs.datastores")}
+ {!datastores ? ( +
{pbsError ? t("pbs.loadFailed") : t("pbs.loading")}
+ ) : datastores.length === 0 ? ( +
{t("pbs.noDatastores")}
+ ) : datastores.map(store => )} +
+ ) : vmLxcStorages && vmLxcStorages.length > 0 ? (
{t("overview.vmLxcStorage")}
@@ -744,7 +804,7 @@ export function SystemOverview() {
)} - {localStorage && ( + {!isPbs && localStorage && (
{t("overview.localStorageSystem")}
@@ -899,7 +959,7 @@ export function SystemOverview() { {formatSystemUptime(systemData.uptime)}
- {t("overview.proxmoxVersion")} + {t(isPbs ? "overview.pbsVersion" : "overview.proxmoxVersion")} {systemData.proxmox_version || t("app.notAvailable")}
diff --git a/AppImage/lib/use-platform.ts b/AppImage/lib/use-platform.ts new file mode 100644 index 00000000..cbb99cc6 --- /dev/null +++ b/AppImage/lib/use-platform.ts @@ -0,0 +1,8 @@ +import useSWR from "swr" +import { fetchApi } from "./api-config" + +// Shares the dashboard's /api/platform request through the SWR cache. +export function useIsPbs(): boolean { + const { data } = useSWR<{ product: string }>("/api/platform", (url: string) => fetchApi<{ product: string }>(url), { revalidateOnFocus: false }) + return data?.product === "pbs" +} diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index fa596302..cc8d31f7 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -50,11 +50,20 @@ "navigation": "PBS navigation", "cpu": "CPU", "memory": "Memory", - "uptime": "Uptime" + "uptime": "Uptime", + "capacityUnavailable": "Capacity not available.", + "datastoresUnavailable": "{count} unavailable", + "datastoresOnline": "All available", + "moreDatastores": "+{count} more datastores", + "estimatedFull": "Estimated full", + "estimatedFullUnknown": "Not enough data yet", + "duration": "Duration", + "noJobs": "No sync, verification or prune schedules are configured." }, "app": { "title": "ProxMenux Monitor", "description": "Proxmox System Dashboard", + "descriptionPbs": "Proxmox Backup Server Dashboard", "loading": "Loading...", "connecting": "Connecting to ProxMenux Monitor", "unknown": "Unknown", @@ -195,6 +204,7 @@ "dayMany": "{count} days" }, "proxmoxVersion": "Proxmox Version:", + "pbsVersion": "PBS Version:", "kernel": "Kernel:", "availableUpdates": "Available Updates:", "packages": "packages", @@ -1880,6 +1890,7 @@ "network": "Network Interfaces", "vms": "VMs & Containers", "services": "PVE Services", + "pbsServices": "PBS Services", "logs": "System Logs", "updates": "System Updates", "security": "Security & Certificates", @@ -4874,6 +4885,7 @@ "network": "Network interfaces", "vms": "VMs & containers", "services": "PVE services", + "pbsServices": "PBS services", "logs": "System logs", "updates": "System updates", "security": "Security & certificates" @@ -6529,5 +6541,38 @@ "cancel": "Cancel" } }, - "runtime": {"notifications":{"templates":{"state_change":{"title":"{hostname}: {category} changed to {current}{entity_suffix}","body":"{category} status changed from {previous} to {current}.\n{reason}","label":"Health state changed"},"new_error":{"title":"{hostname}: New {severity} - {category}{entity_suffix}","body":"{reason}","label":"New health issue"},"error_resolved":{"title":"{hostname}: Resolved - {category}{entity_suffix}","body":"The {category} issue has been resolved.\n{reason}\n🚦 Previous severity: {original_severity}\n⏱️ Duration: {duration}","label":"Recovery notification"},"error_escalated":{"title":"{hostname}: Escalated to {severity} - {category}{entity_suffix}","body":"{reason}","label":"Health issue escalated"},"health_degraded":{"title":"{title_or_default}","body":"{reason}","label":"Health check degraded"},"lxc_updates_available":{"title":"{hostname}: {count} LXC(s) with package updates available","body":"📊 {count} LXC(s) with pending package updates (📦 {total_packages} total, 🔒 {security_count} security):\n\n{ct_list}","label":"LXC updates available (experimental)"},"lxc_update_applied":{"title":"{hostname}: LXC {ct_name} ({vmid}) update {result}","body":"{details}","label":"LXC update applied"},"oci_update_completed":{"title":"{hostname}: {app_name} updated","body":"{app_name} was updated to its new image and its data was kept.\nContainers: {containers}","label":"OCI application updated"},"oci_update_failed":{"title":"{hostname}: {app_name} update did not complete","body":"The update of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application update failed"},"oci_recreate_completed":{"title":"{hostname}: {app_name} recreated","body":"{app_name} was recreated with its saved configuration and its data was kept.\nContainers: {containers}","label":"OCI application recreated"},"oci_recreate_failed":{"title":"{hostname}: {app_name} recreation did not complete","body":"The recreation of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application recreation failed"},"oci_modify_completed":{"title":"{hostname}: {app_name} modified","body":"{app_name} was modified with its new options and its data was kept.\nContainers: {containers}","label":"OCI application modified"},"oci_modify_failed":{"title":"{hostname}: {app_name} modification did not complete","body":"The modification of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application modification failed"},"oci_watchdog_restarted":{"title":"{hostname}: {app_name} restarted after it stopped","body":"{app_name} stopped on its own and the watchdog started it again.\nContainers: {containers}","label":"OCI application restarted by the watchdog"},"oci_watchdog_failed":{"title":"{hostname}: {app_name} could not be kept running","body":"The watchdog could not keep {app_name} running: it stopped again or did not start after several attempts, and it is left stopped.\nContainers: {containers}\nStart it by hand once the cause is solved; the watchdog then watches it again.","label":"OCI application left stopped by the watchdog"},"app_update_available":{"title":"{hostname}: {app_name} update available on CT {vmid}","body":"{app_name} on CT {vmid} ({ct_name}) has a new version:\n {installed} → {latest}","label":"App update available"},"docker_stack_update_available":{"title":"{hostname}: Docker updates available on CT {vmid}","body":"Container {ct_name} (CT {vmid}) has {count} Docker update(s):\n{details}","label":"Docker updates available"},"vm_start":{"title":"{hostname}: VM {vmname} ({vmid}) started","body":"Virtual machine {vmname} (ID: {vmid}) is now running.","label":"VM started"},"vm_start_warning":{"title":"{hostname}: VM {vmname} ({vmid}) started with warnings","body":"Virtual machine {vmname} (ID: {vmid}) started successfully but has warnings.\nWarnings: {reason}","label":"VM started (warnings)"},"vm_stop":{"title":"{hostname}: VM {vmname} ({vmid}) stopped","body":"Virtual machine {vmname} (ID: {vmid}) has been stopped.","label":"VM stopped"},"vm_shutdown":{"title":"{hostname}: VM {vmname} ({vmid}) shut down","body":"Virtual machine {vmname} (ID: {vmid}) has been cleanly shut down.","label":"VM shutdown"},"vm_fail":{"title":"{hostname}: VM {vmname} ({vmid}) FAILED","body":"Virtual machine {vmname} (ID: {vmid}) has crashed or failed to start.\nReason: {reason}","label":"VM FAILED"},"vm_restart":{"title":"{hostname}: VM {vmname} ({vmid}) restarted","body":"Virtual machine {vmname} (ID: {vmid}) has been restarted.","label":"VM restarted"},"ct_start":{"title":"{hostname}: CT {vmname} ({vmid}) started","body":"Container {vmname} (ID: {vmid}) is now running.","label":"CT started"},"ct_start_warning":{"title":"{hostname}: CT {vmname} ({vmid}) started with warnings","body":"Container {vmname} (ID: {vmid}) started successfully but has warnings.\nWarnings: {reason}","label":"CT started (warnings)"},"ct_stop":{"title":"{hostname}: CT {vmname} ({vmid}) stopped","body":"Container {vmname} (ID: {vmid}) has been stopped.","label":"CT stopped"},"ct_shutdown":{"title":"{hostname}: CT {vmname} ({vmid}) shut down","body":"Container {vmname} (ID: {vmid}) has been cleanly shut down.","label":"CT shutdown"},"ct_restart":{"title":"{hostname}: CT {vmname} ({vmid}) restarted","body":"Container {vmname} (ID: {vmid}) has been restarted.","label":"CT restarted"},"ct_fail":{"title":"{hostname}: CT {vmname} ({vmid}) FAILED","body":"Container {vmname} (ID: {vmid}) has crashed or failed to start.\nReason: {reason}","label":"CT FAILED"},"migration_start":{"title":"{hostname}: Migration started — {vmname} ({vmid})","body":"Live migration of {vmname} (ID: {vmid}) to node {target_node} has started.","label":"Migration started"},"migration_complete":{"title":"{hostname}: Migration complete — {vmname} ({vmid})","body":"{vmname} (ID: {vmid}) successfully migrated to node {target_node}.","label":"Migration complete"},"migration_warning":{"title":"{hostname}: Migration complete with warnings — {vmname} ({vmid})","body":"{vmname} (ID: {vmid}) migrated to node {target_node} but encountered warnings.\nWarnings: {reason}","label":"Migration (warnings)"},"migration_fail":{"title":"{hostname}: Migration FAILED — {vmname} ({vmid})","body":"Migration of {vmname} (ID: {vmid}) to node {target_node} failed.\nReason: {reason}","label":"Migration FAILED"},"replication_fail":{"title":"{hostname}: Replication FAILED — {vmname} ({vmid})","body":"Replication of {vmname} (ID: {vmid}) failed.\nReason: {reason}","label":"Replication FAILED"},"replication_complete":{"title":"{hostname}: Replication complete — {vmname} ({vmid})","body":"Replication of {vmname} (ID: {vmid}) completed successfully.","label":"Replication complete"},"backup_start":{"title":"{hostname}: Backup started on {storage}","body":"Backup job started on storage {storage}.\n{reason}","label":"Backup started"},"backup_complete":{"title":"{hostname} → {storage}: Backup complete — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) completed successfully on {storage}.\nSize: {size}","label":"Backup complete"},"backup_warning":{"title":"{hostname} → {storage}: Backup complete with warnings — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) on {storage} completed but encountered warnings.\nWarnings: {reason}","label":"Backup (warnings)"},"backup_fail":{"title":"{hostname} → {storage}: Backup FAILED — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) failed on {storage}.\nReason: {reason}","label":"Backup FAILED"},"host_backup_start":{"title":"{hostname}: Host backup started → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nProfile: {profile_mode}","label":"Host backup started"},"host_backup_complete":{"title":"{hostname}: Host backup complete → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nData size: {data_size}\nArchive size: {archive_size}\nDuration: {duration}","label":"Host backup complete"},"host_backup_fail":{"title":"{hostname}: Host backup FAILED → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nDuration before failure: {duration}\nReason: {reason}\nLog: {log_file}","label":"Host backup FAILED"},"snapshot_complete":{"title":"{hostname}: Snapshot created — {vmname} ({vmid})","body":"Snapshot \"{snapshot_name}\" created for {vmname} (ID: {vmid}).","label":"Snapshot created"},"snapshot_fail":{"title":"{hostname}: Snapshot FAILED — {vmname} ({vmid})","body":"Snapshot creation for {vmname} (ID: {vmid}) failed.\nReason: {reason}","label":"Snapshot FAILED"},"cpu_high":{"title":"{hostname}: High CPU usage — {value}%","body":"CPU usage has reached {value}% on {cores} cores.\n{details}","label":"High CPU usage"},"ram_high":{"title":"{hostname}: High memory usage — {value}%","body":"Memory usage: {used} / {total} ({value}%).\n{details}","label":"High memory usage"},"temp_high":{"title":"{hostname}: High sensor temperature — {value}°C","body":"Sensor temperature has reached {value}°C (threshold: {threshold}°C).\n{details}","label":"High temperature"},"disk_space_low":{"title":"{hostname}: Low disk space on {mount}","body":"Filesystem {mount}: {used}% used ({available} available).\nFree up disk space to avoid service disruption.","label":"Low disk space"},"disk_io_error":{"title":"{hostname}: Disk failure detected — {device}","body":"I/O error or disk failure detected on device {device}.\n{reason}","label":"Disk failure / I/O error"},"storage_unavailable":{"title":"{hostname}: Storage unavailable — {storage_name}","body":"PVE storage \"{storage_name}\" (type: {storage_type}) is not accessible.\nReason: {reason}","label":"Storage unavailable"},"smart_test_complete":{"title":"{hostname}: SMART test completed — {device}","body":"SMART {test_type} test on /dev/{device} has completed.\nResult: {result}\nDuration: {duration}","label":"SMART test completed"},"smart_test_failed":{"title":"{hostname}: SMART test FAILED — {device}","body":"SMART {test_type} test on /dev/{device} has failed.\nResult: {result}\nReason: {reason}","label":"SMART test FAILED"},"gpu_mode_switch":{"title":"{hostname}: GPU mode changed to {new_mode}","body":"GPU passthrough mode has been switched.\nGPU: {gpu_name} ({gpu_pci})\nPrevious mode: {old_mode}\nNew mode: {new_mode}\n{details}","label":"GPU mode switched"},"gpu_passthrough_blocked":{"title":"{hostname}: {guest_type} {guest_id} blocked at startup","body":"PCIe passthrough guard prevented {guest_type} {guest_id} ({guest_name}) from starting.\nReason: {reason}\n{details}","label":"GPU passthrough blocked"},"pci_passthrough_conflict":{"title":"{hostname}: PCIe device conflict detected — {device_pci}","body":"A PCIe device is assigned to multiple guests.\nDevice: {device_pci}\nConflicting guests: {guest_list}\nAction required: Stop one of the guests or reassign the device.","label":"PCIe device conflict"},"load_high":{"title":"{hostname}: High system load — {value}","body":"System load average is {value} on {cores} cores.\n{details}","label":"High system load"},"network_down":{"title":"{hostname}: Network connectivity lost{entity_suffix}","body":"The node has lost network connectivity.\nReason: {reason}","label":"Network connectivity lost"},"network_latency":{"title":"{hostname}: High network latency — {value}ms","body":"Latency to gateway: {value}ms (threshold: {threshold}ms).\nThis may indicate network congestion or hardware issues.","label":"High network latency"},"auth_fail":{"title":"{hostname}: Authentication failure","body":"Failed login attempt detected.\nSource IP: {source_ip}\nUser: {username}\nService: {service}","label":"Authentication failure"},"ip_block":{"title":"{hostname}: IP blocked by Fail2Ban","body":"IP address {source_ip} has been banned.\nJail: {jail}\nFailed attempts: {failures}","label":"IP blocked by Fail2Ban"},"firewall_issue":{"title":"{hostname}: Firewall issue detected{entity_suffix}","body":"A firewall configuration issue has been detected.\nReason: {reason}","label":"Firewall issue detected"},"user_permission_change":{"title":"{hostname}: User permission changed","body":"User: {username}\nChange: {change_details}","label":"User permission changed"},"split_brain":{"title":"{hostname}: Cluster event reported","body":"A cluster event was reported:\n{reason}","label":"Cluster event"},"node_disconnect":{"title":"{hostname}: Node {node_name} disconnected","body":"Node {node_name} has disconnected from the cluster.","label":"Node disconnected"},"node_reconnect":{"title":"{hostname}: Node {node_name} reconnected","body":"Node {node_name} has rejoined the cluster successfully.","label":"Node reconnected"},"system_startup":{"title":"{hostname}: {reason}","body":"{summary}","label":"System startup report"},"system_shutdown":{"title":"{hostname}: System shutting down","body":"The node is shutting down.\n{reason}","label":"System shutting down"},"system_reboot":{"title":"{hostname}: System rebooting","body":"The node is rebooting.\n{reason}","label":"System rebooting"},"system_restore_completed":{"title":"{hostname}: Host restore finished","body":"Post-restore tasks completed in background.\n\nGuest configurations copied: {guests}\nBind-mount stub directories created: {stubs}\nStale node dirs removed: {stale_nodes}\nComponents reinstalled: {components}\nDuration: {duration}\n{warnings_block}","label":"Host restore completed"},"system_problem":{"title":"{hostname}: System problem detected{entity_suffix}","body":"A system-level problem has been detected.\nReason: {reason}","label":"System problem detected"},"service_fail":{"title":"{hostname}: Service failed — {service_name}","body":"System service \"{service_name}\" has failed.\nReason: {reason}","label":"Service failed"},"oom_kill":{"title":"{hostname}: OOM Kill — {process}","body":"Process \"{process}\" was killed by the Out-of-Memory manager.\n{reason}","label":"Out of memory kill"},"service_fail_batch":{"title":"{hostname}: {service_count} services failed{entity_suffix}","body":"{reason}","label":"Service fail batch"},"cron_output":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Cron job output (per-cron stdout via mail)"},"system_mail":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Smartd / mail bounces (PVE system mail)"},"apt_listchanges":{"title":"{hostname}: {pve_title}","body":"Upstream package information forwarded by Proxmox VE through apt-listchanges. The following text comes from the package maintainer and is not a ProxMenux recommendation.\n\n{reason}","label":"apt-listchanges package notices"},"webhook_test":{"title":"{hostname}: Webhook test received","body":"PVE webhook connectivity test successful.\n{reason}","label":"Webhook test"},"update_available":{"title":"{hostname}: Updates available","body":"Total updates: {total_count}\nSecurity: {security_count}\nProxmox: {pve_count}\nKernel: {kernel_count}\nImportant packages:\n{important_list}","label":"Updates available (legacy)"},"unknown_persistent":{"title":"{hostname}: Check unavailable - {category}{entity_suffix}","body":"Health check for {category} has been unavailable for 3+ cycles.\n{reason}","label":"Check unavailable"},"health_persistent":{"title":"{hostname}: {count} active health issue(s){entity_suffix}","body":"The following health issues remain unresolved:\n{issue_list}\n\nThis digest is sent once every 24 hours while issues persist.","label":"Active health issues (daily)"},"health_issue_new":{"title":"{hostname}: New health issue — {category}{entity_suffix}","body":"New {severity} issue detected in: {category}\nDetails: {reason}","label":"New health issue"},"health_issue_resolved":{"title":"{hostname}: Resolved - {category}{entity_suffix}","body":"{category} issue has been resolved.\n{reason}\nDuration: {duration}","label":"Health issue resolved"},"update_summary":{"title":"{hostname}: Updates available","body":"Total updates: {total_count}\nSecurity updates: {security_count}\nProxmox-related updates: {pve_count}\nKernel updates: {kernel_count}\nImportant packages:\n{important_list}","label":"Host package updates"},"pve_update":{"title":"{hostname}: Proxmox VE {new_version} available","body":"A new Proxmox VE release is available.\nCurrent: {current_version}\nNew: {new_version}\n{details}","label":"Proxmox VE update available"},"update_complete":{"title":"{hostname}: System update completed","body":"System packages have been successfully updated.\n{details}","label":"Host update completed"},"ai_model_migrated":{"title":"{hostname}: AI model updated","body":"The AI model for notifications has been automatically updated.\nProvider: {provider}\nPrevious model: {old_model}\nNew model: {new_model}\n\n{message}","label":"AI model auto-updated"},"proxmenux_update":{"title":"{hostname}: ProxMenux {new_version} available","body":"A new version of ProxMenux is available.\nCurrent: {current_version}\nNew: {new_version}","label":"ProxMenux update available"},"mount_stale":{"title":"{hostname}: stale remote mount {mount_target}","body":"Remote mount {mount_target} ({fstype}) from {mount_source} is stale{lxc_scope}.\nStat timed out or returned an error: {error}\n\nApps writing to this path will silently land on the underlying filesystem and may fill the disk. Remount or fix connectivity ASAP.","label":"Remote mount stale"},"mount_readonly":{"title":"{hostname}: remote mount {mount_target} is read-only","body":"Remote mount {mount_target} ({fstype}) from {mount_source} is mounted read-only{lxc_scope}. Writes will fail. If this was unintentional, remount with rw.","label":"Remote mount read-only"},"lxc_disk_low":{"title":"{hostname}: CT {vmid} rootfs at {usage_percent}%","body":"CT {vmid} ({name}) rootfs is at {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nA full LXC rootfs prevents the container from booting cleanly. Either expand the rootfs (pct resize {vmid} rootfs +1G) or free space inside the container.","label":"LXC rootfs near full"},"vm_disk_low":{"title":"{hostname}: VM {vmid} filesystems at {usage_percent}%","body":"VM {vmid} ({name}) guest filesystems are at {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nReported by the QEMU guest agent. Includes every persistent filesystem the guest mounts on a block device — virtual disks and PCI-passthrough drives alike. Free up space inside the guest or expand the affected storage before writes start to fail.","label":"VM filesystems near full"},"lxc_mount_low":{"title":"{hostname}: CT {vmid} mount {mount} at {usage_percent}%","body":"Mount {mount} inside CT {vmid} ({name}) is at {usage_percent}% used.\nFilesystem type: {fstype}\n\nA full mount inside a container often blocks the application silently — writes either fail or, worse, land on the rootfs and trigger the rootfs alert next. Free up space on the mount or expand it.","label":"LXC mount near full"},"pve_storage_full":{"title":"{hostname}: PVE storage {storage_name} at {usage_percent}%","body":"Proxmox storage \"{storage_name}\" (type: {storage_type}) is at {usage_percent}% used.\n\nOnce full, no new VM/CT can be provisioned and existing guests may fail to write. Move/delete unused volumes or expand the underlying pool/LV/RBD image.","label":"PVE storage near full"},"zfs_pool_full":{"title":"{hostname}: ZFS pool {pool_name} at {usage_percent}%","body":"ZFS pool \"{pool_name}\" is at {usage_percent}% capacity.\n\nZFS performance and write reliability degrade sharply above ~80% capacity (CoW needs free space for new blocks). Free up snapshots, prune old datasets, or add more vdevs to the pool.","label":"ZFS pool near full"},"post_install_update":{"title":"{hostname}: {count} ProxMenux optimization update(s) available","body":"{count} ProxMenux optimization update(s) available on this host.\n\n🛠️ Available versions:\n{tool_list}\n\n💡 Apply from:\n • ProxMenux Monitor → Settings → ProxMenux Optimizations\n • Or open the ProxMenux menu → Settings post-install Proxmox → \"Apply available updates\"","label":"ProxMenux optimization updates available"},"secure_gateway_update_available":{"title":"{hostname}: {app_name} update available{update_title_suffix}","body":"{app_name} (managed by ProxMenux) has 📦 {package_count} package update(s) pending in its container.\n{version_line}\n\n💡 Open ProxMenux Monitor > Security > Secure Gateway and click \"Update\" to apply.\n\n🗂️ Packages:\n{package_list}","label":"Secure Gateway update available"},"nvidia_driver_update_available":{"title":"{hostname}: NVIDIA driver update available — v{latest_version}","body":"A newer maintenance release is available for the installed NVIDIA driver branch.\n🔹 Currently installed: v{current_version}\n🟢 Latest available: v{latest_version}\n\n{upgrade_reason}\n\n💡 To reinstall:\n • From the ProxMenux post-install menu: {menu_label}\n\nReinstalling rebuilds the DKMS module against the running kernel and requires a reboot to load the new driver.","label":"NVIDIA driver update available"},"coral_driver_update_available":{"title":"{hostname}: Coral TPU driver update available — {latest_version}","body":"A newer {variant_label} is available.\n🔹 Currently installed: {current_version}\n🟢 Latest available: {latest_version}\n\n{upgrade_reason}\n\n💡 To reinstall:\n • From the ProxMenux post-install menu: {menu_label}\n\n{reboot_note}","label":"Coral TPU driver update available"},"burst_auth_fail":{"title":"{hostname}: +{count} more auth failures in {window}","body":"+{count} additional authentication failures detected in {window} ({total_count} total).\nSources: {entity_list}","label":"Auth failures burst"},"burst_ip_block":{"title":"{hostname}: Fail2Ban banned +{count} more IPs in {window}","body":"+{count} additional IPs banned by Fail2Ban in {window} ({total_count} total).\nIPs: {entity_list}","label":"IP block burst"},"burst_disk_io":{"title":"{hostname}: +{count} more disk I/O errors on {entity_list}","body":"+{count} additional I/O errors detected in {window} ({total_count} total).\nDevices: {entity_list}","label":"Disk I/O burst"},"burst_cluster":{"title":"{hostname}: Cluster flapping detected (+{count} more changes)","body":"Cluster state changed +{count} more times in {window} ({total_count} total).\nNodes: {entity_list}","label":"Cluster flapping burst"},"burst_service_fail":{"title":"{hostname}: +{count} more services failed in {window}","body":"+{count} additional service failures detected in {window} ({total_count} total).\nThis typically indicates a node reboot or PVE service restart.\n\nAdditional failures:\n{details}","label":"Service fail burst"},"burst_system":{"title":"{hostname}: +{count} more system problems in {window}","body":"+{count} additional system problems detected in {window} ({total_count} total).\n\nAdditional issues:\n{details}","label":"System problems burst"},"burst_generic":{"title":"{hostname}: +{count} more {event_type} events in {window}","body":"+{count} additional events of type {event_type} in {window} ({total_count} total).\n\nAdditional events:\n{details}","label":"Generic burst"},"kernel_warning":{"title":"{hostname}: Kernel diagnostic event detected","body":"The kernel recorded a diagnostic event: {kernel_details}","label":"Kernel warnings and diagnostic traces"}},"lxcUpdate":{"status":{"success":"succeeded","failure":"failed","partial":"completed partially","deferred":"deferred","skipped":"skipped"},"source":{"scheduled":"Scheduled","manual":"Manual"},"sourceLabel":"Source","targets":"Targets","osPending":"OS packages pending: {before} → {after}","osUnverified":"OS packages: update command executed; result not verified","applications":"Applications: {items}","applicationsUnverified":"Applications: updater executed; no tracked version change was observed","dockerEngineChange":"Docker Engine: {before} → {after}","dockerEngineVerified":"Docker Engine: verified at {version}","dockerEngineUnverified":"Docker Engine: update command executed; version not verified","dockerImagesPending":"Docker images pending: {before} → {after}","dockerImagesChanged":"Docker images changed: {images}","deferredTargets":"Deferred targets: {targets}","reason":"Reason: {reason}","restartRequired":"Restart required: {value}","yes":"yes","no":"no","verificationPending":"Verification pending until the container is running","verificationWarning":"Verification warning: {error}","duration":"Duration: {duration}"},"fallback":{"unknownTitle":"{hostname}: {event_type}","healthCheckDegraded":"Health check degraded","none":"none","temperatureAlertTitle":"{hostname}: Sensor temperature alert","temperatureAlertBody":"Temperature alert recorded without a complete measurement payload.","recordedReason":"Recorded reason: {reason}","recordedDetails":"Recorded details: {details}"},"fields":{"vmid":"VM/CT","name":"Name","device":"Device","sourceIp":"Source IP","node":"Node","category":"Category","service":"Service","jail":"Jail","user":"User","count":"Count","window":"Window","affected":"Affected"},"vzdump":{"size":"Size: {value}","duration":"Duration: {value}","file":"File","backups":"{count} backups","failed":"{count} failed","total":"Total: {value}","time":"Time: {value}"},"startup":{"issuesTitle":"{hostname}: System startup - {count} issue(s) detected","completeTitle":"{hostname}: System startup completed","operational":"All systems operational.","vmCountOne":"{count} VM","vmCountMany":"{count} VMs","ctCountOne":"{count} CT","ctCountMany":"{count} CTs","started":"✅ {counts} started","vmFailed":"❌ VM failed: {name} - {reason}","ctFailed":"❌ CT failed: {name} - {reason}","unknownError":"unknown error","storageUnavailable":"⚠️ Storage: {count} unavailable ({names})","servicesFailed":"⚠️ Services: {count} failed ({names})","duration":"⏱️ Startup completed in {minutes} min"},"appUpdates":{"singleTitle":"{hostname}: {app_name} update available on CT {vmid}","singleBody":"{app_name} on CT {vmid} ({ct_name}) has a new version:\n {installed} → {latest}","emptyTitle":"{hostname}: Application updates available","emptyBody":"Application updates are available.","batchTitle":"{hostname}: {count} application updates available","batchLeadOneContainer":"{count} applications in {container_count} LXC container have a newer version:","batchLeadManyContainers":"{count} applications in {container_count} LXC containers have a newer version:","additional":"… {count} additional application(s)","app":"app","unknown":"unknown"},"healthDegraded":{"categories":{"cpu":"CPU usage and temperature","memory":"Memory and swap","storage":"Storage mounts and space","disks":"Disk I/O and errors","network":"Network interfaces","vms":"VMs and containers","services":"PVE services","logs":"System logs","updates":"System updates","security":"Security"},"severity":{"critical":"Critical","warning":"Warning"},"singleTitle":"{hostname}: {severity} health status – {category}","multipleTitle":"{hostname}: {count} health checks degraded","multipleLine":"• {severity}: {category}\n {reason}","reasons":{"cpuSustained":"CPU is above {threshold}% for {duration}s."}},"digest":{"title":"{hostname}: 24h summary ({timestamp})","readFailed":"digest read failed: {error}","empty":"No INFO events buffered for this digest window.","lead":"{count} INFO events grouped by category:\n","more":" • … and {count} more","footer":"(Critical/Warning events arrived at the time they happened, not in this digest.)","quietTitle":"{hostname}: {count} events buffered during Quiet Hours","groups":{"vm_ct":"VM a CT","backup":"Backup","resources":"Resources","storage":"Storage","network":"Network","security":"Security","cluster":"Cluster","services":"Services","health":"Health","updates":"Updates","hardware":"Hardware","system":"System","other":"Other"}},"test":{"title":"ProxMenux Test","welcome":"Welcome to ProxMenux Monitor!","verify":"This is a test message to verify your notification channel is working correctly.","configuration":"Channel configuration:","iconsEnabled":"Icons: enabled","iconsDisabled":"Icons: disabled","aiEnabled":"AI: enabled ({info})","aiDisabled":"AI: disabled","alerts":"You will receive alerts about system events, health status changes, and security incidents.","photoCaption":"You can use this image as the profile photo for your notification bot."},"channels":{"discord":{"category":"Category","host":"Host","severity":"Severity"},"email":{"report":"{group} Report","details":"Details","host":"Host","footer":"ProxMenux Notification Service","severity":{"critical":"Critical","warning":"Warning","info":"Information","ok":"Resolved","default":"Notice"},"groups":{"vm_ct":"Virtual Machine / Container","backup":"Backup & Snapshot","resources":"System Resources","storage":"Storage","network":"Network","security":"Security","cluster":"Cluster","services":"System Services","health":"Health Monitor","updates":"System Updates","system":"System","hardware":"Hardware","other":"System Notification"},"fields":{"vmCtId":"VM/CT ID","name":"Name","action":"Action","targetNode":"Target Node","reason":"Reason","storage":"Storage","status":"Status","size":"Size","duration":"Duration","snapshot":"Snapshot","metric":"Metric","currentValue":"Current Value","threshold":"Threshold","cpuCores":"CPU Cores","memory":"Memory","temperature":"Temperature","mountPoint":"Mount Point","usage":"Usage","available":"Available","device":"Device","severity":"Severity","storageName":"Storage Name","type":"Type","interface":"Interface","latency":"Latency","event":"Event","sourceIp":"Source IP","username":"Username","service":"Service","jail":"Jail","failures":"Failures","change":"Change","node":"Node","quorum":"Quorum","nodesAffected":"Nodes Affected","process":"Process","category":"Category","previousSeverity":"Previous Severity","activeIssues":"Active Issues","totalUpdates":"Total Updates","securityUpdates":"Security Updates","proxmoxUpdates":"Proxmox Updates","kernelUpdates":"Kernel Updates","importantPackages":"Important Packages","currentVersion":"Current Version","newVersion":"New Version"},"status":{"failed":"Failed","completed":"Completed","started":"Started","unconfirmed":"Unconfirmed","completed_with_warnings":"Completed with warnings"}}},"temperature":{"sampleSpan":"High samples span {duration}."},"backup":{"unconfirmedTitle":"{hostname}: Backup outcome unconfirmed","confirmedTitle":"{hostname}: Backup complete","confirmedBody":"Backup completed successfully.","errorTitle":"{hostname}: Backup error reported","errorBody":"The backup report contains an error.","unconfirmedBody":"The backup outcome is not confirmed.","warningTitle":"{hostname}: Backup completed with warnings","warningBody":"Backup completed with warnings.","diagnosticsOmitted":"Additional diagnostic lines or text omitted: {count}. Original report retained."}}} + "runtime": {"notifications":{"templates":{"state_change":{"title":"{hostname}: {category} changed to {current}{entity_suffix}","body":"{category} status changed from {previous} to {current}.\n{reason}","label":"Health state changed"},"new_error":{"title":"{hostname}: New {severity} - {category}{entity_suffix}","body":"{reason}","label":"New health issue"},"error_resolved":{"title":"{hostname}: Resolved - {category}{entity_suffix}","body":"The {category} issue has been resolved.\n{reason}\n🚦 Previous severity: {original_severity}\n⏱️ Duration: {duration}","label":"Recovery notification"},"error_escalated":{"title":"{hostname}: Escalated to {severity} - {category}{entity_suffix}","body":"{reason}","label":"Health issue escalated"},"health_degraded":{"title":"{title_or_default}","body":"{reason}","label":"Health check degraded"},"lxc_updates_available":{"title":"{hostname}: {count} LXC(s) with package updates available","body":"📊 {count} LXC(s) with pending package updates (📦 {total_packages} total, 🔒 {security_count} security):\n\n{ct_list}","label":"LXC updates available (experimental)"},"lxc_update_applied":{"title":"{hostname}: LXC {ct_name} ({vmid}) update {result}","body":"{details}","label":"LXC update applied"},"oci_update_completed":{"title":"{hostname}: {app_name} updated","body":"{app_name} was updated to its new image and its data was kept.\nContainers: {containers}","label":"OCI application updated"},"oci_update_failed":{"title":"{hostname}: {app_name} update did not complete","body":"The update of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application update failed"},"oci_recreate_completed":{"title":"{hostname}: {app_name} recreated","body":"{app_name} was recreated with its saved configuration and its data was kept.\nContainers: {containers}","label":"OCI application recreated"},"oci_recreate_failed":{"title":"{hostname}: {app_name} recreation did not complete","body":"The recreation of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application recreation failed"},"oci_modify_completed":{"title":"{hostname}: {app_name} modified","body":"{app_name} was modified with its new options and its data was kept.\nContainers: {containers}","label":"OCI application modified"},"oci_modify_failed":{"title":"{hostname}: {app_name} modification did not complete","body":"The modification of {app_name} did not complete.\nReason: {reason}\nContainers: {containers}\nOpen Manage installed OCI applications to check its state.","label":"OCI application modification failed"},"oci_watchdog_restarted":{"title":"{hostname}: {app_name} restarted after it stopped","body":"{app_name} stopped on its own and the watchdog started it again.\nContainers: {containers}","label":"OCI application restarted by the watchdog"},"oci_watchdog_failed":{"title":"{hostname}: {app_name} could not be kept running","body":"The watchdog could not keep {app_name} running: it stopped again or did not start after several attempts, and it is left stopped.\nContainers: {containers}\nStart it by hand once the cause is solved; the watchdog then watches it again.","label":"OCI application left stopped by the watchdog"},"app_update_available":{"title":"{hostname}: {app_name} update available on CT {vmid}","body":"{app_name} on CT {vmid} ({ct_name}) has a new version:\n {installed} → {latest}","label":"App update available"},"docker_stack_update_available":{"title":"{hostname}: Docker updates available on CT {vmid}","body":"Container {ct_name} (CT {vmid}) has {count} Docker update(s):\n{details}","label":"Docker updates available"},"vm_start":{"title":"{hostname}: VM {vmname} ({vmid}) started","body":"Virtual machine {vmname} (ID: {vmid}) is now running.","label":"VM started"},"vm_start_warning":{"title":"{hostname}: VM {vmname} ({vmid}) started with warnings","body":"Virtual machine {vmname} (ID: {vmid}) started successfully but has warnings.\nWarnings: {reason}","label":"VM started (warnings)"},"vm_stop":{"title":"{hostname}: VM {vmname} ({vmid}) stopped","body":"Virtual machine {vmname} (ID: {vmid}) has been stopped.","label":"VM stopped"},"vm_shutdown":{"title":"{hostname}: VM {vmname} ({vmid}) shut down","body":"Virtual machine {vmname} (ID: {vmid}) has been cleanly shut down.","label":"VM shutdown"},"vm_fail":{"title":"{hostname}: VM {vmname} ({vmid}) FAILED","body":"Virtual machine {vmname} (ID: {vmid}) has crashed or failed to start.\nReason: {reason}","label":"VM FAILED"},"vm_restart":{"title":"{hostname}: VM {vmname} ({vmid}) restarted","body":"Virtual machine {vmname} (ID: {vmid}) has been restarted.","label":"VM restarted"},"ct_start":{"title":"{hostname}: CT {vmname} ({vmid}) started","body":"Container {vmname} (ID: {vmid}) is now running.","label":"CT started"},"ct_start_warning":{"title":"{hostname}: CT {vmname} ({vmid}) started with warnings","body":"Container {vmname} (ID: {vmid}) started successfully but has warnings.\nWarnings: {reason}","label":"CT started (warnings)"},"ct_stop":{"title":"{hostname}: CT {vmname} ({vmid}) stopped","body":"Container {vmname} (ID: {vmid}) has been stopped.","label":"CT stopped"},"ct_shutdown":{"title":"{hostname}: CT {vmname} ({vmid}) shut down","body":"Container {vmname} (ID: {vmid}) has been cleanly shut down.","label":"CT shutdown"},"ct_restart":{"title":"{hostname}: CT {vmname} ({vmid}) restarted","body":"Container {vmname} (ID: {vmid}) has been restarted.","label":"CT restarted"},"ct_fail":{"title":"{hostname}: CT {vmname} ({vmid}) FAILED","body":"Container {vmname} (ID: {vmid}) has crashed or failed to start.\nReason: {reason}","label":"CT FAILED"},"migration_start":{"title":"{hostname}: Migration started — {vmname} ({vmid})","body":"Live migration of {vmname} (ID: {vmid}) to node {target_node} has started.","label":"Migration started"},"migration_complete":{"title":"{hostname}: Migration complete — {vmname} ({vmid})","body":"{vmname} (ID: {vmid}) successfully migrated to node {target_node}.","label":"Migration complete"},"migration_warning":{"title":"{hostname}: Migration complete with warnings — {vmname} ({vmid})","body":"{vmname} (ID: {vmid}) migrated to node {target_node} but encountered warnings.\nWarnings: {reason}","label":"Migration (warnings)"},"migration_fail":{"title":"{hostname}: Migration FAILED — {vmname} ({vmid})","body":"Migration of {vmname} (ID: {vmid}) to node {target_node} failed.\nReason: {reason}","label":"Migration FAILED"},"replication_fail":{"title":"{hostname}: Replication FAILED — {vmname} ({vmid})","body":"Replication of {vmname} (ID: {vmid}) failed.\nReason: {reason}","label":"Replication FAILED"},"replication_complete":{"title":"{hostname}: Replication complete — {vmname} ({vmid})","body":"Replication of {vmname} (ID: {vmid}) completed successfully.","label":"Replication complete"},"backup_start":{"title":"{hostname}: Backup started on {storage}","body":"Backup job started on storage {storage}.\n{reason}","label":"Backup started"},"backup_complete":{"title":"{hostname} → {storage}: Backup complete — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) completed successfully on {storage}.\nSize: {size}","label":"Backup complete"},"backup_warning":{"title":"{hostname} → {storage}: Backup complete with warnings — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) on {storage} completed but encountered warnings.\nWarnings: {reason}","label":"Backup (warnings)"},"backup_fail":{"title":"{hostname} → {storage}: Backup FAILED — {vmname} ({vmid})","body":"Backup of {vmname} (ID: {vmid}) failed on {storage}.\nReason: {reason}","label":"Backup FAILED"},"host_backup_start":{"title":"{hostname}: Host backup started → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nProfile: {profile_mode}","label":"Host backup started"},"host_backup_complete":{"title":"{hostname}: Host backup complete → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nData size: {data_size}\nArchive size: {archive_size}\nDuration: {duration}","label":"Host backup complete"},"host_backup_fail":{"title":"{hostname}: Host backup FAILED → {backend_label}","body":"Job: {job_id}\nBackend: {backend_label}\nDestination: {destination}\nDuration before failure: {duration}\nReason: {reason}\nLog: {log_file}","label":"Host backup FAILED"},"snapshot_complete":{"title":"{hostname}: Snapshot created — {vmname} ({vmid})","body":"Snapshot \"{snapshot_name}\" created for {vmname} (ID: {vmid}).","label":"Snapshot created"},"snapshot_fail":{"title":"{hostname}: Snapshot FAILED — {vmname} ({vmid})","body":"Snapshot creation for {vmname} (ID: {vmid}) failed.\nReason: {reason}","label":"Snapshot FAILED"},"cpu_high":{"title":"{hostname}: High CPU usage — {value}%","body":"CPU usage has reached {value}% on {cores} cores.\n{details}","label":"High CPU usage"},"ram_high":{"title":"{hostname}: High memory usage — {value}%","body":"Memory usage: {used} / {total} ({value}%).\n{details}","label":"High memory usage"},"temp_high":{"title":"{hostname}: High sensor temperature — {value}°C","body":"Sensor temperature has reached {value}°C (threshold: {threshold}°C).\n{details}","label":"High temperature"},"disk_space_low":{"title":"{hostname}: Low disk space on {mount}","body":"Filesystem {mount}: {used}% used ({available} available).\nFree up disk space to avoid service disruption.","label":"Low disk space"},"disk_io_error":{"title":"{hostname}: Disk failure detected — {device}","body":"I/O error or disk failure detected on device {device}.\n{reason}","label":"Disk failure / I/O error"},"storage_unavailable":{"title":"{hostname}: Storage unavailable — {storage_name}","body":"PVE storage \"{storage_name}\" (type: {storage_type}) is not accessible.\nReason: {reason}","label":"Storage unavailable"},"smart_test_complete":{"title":"{hostname}: SMART test completed — {device}","body":"SMART {test_type} test on /dev/{device} has completed.\nResult: {result}\nDuration: {duration}","label":"SMART test completed"},"smart_test_failed":{"title":"{hostname}: SMART test FAILED — {device}","body":"SMART {test_type} test on /dev/{device} has failed.\nResult: {result}\nReason: {reason}","label":"SMART test FAILED"},"gpu_mode_switch":{"title":"{hostname}: GPU mode changed to {new_mode}","body":"GPU passthrough mode has been switched.\nGPU: {gpu_name} ({gpu_pci})\nPrevious mode: {old_mode}\nNew mode: {new_mode}\n{details}","label":"GPU mode switched"},"gpu_passthrough_blocked":{"title":"{hostname}: {guest_type} {guest_id} blocked at startup","body":"PCIe passthrough guard prevented {guest_type} {guest_id} ({guest_name}) from starting.\nReason: {reason}\n{details}","label":"GPU passthrough blocked"},"pci_passthrough_conflict":{"title":"{hostname}: PCIe device conflict detected — {device_pci}","body":"A PCIe device is assigned to multiple guests.\nDevice: {device_pci}\nConflicting guests: {guest_list}\nAction required: Stop one of the guests or reassign the device.","label":"PCIe device conflict"},"load_high":{"title":"{hostname}: High system load — {value}","body":"System load average is {value} on {cores} cores.\n{details}","label":"High system load"},"network_down":{"title":"{hostname}: Network connectivity lost{entity_suffix}","body":"The node has lost network connectivity.\nReason: {reason}","label":"Network connectivity lost"},"network_latency":{"title":"{hostname}: High network latency — {value}ms","body":"Latency to gateway: {value}ms (threshold: {threshold}ms).\nThis may indicate network congestion or hardware issues.","label":"High network latency"},"auth_fail":{"title":"{hostname}: Authentication failure","body":"Failed login attempt detected.\nSource IP: {source_ip}\nUser: {username}\nService: {service}","label":"Authentication failure"},"ip_block":{"title":"{hostname}: IP blocked by Fail2Ban","body":"IP address {source_ip} has been banned.\nJail: {jail}\nFailed attempts: {failures}","label":"IP blocked by Fail2Ban"},"firewall_issue":{"title":"{hostname}: Firewall issue detected{entity_suffix}","body":"A firewall configuration issue has been detected.\nReason: {reason}","label":"Firewall issue detected"},"user_permission_change":{"title":"{hostname}: User permission changed","body":"User: {username}\nChange: {change_details}","label":"User permission changed"},"split_brain":{"title":"{hostname}: Cluster event reported","body":"A cluster event was reported:\n{reason}","label":"Cluster event"},"node_disconnect":{"title":"{hostname}: Node {node_name} disconnected","body":"Node {node_name} has disconnected from the cluster.","label":"Node disconnected"},"node_reconnect":{"title":"{hostname}: Node {node_name} reconnected","body":"Node {node_name} has rejoined the cluster successfully.","label":"Node reconnected"},"system_startup":{"title":"{hostname}: {reason}","body":"{summary}","label":"System startup report"},"system_shutdown":{"title":"{hostname}: System shutting down","body":"The node is shutting down.\n{reason}","label":"System shutting down"},"system_reboot":{"title":"{hostname}: System rebooting","body":"The node is rebooting.\n{reason}","label":"System rebooting"},"system_restore_completed":{"title":"{hostname}: Host restore finished","body":"Post-restore tasks completed in background.\n\nGuest configurations copied: {guests}\nBind-mount stub directories created: {stubs}\nStale node dirs removed: {stale_nodes}\nComponents reinstalled: {components}\nDuration: {duration}\n{warnings_block}","label":"Host restore completed"},"system_problem":{"title":"{hostname}: System problem detected{entity_suffix}","body":"A system-level problem has been detected.\nReason: {reason}","label":"System problem detected"},"service_fail":{"title":"{hostname}: Service failed — {service_name}","body":"System service \"{service_name}\" has failed.\nReason: {reason}","label":"Service failed"},"oom_kill":{"title":"{hostname}: OOM Kill — {process}","body":"Process \"{process}\" was killed by the Out-of-Memory manager.\n{reason}","label":"Out of memory kill"},"service_fail_batch":{"title":"{hostname}: {service_count} services failed{entity_suffix}","body":"{reason}","label":"Service fail batch"},"cron_output":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Cron job output (per-cron stdout via mail)"},"system_mail":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Smartd / mail bounces (PVE system mail)"},"apt_listchanges":{"title":"{hostname}: {pve_title}","body":"Upstream package information forwarded by Proxmox VE through apt-listchanges. The following text comes from the package maintainer and is not a ProxMenux recommendation.\n\n{reason}","label":"apt-listchanges package notices"},"webhook_test":{"title":"{hostname}: Webhook test received","body":"PVE webhook connectivity test successful.\n{reason}","label":"Webhook test"},"update_available":{"title":"{hostname}: Updates available","body":"Total updates: {total_count}\nSecurity: {security_count}\nProxmox: {pve_count}\nKernel: {kernel_count}\nImportant packages:\n{important_list}","label":"Updates available (legacy)"},"unknown_persistent":{"title":"{hostname}: Check unavailable - {category}{entity_suffix}","body":"Health check for {category} has been unavailable for 3+ cycles.\n{reason}","label":"Check unavailable"},"health_persistent":{"title":"{hostname}: {count} active health issue(s){entity_suffix}","body":"The following health issues remain unresolved:\n{issue_list}\n\nThis digest is sent once every 24 hours while issues persist.","label":"Active health issues (daily)"},"health_issue_new":{"title":"{hostname}: New health issue — {category}{entity_suffix}","body":"New {severity} issue detected in: {category}\nDetails: {reason}","label":"New health issue"},"health_issue_resolved":{"title":"{hostname}: Resolved - {category}{entity_suffix}","body":"{category} issue has been resolved.\n{reason}\nDuration: {duration}","label":"Health issue resolved"},"update_summary":{"title":"{hostname}: Updates available","body":"Total updates: {total_count}\nSecurity updates: {security_count}\nProxmox-related updates: {pve_count}\nKernel updates: {kernel_count}\nImportant packages:\n{important_list}","label":"Host package updates"},"pve_update":{"title":"{hostname}: Proxmox VE {new_version} available","body":"A new Proxmox VE release is available.\nCurrent: {current_version}\nNew: {new_version}\n{details}","label":"Proxmox VE update available"},"update_complete":{"title":"{hostname}: System update completed","body":"System packages have been successfully updated.\n{details}","label":"Host update completed"},"ai_model_migrated":{"title":"{hostname}: AI model updated","body":"The AI model for notifications has been automatically updated.\nProvider: {provider}\nPrevious model: {old_model}\nNew model: {new_model}\n\n{message}","label":"AI model auto-updated"},"proxmenux_update":{"title":"{hostname}: ProxMenux {new_version} available","body":"A new version of ProxMenux is available.\nCurrent: {current_version}\nNew: {new_version}","label":"ProxMenux update available"},"mount_stale":{"title":"{hostname}: stale remote mount {mount_target}","body":"Remote mount {mount_target} ({fstype}) from {mount_source} is stale{lxc_scope}.\nStat timed out or returned an error: {error}\n\nApps writing to this path will silently land on the underlying filesystem and may fill the disk. Remount or fix connectivity ASAP.","label":"Remote mount stale"},"mount_readonly":{"title":"{hostname}: remote mount {mount_target} is read-only","body":"Remote mount {mount_target} ({fstype}) from {mount_source} is mounted read-only{lxc_scope}. Writes will fail. If this was unintentional, remount with rw.","label":"Remote mount read-only"},"lxc_disk_low":{"title":"{hostname}: CT {vmid} rootfs at {usage_percent}%","body":"CT {vmid} ({name}) rootfs is at {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nA full LXC rootfs prevents the container from booting cleanly. Either expand the rootfs (pct resize {vmid} rootfs +1G) or free space inside the container.","label":"LXC rootfs near full"},"vm_disk_low":{"title":"{hostname}: VM {vmid} filesystems at {usage_percent}%","body":"VM {vmid} ({name}) guest filesystems are at {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nReported by the QEMU guest agent. Includes every persistent filesystem the guest mounts on a block device — virtual disks and PCI-passthrough drives alike. Free up space inside the guest or expand the affected storage before writes start to fail.","label":"VM filesystems near full"},"lxc_mount_low":{"title":"{hostname}: CT {vmid} mount {mount} at {usage_percent}%","body":"Mount {mount} inside CT {vmid} ({name}) is at {usage_percent}% used.\nFilesystem type: {fstype}\n\nA full mount inside a container often blocks the application silently — writes either fail or, worse, land on the rootfs and trigger the rootfs alert next. Free up space on the mount or expand it.","label":"LXC mount near full"},"pve_storage_full":{"title":"{hostname}: PVE storage {storage_name} at {usage_percent}%","body":"Proxmox storage \"{storage_name}\" (type: {storage_type}) is at {usage_percent}% used.\n\nOnce full, no new VM/CT can be provisioned and existing guests may fail to write. Move/delete unused volumes or expand the underlying pool/LV/RBD image.","label":"PVE storage near full"},"zfs_pool_full":{"title":"{hostname}: ZFS pool {pool_name} at {usage_percent}%","body":"ZFS pool \"{pool_name}\" is at {usage_percent}% capacity.\n\nZFS performance and write reliability degrade sharply above ~80% capacity (CoW needs free space for new blocks). Free up snapshots, prune old datasets, or add more vdevs to the pool.","label":"ZFS pool near full"},"post_install_update":{"title":"{hostname}: {count} ProxMenux optimization update(s) available","body":"{count} ProxMenux optimization update(s) available on this host.\n\n🛠️ Available versions:\n{tool_list}\n\n💡 Apply from:\n • ProxMenux Monitor → Settings → ProxMenux Optimizations\n • Or open the ProxMenux menu → Settings post-install Proxmox → \"Apply available updates\"","label":"ProxMenux optimization updates available"},"secure_gateway_update_available":{"title":"{hostname}: {app_name} update available{update_title_suffix}","body":"{app_name} (managed by ProxMenux) has 📦 {package_count} package update(s) pending in its container.\n{version_line}\n\n💡 Open ProxMenux Monitor > Security > Secure Gateway and click \"Update\" to apply.\n\n🗂️ Packages:\n{package_list}","label":"Secure Gateway update available"},"nvidia_driver_update_available":{"title":"{hostname}: NVIDIA driver update available — v{latest_version}","body":"A newer maintenance release is available for the installed NVIDIA driver branch.\n🔹 Currently installed: v{current_version}\n🟢 Latest available: v{latest_version}\n\n{upgrade_reason}\n\n💡 To reinstall:\n • From the ProxMenux post-install menu: {menu_label}\n\nReinstalling rebuilds the DKMS module against the running kernel and requires a reboot to load the new driver.","label":"NVIDIA driver update available"},"coral_driver_update_available":{"title":"{hostname}: Coral TPU driver update available — {latest_version}","body":"A newer {variant_label} is available.\n🔹 Currently installed: {current_version}\n🟢 Latest available: {latest_version}\n\n{upgrade_reason}\n\n💡 To reinstall:\n • From the ProxMenux post-install menu: {menu_label}\n\n{reboot_note}","label":"Coral TPU driver update available"},"burst_auth_fail":{"title":"{hostname}: +{count} more auth failures in {window}","body":"+{count} additional authentication failures detected in {window} ({total_count} total).\nSources: {entity_list}","label":"Auth failures burst"},"burst_ip_block":{"title":"{hostname}: Fail2Ban banned +{count} more IPs in {window}","body":"+{count} additional IPs banned by Fail2Ban in {window} ({total_count} total).\nIPs: {entity_list}","label":"IP block burst"},"burst_disk_io":{"title":"{hostname}: +{count} more disk I/O errors on {entity_list}","body":"+{count} additional I/O errors detected in {window} ({total_count} total).\nDevices: {entity_list}","label":"Disk I/O burst"},"burst_cluster":{"title":"{hostname}: Cluster flapping detected (+{count} more changes)","body":"Cluster state changed +{count} more times in {window} ({total_count} total).\nNodes: {entity_list}","label":"Cluster flapping burst"},"burst_service_fail":{"title":"{hostname}: +{count} more services failed in {window}","body":"+{count} additional service failures detected in {window} ({total_count} total).\nThis typically indicates a node reboot or PVE service restart.\n\nAdditional failures:\n{details}","label":"Service fail burst"},"burst_system":{"title":"{hostname}: +{count} more system problems in {window}","body":"+{count} additional system problems detected in {window} ({total_count} total).\n\nAdditional issues:\n{details}","label":"System problems burst"},"burst_generic":{"title":"{hostname}: +{count} more {event_type} events in {window}","body":"+{count} additional events of type {event_type} in {window} ({total_count} total).\n\nAdditional events:\n{details}","label":"Generic burst"},"kernel_warning":{"title":"{hostname}: Kernel diagnostic event detected","body":"The kernel recorded a diagnostic event: {kernel_details}","label":"Kernel warnings and diagnostic traces"}},"lxcUpdate":{"status":{"success":"succeeded","failure":"failed","partial":"completed partially","deferred":"deferred","skipped":"skipped"},"source":{"scheduled":"Scheduled","manual":"Manual"},"sourceLabel":"Source","targets":"Targets","osPending":"OS packages pending: {before} → {after}","osUnverified":"OS packages: update command executed; result not verified","applications":"Applications: {items}","applicationsUnverified":"Applications: updater executed; no tracked version change was observed","dockerEngineChange":"Docker Engine: {before} → {after}","dockerEngineVerified":"Docker Engine: verified at {version}","dockerEngineUnverified":"Docker Engine: update command executed; version not verified","dockerImagesPending":"Docker images pending: {before} → {after}","dockerImagesChanged":"Docker images changed: {images}","deferredTargets":"Deferred targets: {targets}","reason":"Reason: {reason}","restartRequired":"Restart required: {value}","yes":"yes","no":"no","verificationPending":"Verification pending until the container is running","verificationWarning":"Verification warning: {error}","duration":"Duration: {duration}"},"fallback":{"unknownTitle":"{hostname}: {event_type}","healthCheckDegraded":"Health check degraded","none":"none","temperatureAlertTitle":"{hostname}: Sensor temperature alert","temperatureAlertBody":"Temperature alert recorded without a complete measurement payload.","recordedReason":"Recorded reason: {reason}","recordedDetails":"Recorded details: {details}"},"fields":{"vmid":"VM/CT","name":"Name","device":"Device","sourceIp":"Source IP","node":"Node","category":"Category","service":"Service","jail":"Jail","user":"User","count":"Count","window":"Window","affected":"Affected"},"vzdump":{"size":"Size: {value}","duration":"Duration: {value}","file":"File","backups":"{count} backups","failed":"{count} failed","total":"Total: {value}","time":"Time: {value}"},"startup":{"issuesTitle":"{hostname}: System startup - {count} issue(s) detected","completeTitle":"{hostname}: System startup completed","operational":"All systems operational.","vmCountOne":"{count} VM","vmCountMany":"{count} VMs","ctCountOne":"{count} CT","ctCountMany":"{count} CTs","started":"✅ {counts} started","vmFailed":"❌ VM failed: {name} - {reason}","ctFailed":"❌ CT failed: {name} - {reason}","unknownError":"unknown error","storageUnavailable":"⚠️ Storage: {count} unavailable ({names})","servicesFailed":"⚠️ Services: {count} failed ({names})","duration":"⏱️ Startup completed in {minutes} min"},"appUpdates":{"singleTitle":"{hostname}: {app_name} update available on CT {vmid}","singleBody":"{app_name} on CT {vmid} ({ct_name}) has a new version:\n {installed} → {latest}","emptyTitle":"{hostname}: Application updates available","emptyBody":"Application updates are available.","batchTitle":"{hostname}: {count} application updates available","batchLeadOneContainer":"{count} applications in {container_count} LXC container have a newer version:","batchLeadManyContainers":"{count} applications in {container_count} LXC containers have a newer version:","additional":"… {count} additional application(s)","app":"app","unknown":"unknown"},"healthDegraded":{"categories":{"cpu":"CPU usage and temperature","memory":"Memory and swap","storage":"Storage mounts and space","disks":"Disk I/O and errors","network":"Network interfaces","vms":"VMs and containers","services":"PVE services","logs":"System logs","updates":"System updates","security":"Security"},"severity":{"critical":"Critical","warning":"Warning"},"singleTitle":"{hostname}: {severity} health status – {category}","multipleTitle":"{hostname}: {count} health checks degraded","multipleLine":"• {severity}: {category}\n {reason}","reasons":{"cpuSustained":"CPU is above {threshold}% for {duration}s."}},"digest":{"title":"{hostname}: 24h summary ({timestamp})","readFailed":"digest read failed: {error}","empty":"No INFO events buffered for this digest window.","lead":"{count} INFO events grouped by category:\n","more":" • … and {count} more","footer":"(Critical/Warning events arrived at the time they happened, not in this digest.)","quietTitle":"{hostname}: {count} events buffered during Quiet Hours","groups":{"vm_ct":"VM a CT","backup":"Backup","resources":"Resources","storage":"Storage","network":"Network","security":"Security","cluster":"Cluster","services":"Services","health":"Health","updates":"Updates","hardware":"Hardware","system":"System","other":"Other"}},"test":{"title":"ProxMenux Test","welcome":"Welcome to ProxMenux Monitor!","verify":"This is a test message to verify your notification channel is working correctly.","configuration":"Channel configuration:","iconsEnabled":"Icons: enabled","iconsDisabled":"Icons: disabled","aiEnabled":"AI: enabled ({info})","aiDisabled":"AI: disabled","alerts":"You will receive alerts about system events, health status changes, and security incidents.","photoCaption":"You can use this image as the profile photo for your notification bot."},"channels":{"discord":{"category":"Category","host":"Host","severity":"Severity"},"email":{"report":"{group} Report","details":"Details","host":"Host","footer":"ProxMenux Notification Service","severity":{"critical":"Critical","warning":"Warning","info":"Information","ok":"Resolved","default":"Notice"},"groups":{"vm_ct":"Virtual Machine / Container","backup":"Backup & Snapshot","resources":"System Resources","storage":"Storage","network":"Network","security":"Security","cluster":"Cluster","services":"System Services","health":"Health Monitor","updates":"System Updates","system":"System","hardware":"Hardware","other":"System Notification"},"fields":{"vmCtId":"VM/CT ID","name":"Name","action":"Action","targetNode":"Target Node","reason":"Reason","storage":"Storage","status":"Status","size":"Size","duration":"Duration","snapshot":"Snapshot","metric":"Metric","currentValue":"Current Value","threshold":"Threshold","cpuCores":"CPU Cores","memory":"Memory","temperature":"Temperature","mountPoint":"Mount Point","usage":"Usage","available":"Available","device":"Device","severity":"Severity","storageName":"Storage Name","type":"Type","interface":"Interface","latency":"Latency","event":"Event","sourceIp":"Source IP","username":"Username","service":"Service","jail":"Jail","failures":"Failures","change":"Change","node":"Node","quorum":"Quorum","nodesAffected":"Nodes Affected","process":"Process","category":"Category","previousSeverity":"Previous Severity","activeIssues":"Active Issues","totalUpdates":"Total Updates","securityUpdates":"Security Updates","proxmoxUpdates":"Proxmox Updates","kernelUpdates":"Kernel Updates","importantPackages":"Important Packages","currentVersion":"Current Version","newVersion":"New Version"},"status":{"failed":"Failed","completed":"Completed","started":"Started","unconfirmed":"Unconfirmed","completed_with_warnings":"Completed with warnings"}}},"temperature":{"sampleSpan":"High samples span {duration}."},"backup":{"unconfirmedTitle":"{hostname}: Backup outcome unconfirmed","confirmedTitle":"{hostname}: Backup complete","confirmedBody":"Backup completed successfully.","errorTitle":"{hostname}: Backup error reported","errorBody":"The backup report contains an error.","unconfirmedBody":"The backup outcome is not confirmed.","warningTitle":"{hostname}: Backup completed with warnings","warningBody":"Backup completed with warnings.","diagnosticsOmitted":"Additional diagnostic lines or text omitted: {count}. Original report retained."}}}, + "monitorLinks": { + "thisNode": "This node", + "checking": "Checking nodes…", + "loadFailed": "Could not load the node list.", + "empty": "No other nodes yet. Add one to open its Monitor from here.", + "groups": { + "cluster": "Cluster", + "standalone": "Standalone nodes", + "pbs": "Proxmox Backup Server" + }, + "noResponse": "No response", + "pbsInterface": "PBS interface", + "openNewTab": "Open in a new tab", + "manage": "Add or manage nodes…", + "dialogTitle": "Linked nodes", + "dialogDescription": "Cluster nodes and configured Proxmox Backup Servers are detected automatically. Each Monitor keeps its own sign-in.", + "addTitle": "Add a node", + "name": "Name", + "address": "Monitor address", + "type": "Type", + "add": "Add", + "save": "Save", + "remove": "Remove", + "visible": "Visible", + "autoAddress": "Automatic", + "detected": "Detected", + "added": "Added", + "saveFailed": "Could not save the node list.", + "backTo": "Back to {name}", + "cameFromTitle": "Add Monitor node?", + "cameFromBody": "Add {name} to the linked nodes of this Monitor.", + "notNow": "Not now" + } } diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index 322ea52a..a9754ca6 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -50,12 +50,21 @@ "navigation": "Navegación de PBS", "cpu": "CPU", "memory": "Memoria", - "uptime": "Tiempo de actividad" + "uptime": "Tiempo de actividad", + "capacityUnavailable": "Capacidad no disponible.", + "datastoresUnavailable": "{count} no disponibles", + "datastoresOnline": "Todos disponibles", + "moreDatastores": "+{count} datastores más", + "estimatedFull": "Lleno previsto", + "estimatedFullUnknown": "Aún no hay datos suficientes", + "duration": "Duración", + "noJobs": "No hay programaciones de sincronización, verificación ni prune." }, "_meta": "Community translation target. Copy keys from ../en/common.json and translate the values.", "app": { "title": "ProxMenux Monitor", "description": "Proxmox System Dashboard", + "descriptionPbs": "Proxmox Backup Server Dashboard", "loading": "Cargando...", "connecting": "Conexión al ProxMenux Monitor", "unknown": "Desconocido", @@ -193,6 +202,7 @@ "dayMany": "{count} días" }, "proxmoxVersion": "Versión Proxmox:", + "pbsVersion": "Versión PBS:", "kernel": "Núcleo:", "availableUpdates": "Actualizaciones disponibles:", "packages": "paquetes", @@ -1881,6 +1891,7 @@ "network": "Interfaces de red", "vms": "Máquinas virtuales y contenedores", "services": "Servicios PVE", + "pbsServices": "Servicios PBS", "logs": "Registros del sistema", "updates": "Actualizaciones del sistema", "security": "Seguridad y certificados", @@ -4874,7 +4885,8 @@ "disks": "E/S de disco y errores", "network": "Interfaces de red", "vms": "Máquinas virtuales y contenedores", - "services": "servicios PVE", + "services": "Servicios PVE", + "pbsServices": "Servicios PBS", "logs": "Registros del sistema", "updates": "Actualizaciones del sistema", "security": "Seguridad y certificados" @@ -7321,5 +7333,38 @@ "diagnosticsOmitted": "Líneas o texto de diagnóstico omitidos: {count}. Se conserva el informe original." } } + }, + "monitorLinks": { + "thisNode": "Este nodo", + "checking": "Comprobando nodos…", + "loadFailed": "No se pudo cargar la lista de nodos.", + "empty": "Aún no hay otros nodos. Añade uno para abrir su Monitor desde aquí.", + "groups": { + "cluster": "Clúster", + "standalone": "Nodos independientes", + "pbs": "Proxmox Backup Server" + }, + "noResponse": "Sin respuesta", + "pbsInterface": "Interfaz de PBS", + "openNewTab": "Abrir en una pestaña nueva", + "manage": "Añadir o gestionar nodos…", + "dialogTitle": "Nodos enlazados", + "dialogDescription": "Los nodos del clúster y los Proxmox Backup Server configurados se detectan automáticamente. Cada Monitor mantiene su propio inicio de sesión.", + "addTitle": "Añadir un nodo", + "name": "Nombre", + "address": "Dirección del Monitor", + "type": "Tipo", + "add": "Añadir", + "save": "Guardar", + "remove": "Eliminar", + "visible": "Visible", + "autoAddress": "Automática", + "detected": "Detectado", + "added": "Añadido", + "saveFailed": "No se pudo guardar la lista de nodos.", + "backTo": "Volver a {name}", + "cameFromTitle": "¿Añadir nodo Monitor?", + "cameFromBody": "Añade {name} a los nodos enlazados de este Monitor.", + "notNow": "Ahora no" } } diff --git a/AppImage/scripts/build_appimage.sh b/AppImage/scripts/build_appimage.sh index 40560c21..783978f6 100755 --- a/AppImage/scripts/build_appimage.sh +++ b/AppImage/scripts/build_appimage.sh @@ -163,6 +163,8 @@ cp "$SCRIPT_DIR/temperature_sensor_resolver.py" "$APP_DIR/usr/bin/" 2>/dev/null cp "$SCRIPT_DIR/proxmox_storage_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ proxmox_storage_monitor.py not found" cp "$SCRIPT_DIR/pbs_manager.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/flask_pbs_routes.py" "$APP_DIR/usr/bin/" +cp "$SCRIPT_DIR/monitor_links.py" "$APP_DIR/usr/bin/" +cp "$SCRIPT_DIR/flask_monitor_links_routes.py" "$APP_DIR/usr/bin/" cp "$APPIMAGE_ROOT/../scripts/global/proxmox_platform.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/flask_script_runner.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_script_runner.py not found" cp "$SCRIPT_DIR/security_manager.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ security_manager.py not found" diff --git a/AppImage/scripts/flask_monitor_links_routes.py b/AppImage/scripts/flask_monitor_links_routes.py new file mode 100644 index 00000000..24c7d3ee --- /dev/null +++ b/AppImage/scripts/flask_monitor_links_routes.py @@ -0,0 +1,58 @@ +"""Links between ProxMenux Monitors for the node selector in the header.""" + +from flask import Blueprint, jsonify, request +import monitor_links +from jwt_middleware import require_auth, require_admin_scope + +monitor_links_bp = Blueprint("monitor_links", __name__) + + +@monitor_links_bp.after_request +def no_cache(response): + response.headers["Cache-Control"] = "no-store" + return response + + +@monitor_links_bp.route("/api/monitor-links") +@require_auth +def list_links(): + return jsonify(monitor_links.cached_links(include_hidden=request.args.get("all") == "1")) + + +@monitor_links_bp.route("/api/monitor-links", methods=["POST"]) +@require_admin_scope +def add_link(): + try: + key = monitor_links.add_link(request.get_json(silent=True)) + monitor_links.refresh() + return jsonify(success=True, key=key) + except monitor_links.LinkError as exc: + return jsonify(error=str(exc)), 400 + except OSError: + return jsonify(error="Could not save the node list."), 500 + + +@monitor_links_bp.route("/api/monitor-links/", methods=["PATCH"]) +@require_admin_scope +def update_link(key): + try: + monitor_links.update_link(key, request.get_json(silent=True)) + monitor_links.refresh() + return jsonify(success=True) + except monitor_links.LinkError as exc: + return jsonify(error=str(exc)), 400 + except OSError: + return jsonify(error="Could not save the node list."), 500 + + +@monitor_links_bp.route("/api/monitor-links/", methods=["DELETE"]) +@require_admin_scope +def delete_link(key): + try: + monitor_links.delete_link(key) + monitor_links.refresh() + return jsonify(success=True) + except monitor_links.LinkError as exc: + return jsonify(error=str(exc)), 400 + except OSError: + return jsonify(error="Could not save the node list."), 500 diff --git a/AppImage/scripts/flask_notification_routes.py b/AppImage/scripts/flask_notification_routes.py index 2354141d..907ad3b6 100644 --- a/AppImage/scripts/flask_notification_routes.py +++ b/AppImage/scripts/flask_notification_routes.py @@ -1156,6 +1156,14 @@ def setup_pve_webhook_core() -> dict: 'fallback_commands': [], 'error': None, } + try: # Proxmox Backup Server has no /etc/pve notification configuration + from pbs_manager import is_pbs_host + on_pbs = is_pbs_host() + except Exception: + on_pbs = False + if on_pbs: + result['not_applicable'] = True + return result try: if not _pve_cluster_fs_mounted(): @@ -1339,6 +1347,13 @@ def cleanup_pve_webhook_core() -> dict: Only removes blocks named 'proxmenux-webhook' / 'proxmenux-default'. """ result = {'cleaned': False, 'error': None} + try: # Proxmox Backup Server has no /etc/pve notification configuration + from pbs_manager import is_pbs_host + on_pbs = is_pbs_host() + except Exception: + on_pbs = False + if on_pbs: + return {'cleaned': True, 'error': None, 'not_applicable': True} try: if not _pve_cluster_fs_mounted(): diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 3ffa0c4c..ca789d38 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -91,6 +91,7 @@ from flask_health_routes import health_bp # noqa: E402 from flask_auth_routes import auth_bp # noqa: E402 from flask_proxmenux_routes import proxmenux_bp # noqa: E402 from flask_pbs_routes import pbs_bp # noqa: E402 +from flask_monitor_links_routes import monitor_links_bp # noqa: E402 from pbs_manager import get_platform, is_pbs_host # noqa: E402 from flask_security_routes import security_bp # noqa: E402 from flask_notification_routes import notification_bp # noqa: E402 @@ -198,6 +199,7 @@ app.register_blueprint(auth_bp) app.register_blueprint(health_bp) app.register_blueprint(proxmenux_bp) app.register_blueprint(pbs_bp) +app.register_blueprint(monitor_links_bp) app.register_blueprint(security_bp) app.register_blueprint(notification_bp) app.register_blueprint(oci_bp) @@ -12214,6 +12216,17 @@ def _get_node_metrics_payload(timeframe, max_age=_NODE_METRICS_TTL): lock.release() +def _node_rrd_command(local_node, timeframe, cf=None): + """Node RRD query; Proxmox Backup Server keeps the same fields under its own API.""" + if is_pbs_host(): + return ['proxmox-backup-debug', 'api', 'get', '/nodes/localhost/rrd', + '--timeframe', timeframe, '--cf', cf or 'AVERAGE', '--output-format', 'json'] + command = ['pvesh', 'get', f'/nodes/{local_node}/rrddata', '--timeframe', timeframe] + if cf: + command += ['--cf', cf] + return command + ['--output-format', 'json'] + + def _compute_node_metrics_payload(timeframe: str) -> dict: """Fetch and shape one node RRD snapshot within a bounded query budget.""" valid_timeframes = ('hour', 'day', 'week', 'month', 'year') @@ -12237,8 +12250,7 @@ def _compute_node_metrics_payload(timeframe: str) -> dict: try: rrd_result = subprocess.run( - ['pvesh', 'get', f'/nodes/{local_node}/rrddata', - '--timeframe', timeframe, '--output-format', 'json'], + _node_rrd_command(local_node, timeframe), capture_output=True, text=True, timeout=_NODE_METRICS_TIMEOUT, ) except subprocess.TimeoutExpired: @@ -12332,9 +12344,7 @@ def _compute_node_metrics_payload(timeframe: str) -> dict: return None try: extra = subprocess.run( - ['pvesh', 'get', f'/nodes/{local_node}/rrddata', - '--timeframe', timeframe, '--cf', cf, - '--output-format', 'json'], + _node_rrd_command(local_node, timeframe, cf), capture_output=True, text=True, timeout=remaining, ) if extra.returncode == 0 and extra.stdout: @@ -23339,8 +23349,8 @@ if __name__ == '__main__': # the 24 h PollingCollector cycle. Runs in a daemon thread with # a delay so it doesn't block startup — notification_manager needs # a moment to bind channels before we emit. - # The guest services below read VMs, LXCs and the PVE node metrics, - # none of which exist on a Proxmox Backup Server. + # The guest services below read VMs and LXCs, which do not exist on a + # Proxmox Backup Server. pve_host = not is_pbs_host() try: def _startup_emit_pending_app_updates(): @@ -23359,13 +23369,19 @@ if __name__ == '__main__': # Prewarm only the Overview's default day range; other ranges are lazy. try: - if pve_host: - metrics_thread = threading.Thread(target=_node_metrics_prewarmer_loop, daemon=True, name='node-metrics-prewarmer') - metrics_thread.start() - print("[ProxMenux] Node-metrics prewarmer started (day range, 90s interval)") + metrics_thread = threading.Thread(target=_node_metrics_prewarmer_loop, daemon=True, name='node-metrics-prewarmer') + metrics_thread.start() + print("[ProxMenux] Node-metrics prewarmer started (day range, 90s interval)") except Exception as e: print(f"[ProxMenux] Node-metrics prewarmer failed to start: {e}") + # Linked Monitors for the header node selector, renewed every 5 minutes. + try: + import monitor_links + monitor_links.start_prewarmer() + except Exception as e: + print(f"[ProxMenux] Monitor links prewarmer failed to start: {e}") + # ── VM/CT modal-cache prewarmer ── # Keeps _vm_details_cache / _vm_backups_cache / app snapshots / # _vm_schedule_cache warm from the backend so the "Loading diff --git a/AppImage/scripts/monitor_links.py b/AppImage/scripts/monitor_links.py new file mode 100644 index 00000000..35ff58a2 --- /dev/null +++ b/AppImage/scripts/monitor_links.py @@ -0,0 +1,321 @@ +"""Other ProxMenux Monitors reachable from this one: cluster nodes, PBS servers +and entries added by hand. + +Only links are kept here. No credential is stored or sent: the browser opens +the other Monitor, which keeps its own sign-in. +""" + +import ipaddress +import json +import os +import re +import secrets +import socket +import ssl +import subprocess +import threading +import time +import urllib.parse +import urllib.request +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path + +import pbs_manager + +LINKS_FILE = Path(os.environ.get("PROXMENUX_MONITOR_LINKS", "/usr/local/share/proxmenux/monitor_links.json")) +SSL_CONFIG_FILE = Path(os.environ.get("PROXMENUX_SSL_CONFIG", "/etc/proxmenux/ssl_config.json")) +STORAGE_CFG = Path("/etc/pve/storage.cfg") +MONITOR_PORT = 8008 +PBS_UI_PORT = 8007 +PROBE_TIMEOUT = 3 +PROBE_TTL = 300 +MAX_MANUAL = 64 +PRODUCTS = ("pve", "pbs") + +_lock = threading.Lock() +_probe_cache = {} +_snapshot = {"at": 0.0, "data": None} +_refresh_lock = threading.Lock() + + +class LinkError(ValueError): + pass + + +def _load(): + try: + data = json.loads(LINKS_FILE.read_text()) + except (OSError, ValueError): + return {"manual": [], "overrides": {}} + manual = data.get("manual") if isinstance(data.get("manual"), list) else [] + overrides = data.get("overrides") if isinstance(data.get("overrides"), dict) else {} + return {"manual": [row for row in manual if isinstance(row, dict)], "overrides": overrides} + + +def _save(data): + LINKS_FILE.parent.mkdir(parents=True, exist_ok=True) + temporary = LINKS_FILE.with_suffix(".tmp") + temporary.write_text(json.dumps(data, indent=2)) + os.chmod(temporary, 0o600) + os.replace(temporary, LINKS_FILE) + + +def normalize_url(value): + """Return the origin of an http(s) address, or raise LinkError.""" + if not isinstance(value, str) or not value.strip(): + raise LinkError("Enter the address of the Monitor.") + text = value.strip() + if "://" not in text: + text = "https://" + text + parsed = urllib.parse.urlsplit(text) + if parsed.scheme not in ("http", "https") or not parsed.hostname: + raise LinkError("The address must start with http:// or https:// and name a host.") + if parsed.username or parsed.password: + raise LinkError("The address must not contain a user or password.") + try: + port = parsed.port + except ValueError as exc: + raise LinkError("The port of the address is not valid.") from exc + host = parsed.hostname + if ":" in host: + host = f"[{host}]" + return f"{parsed.scheme}://{host}" + (f":{port}" if port else "") + + +def _local_scheme(): + try: + return "https" if json.loads(SSL_CONFIG_FILE.read_text()).get("enabled") else "http" + except (OSError, ValueError, AttributeError): + return "http" + + +def _host_url(host, port, scheme=None): + if ":" in host and not host.startswith("["): + host = f"[{host}]" + return f"{scheme or _local_scheme()}://{host}:{port}" + + +def _is_private(host): + """Only addresses in private ranges are probed without being asked to.""" + try: + addresses = {info[4][0] for info in socket.getaddrinfo(host, None)} + except OSError: + return False + try: + return bool(addresses) and all(ipaddress.ip_address(a.split("%")[0]).is_private for a in addresses) + except ValueError: + return False + + +def _cluster_nodes(): + try: + result = subprocess.run(["pvesh", "get", "/cluster/status", "--output-format", "json"], + capture_output=True, text=True, timeout=8) + rows = json.loads(result.stdout) if result.returncode == 0 else [] + except (OSError, subprocess.SubprocessError, ValueError): + return [] + nodes = [] + for row in rows if isinstance(rows, list) else []: + if isinstance(row, dict) and row.get("type") == "node" and not row.get("local") and row.get("name") and row.get("ip"): + nodes.append({"key": f"cluster:{row['name']}", "name": str(row["name"]), "product": "pve", + "group": "cluster", "host": str(row["ip"])}) + return nodes + + +def _storage_pbs(): + try: + text = STORAGE_CFG.read_text() + except OSError: + return [] + servers, current = [], None + for line in text.splitlines(): + header = re.match(r"^(\w+):\s*(\S+)", line) + if header: + current = {"name": header.group(2), "port": PBS_UI_PORT} if header.group(1) == "pbs" else None + if current is not None: + servers.append(current) + continue + option = re.match(r"^\s+(server|port)\s+(\S+)", line) + if current is not None and option: + current[option.group(1)] = option.group(2) + return [row for row in servers if row.get("server")] + + +def _pbs_servers(): + found = {} + for row in _storage_pbs(): + host = row["server"] + port = row.get("port") if str(row.get("port", "")).isdigit() else PBS_UI_PORT + found.setdefault(host, {"name": row["name"], "native_url": _host_url(host, port, "https")}) + try: + for row in pbs_manager.public_servers(): + host = urllib.parse.urlsplit(row.get("url", "")).hostname + if host: + found.setdefault(host, {"name": row.get("name") or host, "native_url": normalize_url(row["url"])}) + except Exception: + pass + return [{"key": f"pbs:{host}", "name": info["name"], "product": "pbs", "group": "pbs", + "host": host, "native_url": info["native_url"]} for host, info in found.items()] + + +def _probe(url): + """True when a ProxMenux Monitor answers at url; nothing is sent but the request.""" + now = time.monotonic() + with _lock: + cached = _probe_cache.get(url) + if cached and cached[0] > now: + return cached[1] + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE # liveness only: no credential or token is ever sent + online = False + try: + request = urllib.request.Request(url + "/api/auth/status", headers={"Accept": "application/json"}) + with urllib.request.urlopen(request, timeout=PROBE_TIMEOUT, context=context) as response: + online = response.status == 200 and "auth_enabled" in json.loads(response.read(4096)) + except Exception: + online = False + with _lock: + if len(_probe_cache) > 256: + _probe_cache.clear() + _probe_cache[url] = (now + PROBE_TTL, online) + return online + + +def _resolve(entry, override): + """Pick the Monitor address and its state for one discovered or manual entry.""" + if override.get("url"): + return override["url"], "online" if _probe(override["url"]) else "offline" + if entry.get("url"): + return entry["url"], "online" if _probe(entry["url"]) else "offline" + host = entry["host"] + if not _is_private(host): + return None, "unknown" + for scheme in (_local_scheme(), "https" if _local_scheme() == "http" else "http"): + url = _host_url(host, MONITOR_PORT, scheme) + if _probe(url): + return url, "online" + return _host_url(host, MONITOR_PORT), "offline" + + +def list_links(include_hidden=False): + platform = pbs_manager.get_platform() + data = _load() + entries = [] + if platform.get("product") != "pbs": + entries += _cluster_nodes() + _pbs_servers() + for row in data["manual"]: + entries.append({"key": f"manual:{row.get('id')}", "name": row.get("name") or row.get("url"), + "product": row.get("product") if row.get("product") in PRODUCTS else "pve", + "group": "pbs" if row.get("product") == "pbs" else "standalone", + "url": row.get("url"), "manual": True}) + visible = [e for e in entries if include_hidden or not data["overrides"].get(e["key"], {}).get("hidden")] + with ThreadPoolExecutor(max_workers=8) as executor: + resolved = list(executor.map(lambda e: _resolve(e, data["overrides"].get(e["key"], {})), visible)) + links = [] + for entry, (url, state) in zip(visible, resolved): + override = data["overrides"].get(entry["key"], {}) + links.append({"key": entry["key"], "name": entry["name"], "product": entry["product"], + "group": entry["group"], "url": url, "status": state, + "native_url": entry.get("native_url"), "manual": bool(entry.get("manual")), + "hidden": bool(override.get("hidden")), "custom_url": bool(override.get("url"))}) + return {"self": {"name": platform.get("hostname") or socket.gethostname(), "product": platform.get("product")}, + "links": links} + + +def refresh(): + """Recompute the list with fresh probes and keep it for the header.""" + with _refresh_lock: + with _lock: + _probe_cache.clear() + data = list_links(include_hidden=True) + _snapshot.update(at=time.monotonic(), data=data) + return data + + +def _refresh_quietly(): + try: + refresh() + except Exception: + pass + + +def cached_links(include_hidden=False): + """The last list at once; a stale one is renewed in the background.""" + data = _snapshot["data"] + if data is None: + data = refresh() + elif time.monotonic() - _snapshot["at"] > PROBE_TTL and not _refresh_lock.locked(): + threading.Thread(target=_refresh_quietly, daemon=True, name="monitor-links-refresh").start() + if include_hidden: + return data + return {**data, "links": [row for row in data["links"] if not row["hidden"]]} + + +def start_prewarmer(): + def loop(): + time.sleep(10) + while True: + _refresh_quietly() + time.sleep(PROBE_TTL) + threading.Thread(target=loop, daemon=True, name="monitor-links-prewarmer").start() + + +def add_link(payload): + if not isinstance(payload, dict): + raise LinkError("Invalid request.") + name = str(payload.get("name") or "").strip()[:64] + url = normalize_url(payload.get("url")) + product = payload.get("product") if payload.get("product") in PRODUCTS else "pve" + with _lock: + data = _load() + if len(data["manual"]) >= MAX_MANUAL: + raise LinkError(f"At most {MAX_MANUAL} nodes can be added.") + if any(row.get("url") == url for row in data["manual"]): + raise LinkError("This address is already in the list.") + identifier = secrets.token_hex(6) + data["manual"].append({"id": identifier, "name": name or urllib.parse.urlsplit(url).hostname, + "url": url, "product": product}) + _save(data) + return f"manual:{identifier}" + + +def update_link(key, payload): + if not isinstance(payload, dict): + raise LinkError("Invalid request.") + with _lock: + data = _load() + if key.startswith("manual:"): + row = next((r for r in data["manual"] if f"manual:{r.get('id')}" == key), None) + if row is None: + raise LinkError("Node not found.") + if "name" in payload: + row["name"] = str(payload["name"] or "").strip()[:64] or row["name"] + if payload.get("url"): + row["url"] = normalize_url(payload["url"]) + if payload.get("product") in PRODUCTS: + row["product"] = payload["product"] + elif re.fullmatch(r"(cluster|pbs):[\w.:\-\[\]]{1,253}", key): + override = data["overrides"].setdefault(key, {}) + if "url" in payload: + if payload["url"]: + override["url"] = normalize_url(payload["url"]) + else: + override.pop("url", None) + if "hidden" in payload: + override["hidden"] = bool(payload["hidden"]) + if not override: + data["overrides"].pop(key, None) + else: + raise LinkError("Node not found.") + _save(data) + + +def delete_link(key): + with _lock: + data = _load() + remaining = [r for r in data["manual"] if f"manual:{r.get('id')}" != key] + if len(remaining) == len(data["manual"]): + raise LinkError("Only nodes added by hand can be removed; detected ones can be hidden.") + data["manual"] = remaining + _save(data) diff --git a/AppImage/scripts/tests/test_fwbr_udev_rule_order.py b/AppImage/scripts/tests/test_fwbr_udev_rule_order.py index 70f61b5f..231fc61d 100644 --- a/AppImage/scripts/tests/test_fwbr_udev_rule_order.py +++ b/AppImage/scripts/tests/test_fwbr_udev_rule_order.py @@ -26,18 +26,18 @@ class FwbrUdevRuleOrderTests(unittest.TestCase): def test_auto_post_install_uses_late_rule_and_removes_legacy_rule(self): source = network_optimization_block(AUTO_POST_INSTALL) self.assertIn('local FUNC_VERSION="1.2"', source) - self.assertIn(f"rm -f /etc/udev/rules.d/{OLD_RULE}", source) - self.assertIn(f"cat > /etc/udev/rules.d/{NEW_RULE}", source) + self.assertIn(f"pmx_remove_file /etc/udev/rules.d/{OLD_RULE}", source) + self.assertIn(f"pmx_write_file /etc/udev/rules.d/{NEW_RULE}", source) self.assertIn(f"chmod 0644 /etc/udev/rules.d/{NEW_RULE}", source) - self.assertNotIn(f"cat > /etc/udev/rules.d/{OLD_RULE}", source) + self.assertNotIn(f"pmx_write_file /etc/udev/rules.d/{OLD_RULE}", source) def test_customizable_post_install_uses_late_rule_and_removes_legacy_rule(self): source = network_optimization_block(CUSTOM_POST_INSTALL) self.assertIn('local FUNC_VERSION="1.2"', source) - self.assertIn(f"rm -f /etc/udev/rules.d/{OLD_RULE}", source) - self.assertIn(f"cat > /etc/udev/rules.d/{NEW_RULE}", source) + self.assertIn(f"pmx_remove_file /etc/udev/rules.d/{OLD_RULE}", source) + self.assertIn(f"pmx_write_file /etc/udev/rules.d/{NEW_RULE}", source) self.assertIn(f"chmod 0644 /etc/udev/rules.d/{NEW_RULE}", source) - self.assertNotIn(f"cat > /etc/udev/rules.d/{OLD_RULE}", source) + self.assertNotIn(f"pmx_write_file /etc/udev/rules.d/{OLD_RULE}", source) def test_uninstall_removes_new_and_legacy_rule_names(self): source = UNINSTALL_TOOLS.read_text() diff --git a/AppImage/scripts/tests/test_monitor_links.py b/AppImage/scripts/tests/test_monitor_links.py new file mode 100644 index 00000000..48036393 --- /dev/null +++ b/AppImage/scripts/tests/test_monitor_links.py @@ -0,0 +1,150 @@ +import json +from pathlib import Path +import sys +import tempfile +from types import SimpleNamespace +import unittest +from unittest import mock + +ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(ROOT / "AppImage/scripts")) + +import monitor_links as links + + +class MonitorLinksTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + storage = self.root / "storage.cfg" + storage.write_text("dir: local\n\tpath /var/lib/vz\n\npbs: Lab\n\tdatastore store1\n\tserver 10.0.0.49\n\n" + "pbs: Cloud\n\tdatastore x\n\tserver pbs.example.com\n\tport 8443\n") + patches = [ + mock.patch.object(links, "LINKS_FILE", self.root / "links.json"), + mock.patch.object(links, "SSL_CONFIG_FILE", self.root / "ssl.json"), + mock.patch.object(links, "STORAGE_CFG", storage), + mock.patch.object(links.pbs_manager, "get_platform", return_value={"product": "pve", "hostname": "amd"}), + mock.patch.object(links.pbs_manager, "public_servers", return_value=[]), + ] + for patch in patches: + patch.start() + self.addCleanup(patch.stop) + links._probe_cache.clear() + cluster = [{"type": "cluster", "name": "Casa"}, + {"type": "node", "name": "amd", "ip": "10.0.0.55", "local": 1}, + {"type": "node", "name": "constructor", "ip": "10.0.0.50", "local": 0}] + self.run_patch = mock.patch.object(links.subprocess, "run", + return_value=SimpleNamespace(returncode=0, stdout=json.dumps(cluster))) + self.run_patch.start() + self.addCleanup(self.run_patch.stop) + + def test_normalize_url_keeps_only_the_origin_and_rejects_credentials(self): + self.assertEqual(links.normalize_url("192.168.0.49:8008/dashboard?x=1"), "https://192.168.0.49:8008") + self.assertEqual(links.normalize_url("http://pve.lan"), "http://pve.lan") + for bad in ("", "ftp://host", "https://user:pass@host", "https://host:99999", "javascript:alert(1)"): + with self.subTest(bad=bad), self.assertRaises(links.LinkError): + links.normalize_url(bad) + + def test_discovers_other_cluster_nodes_and_pbs_without_the_local_node(self): + probed = [] + with mock.patch.object(links, "_probe", side_effect=lambda url: probed.append(url) or url.endswith("10.0.0.50:8008")), \ + mock.patch.object(links, "_is_private", side_effect=lambda host: host.startswith("10.")): + result = links.list_links() + by_key = {row["key"]: row for row in result["links"]} + self.assertEqual(result["self"]["name"], "amd") + self.assertNotIn("cluster:amd", by_key) + self.assertEqual(by_key["cluster:constructor"]["status"], "online") + self.assertEqual(by_key["pbs:10.0.0.49"]["native_url"], "https://10.0.0.49:8007") + self.assertEqual(by_key["pbs:10.0.0.49"]["status"], "offline") + # A public PBS is listed with its own interface but never probed unasked. + self.assertEqual(by_key["pbs:pbs.example.com"]["status"], "unknown") + self.assertEqual(by_key["pbs:pbs.example.com"]["native_url"], "https://pbs.example.com:8443") + self.assertFalse(any("example.com" in url for url in probed)) + + def test_pbs_host_lists_only_manual_entries(self): + links.pbs_manager.get_platform.return_value = {"product": "pbs", "hostname": "pbs-lab"} + links.add_link({"name": "amd", "url": "https://10.0.0.55:8008", "product": "pve"}) + with mock.patch.object(links, "_probe", return_value=True): + result = links.list_links() + self.assertEqual([row["name"] for row in result["links"]], ["amd"]) + self.links_run_not_called() + + def links_run_not_called(self): + self.assertFalse(any(call.args and call.args[0][:1] == ["pvesh"] for call in links.subprocess.run.call_args_list)) + + def test_manual_entries_overrides_and_hidden_detected_nodes(self): + key = links.add_link({"name": "lab", "url": "10.0.0.46:8008", "product": "pve"}) + with self.assertRaises(links.LinkError): + links.add_link({"url": "https://10.0.0.46:8008"}) + links.update_link("cluster:constructor", {"url": "https://constructor.lan:8008"}) + links.update_link("pbs:10.0.0.49", {"hidden": True}) + with mock.patch.object(links, "_probe", return_value=False), \ + mock.patch.object(links, "_is_private", return_value=True): + visible = {row["key"]: row for row in links.list_links()["links"]} + everything = {row["key"]: row for row in links.list_links(include_hidden=True)["links"]} + self.assertEqual(visible[key]["url"], "https://10.0.0.46:8008") + self.assertEqual(visible["cluster:constructor"]["url"], "https://constructor.lan:8008") + self.assertNotIn("pbs:10.0.0.49", visible) + self.assertTrue(everything["pbs:10.0.0.49"]["hidden"]) + with self.assertRaises(links.LinkError): + links.delete_link("cluster:constructor") + links.delete_link(key) + self.assertEqual(json.loads((self.root / "links.json").read_text())["manual"], []) + self.assertEqual((self.root / "links.json").stat().st_mode & 0o777, 0o600) + + def test_cached_list_is_served_without_new_probes_and_renewed_when_stale(self): + links._snapshot.update(at=0.0, data=None) + self.addCleanup(links._snapshot.update, at=0.0, data=None) + links.update_link("pbs:10.0.0.49", {"hidden": True}) + with mock.patch.object(links, "_probe", return_value=True) as probe, \ + mock.patch.object(links, "_is_private", return_value=True): + first = links.cached_links() + calls = probe.call_count + again = links.cached_links() + self.assertEqual(probe.call_count, calls) + self.assertEqual(first, again) + self.assertNotIn("pbs:10.0.0.49", [row["key"] for row in first["links"]]) + self.assertIn("pbs:10.0.0.49", [row["key"] for row in links.cached_links(include_hidden=True)["links"]]) + links._snapshot["at"] -= links.PROBE_TTL + 1 + with mock.patch.object(links.threading, "Thread") as thread: + stale = links.cached_links() + self.assertEqual(stale, first) + thread.assert_called_once() + + def test_unknown_keys_are_rejected(self): + for key in ("other:x", "cluster:../../etc", "manual:nope"): + with self.subTest(key=key), self.assertRaises(links.LinkError): + links.update_link(key, {"hidden": True}) + + def test_probe_sends_no_credentials_and_needs_a_monitor_answer(self): + seen = [] + + class Response: + status = 200 + + def __init__(self, body): + self.body = body + + def read(self, _size): + return self.body + + def __enter__(self): + return self + + def __exit__(self, *_): + return False + + def urlopen(request, timeout, context): + seen.append(request) + return Response(b'{"auth_enabled": true}' if "monitor" in request.full_url else b"") + + with mock.patch.object(links.urllib.request, "urlopen", side_effect=urlopen): + self.assertTrue(links._probe("https://monitor:8008")) + self.assertFalse(links._probe("https://other:8008")) + self.assertTrue(all(request.full_url.endswith("/api/auth/status") for request in seen)) + self.assertTrue(all("Authorization" not in request.headers for request in seen)) + + +if __name__ == "__main__": + unittest.main() diff --git a/lang/es.json b/lang/es.json index de4d581e..c0605465 100644 --- a/lang/es.json +++ b/lang/es.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (ruta por defecto de vzdump)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (por defecto en Proxmox)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = sticky bit + rwx para todos. No se necesita un grupo compartido.", + "====== PBS UPDATE COMPLETED ======": "====== ACTUALIZACIÓN PBS COMPLETADA ======", "====== PVE UPDATE COMPLETED ======": "====== ACTUALIZACIÓN PVE COMPLETADA ======", ">> Create a new backup job <<": ">> Crear un trabajo de backup nuevo <<", ">> Save and return <<": ">> Guardar y volver <<", @@ -86,6 +87,7 @@ "A modern wiki and knowledge base for teams": "Una wiki y base de conocimiento moderna para equipos", "A new ProxMenux version is available:": "Una nueva versión de ProxMenux está disponible:", "A new kernel is staged for the next boot:": "Se prepara un nuevo kernel para el siguiente arranque:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "Hay un kernel más reciente instalado. Reinicie antes de la próxima ventana de backups para usarlo.", "A newer version is available:": "Hay una versión más nueva disponible:", "A pending operation exists for": "Existe una operación pendiente", "A pending stack assembly already exists; it is not overwritten": "Ya existe un ensamblado de pila pendiente; no se sobrescribe", @@ -168,6 +170,7 @@ "APT language configuration updated": "Configuración del idioma APT actualizada", "APT language downloads restored": "Descargas del idioma APT restauradas", "APT package lists updated": "Listas de paquetes APT actualizadas", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "La actualización de APT ha fallado. Revise la suscripción y los repositorios de PBS en Administration > Repositories. Log:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "No se pudieron actualizar los índices de APT. Comprueba la suscripción y los repositorios de PBS en su interfaz nativa; no se ha cambiado ninguna fuente.", "APT update failed. Check repository access; NVIDIA installation stopped.": "La actualización de APT ha fallado. Verifica el acceso a los repositorios; la instalación de NVIDIA se ha detenido.", "Aborted": "Abortado", @@ -536,6 +539,7 @@ "Available Borg targets:": "Destinos Borg disponibles:", "Available Disks on Host": "Discos disponibles en el host", "Available ISO Images": "Imágenes ISO disponibles", + "Available PBS Version": "Versión PBS disponible", "Available PBS repositories:": "Repositorios de PBS disponibles:", "Available PVE Version": "Versión PVE disponible", "Available Shares:": "Recursos compartidos disponibles:", @@ -1518,6 +1522,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Actual inventario NVIDIA resuelto: un refresco es necesario", "Current NVIDIA inventory resolved: no refresh is required": "Inventario NVIDIA actual resuelto: ningún refresco es necesario", "Current Network Configuration": "Configuración de red actual", + "Current PBS Version": "Versión PBS actual", "Current PVE Version": "Versión PVE actual", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Los scripts de host ProxMenux actuales registran recursos compartidos remotos como almacenamientos en Proxmox utilizando pvesm.", "Current ProxMenux version:": "Versión actual de ProxMenux:", @@ -1683,6 +1688,7 @@ "Detected Coral hardware:": "Hardware Coral detectado:", "Detected GPU(s):": "GPU detectadas:", "Detected Intel CPU": "CPU Intel detectada", + "Detected Proxmox Backup Server": "Proxmox Backup Server detectado", "Detected Proxmox VE": "Proxmox VE detectado", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Proxmox VE 8.x detectado: instalación de Ceph Squid para Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Detectado Proxmox VE 9.x - Instalación de Ceph Squid para Debian Trixie", @@ -1843,6 +1849,7 @@ "Do you want to proceed with the system update?": "¿Quieres continuar con la actualización del sistema?", "Do you want to proceed?": "¿Quieres continuar?", "Do you want to reboot now?": "¿Quieres reiniciar ahora?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "¿Desea eliminar el banner de suscripción de Proxmox de la interfaz web de Proxmox Backup Server?", "Do you want to remove the stale entry from vfio.conf and continue?": "¿Quiere eliminar la entrada obsoleta de vfio.conf y continuar?", "Do you want to remove the stale entry from vfio.conf?": "¿Quiere eliminar la entrada obsoleta de vfio.conf?", "Do you want to restart the network service now to apply changes?": "¿Quieres reiniciar el servicio de red ahora para aplicar los cambios?", @@ -2267,6 +2274,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "No se pudo clonar el repositorio log2ram. Compruebe /tmp/log2ram_install.log", "Failed to configure EFI disk": "No se pudo configurar el disco EFI", "Failed to configure IOMMU automatically.": "No se pudo configurar IOMMU automáticamente.", + "Failed to configure Proxmox Backup Server repositories": "No se han podido configurar los repositorios de Proxmox Backup Server", "Failed to configure Proxmox repositories": "No se pudieron configurar los repositorios de Proxmox", "Failed to configure TPM device in VM": "No se pudo configurar el dispositivo TPM en VM", "Failed to configure repositories.": "No se pudieron configurar los repositorios.", @@ -4152,6 +4160,7 @@ "No active Proxmox storage accepts OCI template archives.": "Ningún almacenamiento activo de Proxmox acepta archivos de plantilla OCI.", "No active exports or showmount not available": "No hay exportaciones activas o showmount no disponible", "No active session": "Ninguna sesión activa", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "No hay suscripción activa ni un repositorio de PBS utilizable. El modo no interactivo no puede cambiar las fuentes de APT; configúrelas en Administration > Repositories.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "No hay suscripción activa ni un repositorio de PVE utilizable. El modo no interactivo no puede cambiar las fuentes de APT; configúralas en Nodo > Actualizaciones > Repositorios.", "No additional GPU can be added.": "No se puede agregar ninguna GPU adicional.", "No additional device needs to be added.": "No es necesario agregar ningún dispositivo adicional.", @@ -4525,7 +4534,7 @@ "PBS extraction failed": "Error en la extracción de PBS", "PBS host or IP address:": "Host de PBS o dirección IP:", "PBS package update": "Actualización de paquetes de PBS", - "PBS package upgrade completed. Check whether a reboot is required before your next backup window.": "Actualización de paquetes de PBS completada. Comprueba si es necesario reiniciar antes del próximo período de copias de seguridad.", + "PBS package upgrade completed. Check whether a reboot is required before your next backup window.": "Actualización de paquetes de PBS completada. Comprueba si hace falta reiniciar antes de la próxima ventana de backups.", "PBS package upgrade failed. Review the APT output before retrying.": "La actualización de paquetes de PBS ha fallado. Revisa la salida de APT antes de volver a intentarlo.", "PBS upload": "Subida a PBS", "PBS version and packages": "Versión y paquetes de PBS", @@ -4866,6 +4875,9 @@ "Proxmox 9 system update allready": "Actualización del sistema Proxmox 9 ya", "Proxmox APT repositories configured": "Repositorios Proxmox APT configurados", "Proxmox Backup Server (PBS) destinations": "Destinos de Proxmox Backup Server (PBS)", + "Proxmox Backup Server APT repositories configured": "Repositorios APT de Proxmox Backup Server configurados", + "Proxmox Backup Server Update": "Actualización de Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Actualización de Proxmox Backup Server completada.", "Proxmox CIFS Storage Status:": "Estado de almacenamiento CIFS de Proxmox:", "Proxmox NFS Storage Status:": "Estado de almacenamiento NFS de Proxmox:", "Proxmox System Update": "Actualización del sistema Proxmox", @@ -5218,6 +5230,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "El asistente de repositorios no está disponible. La instalación de NVIDIA se ha detenido.", "Repository policy returned an unexpected result.": "La política de repositorios ha devuelto un resultado inesperado.", "Repository ready.": "Repositorio listo.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Las fuentes de repositorio han cambiado, pero la actualización de la lista de paquetes de APT ha fallado. Operación detenida; las fuentes no se han revertido. Revise Administration > Repositories y vuelva a ejecutar apt-get update antes de continuar.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Las fuentes de los repositorios han cambiado, pero la actualización de la lista de paquetes de APT ha fallado. La operación se ha detenido; las fuentes no se han revertido. Revisa Nodo > Actualizaciones > Repositorios y vuelve a ejecutar apt-get update antes de continuar.", "Repository switch declined; no APT source changed.": "Cambio de repositorio rechazado; no se ha modificado ninguna fuente de APT.", "Repository:": "Repositorio:", @@ -6758,6 +6771,7 @@ "This CIFS share is mounted with restrictive permissions.": "Este recurso compartido CIFS está montado con permisos restrictivos.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU se considera incompatible con el passthrough de GPU a una VM en ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este recurso compartido NFS está completamente restringido: ni siquiera la raíz del host puede escribir en él.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Este Proxmox Backup Server no tiene una suscripción activa, ¿cambiar al repositorio sin suscripción? Se deshabilitará la fuente Enterprise de PBS, que no es accesible.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Esta máquina virtual requiere pasos de instalación adicionales; consulte la guía de instalación en:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Esta cuenta es de {registry}. Escribe una imagen de ese registro.", "This action will:": "Esta acción:", @@ -6841,6 +6855,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Este script no se puede ejecutar desde la terminal web de Proxmox.", "This script must be run as root.": "Este script debe ejecutarse como root.", "This script must be run on a Proxmox host.": "Este script debe ejecutarse en un host Proxmox.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Este script aplicará las siguientes optimizaciones y ajustes avanzados a su Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Este script aplicará las siguientes optimizaciones y ajustes avanzados a su servidor Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Este script actualizará su sistema Proxmox VE con opciones avanzadas:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Esta sesión se ejecuta en el terminal del Monitor. Ejecutarlo desde aquí cortaría la conexión a mitad de la instalación y dejaría el cambio en un estado roto.", @@ -7165,6 +7180,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Asistente de actualización: Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Actualización cancelada por el usuario", "Upgrade one node at a time": "Actualice un nodo a la vez", + "Upgrade the system, offering the": "Actualizar el sistema, ofreciendo el", "Upgrade to Proxmox VE 9": "Actualice a Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Actualizando las utilidades de NVIDIA (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "¿Cargar una copia cifrada de la clave a PBS para poder recuperarla en un host reinstalado con solo una frase de contraseña?", @@ -8084,6 +8100,7 @@ "was": "era", "was installed, but the kernel reports:": "se instaló, pero el kernel informa:", "when finished": "al terminar", + "when the server has no subscription (you will be asked)": "cuando el servidor no tiene suscripción (se le preguntará)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "volverá a vincular la GPU a vfio-pci en el próximo reinicio, rompiendo el controlador que está a punto de instalarse.", "wipefs failed on": "los borrados fallaron", "with": "con", diff --git a/scripts/global/remove-banner-pve-v3.sh b/scripts/global/remove-banner-pve-v3.sh index 4ddf7af2..92df1028 100644 --- a/scripts/global/remove-banner-pve-v3.sh +++ b/scripts/global/remove-banner-pve-v3.sh @@ -283,6 +283,10 @@ reload_services() { systemctl is-active --quiet pvedaemon 2>/dev/null && { systemctl reload pvedaemon 2>/dev/null || true } + # Proxmox Backup Server serves the same toolkit from its own proxy. + systemctl is-active --quiet proxmox-backup-proxy 2>/dev/null && { + systemctl reload proxmox-backup-proxy 2>/dev/null || systemctl restart proxmox-backup-proxy 2>/dev/null || true + } } main() { @@ -316,9 +320,12 @@ EOFAPT # Main function to remove subscription banner remove_subscription_banner_v3() { local pve_version - pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1 || echo "unknown") - - msg_info "$(translate "Detected Proxmox VE") ${pve_version} - $(translate "applying minimal banner patch")" + if command -v pveversion >/dev/null 2>&1; then + pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1 || echo "unknown") + msg_info "$(translate "Detected Proxmox VE") ${pve_version} - $(translate "applying minimal banner patch")" + else + msg_info "$(translate "Detected Proxmox Backup Server") - $(translate "applying minimal banner patch")" + fi diff --git a/scripts/global/repository-functions.sh b/scripts/global/repository-functions.sh index d6114085..7a2f3b86 100644 --- a/scripts/global/repository-functions.sh +++ b/scripts/global/repository-functions.sh @@ -62,3 +62,48 @@ ensure_repositories() { fi return 0 } + +# The no-subscription switch of a Proxmox Backup Server: the same policy and +# API as on PVE, offered only when the host has no active subscription. +ensure_pbs_repositories() { + local suite decision refresh_status + if [[ "$(pmx_platform_field supported)" != true ]]; then + msg_error "$(translate 'This operation supports PBS 3 on Debian 12 and PBS 4 on Debian 13.')" + return 1 + fi + suite=$(pmx_platform_field codename) + decision=$(repository_policy plan "$suite" pbs) || return 1 + case "$decision" in + preserve) return 0 ;; + offer) ;; + *) msg_error "$(translate 'Repository policy returned an unexpected result.')"; return 1 ;; + esac + if ! declare -F hybrid_yesno >/dev/null || { [[ ! -t 0 ]] && ! { declare -F is_web_mode >/dev/null && is_web_mode; }; }; then + msg_error "$(translate 'No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.')" + return 1 + fi + if ! hybrid_yesno "$(translate 'Proxmox repository')" \ + "$(translate 'This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.')" 14 90; then + msg_error "$(translate 'Repository switch declined; no APT source changed.')" + return 1 + fi + decision=$(repository_policy apply "$suite" pbs) || return 1 + [[ "$decision" == changed || "$decision" == preserve ]] || return 1 + if [[ "$decision" == changed ]]; then + if declare -F pmx_record_execution >/dev/null; then + local journal_function="${PMX_JOURNAL_FUNCTION:-}" journal_version="${PMX_JOURNAL_VERSION:-}" + pmx_journal_context "ensure_pbs_repositories" "" "${PMX_JOURNAL_SOURCE:-}" + pmx_record_execution "Switch to the PBS no-subscription repository" \ + "proxmox-backup-debug api set /nodes/localhost/apt/repositories --handle no-subscription" + PMX_JOURNAL_FUNCTION="$journal_function" PMX_JOURNAL_VERSION="$journal_version" + fi + if apt-get update; then + return 0 + else + refresh_status=$? + msg_error "$(translate 'Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.')" + return "$refresh_status" + fi + fi + return 0 +} diff --git a/scripts/global/repository_policy.py b/scripts/global/repository_policy.py index 7e0dca70..95f40d68 100644 --- a/scripts/global/repository_policy.py +++ b/scripts/global/repository_policy.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -"""Proxmox repository policy via its own parsed APT repository API. +"""Proxmox VE and Proxmox Backup Server repository policy via their own parsed APT +repository API. Only `notfound` or `expired` permits an offered switch. Never print raw subscription or repository API responses: they can contain subscription keys or credentials. @@ -12,6 +13,19 @@ import sys ENDPOINT = '/nodes/localhost/apt/repositories' PVE_CHANNELS = {'pve-enterprise', 'pve-no-subscription', 'pve-test', 'pvetest'} +# Proxmox VE and Proxmox Backup Server share the repository API; each has its +# own command line, channels and place in the web interface. +PRODUCTS = { + 'pve': {'api': ['pvesh'], 'name': 'Proxmox VE', 'short': 'PVE', + 'channels': PVE_CHANNELS, 'enterprise': 'pve-enterprise', 'public': 'pve-no-subscription', + 'test': {'pve-no-subscription', 'pve-test', 'pvetest'}, + 'ui': 'Node > Updates > Repositories', 'ceph': True}, + 'pbs': {'api': ['proxmox-backup-debug', 'api'], 'name': 'Proxmox Backup Server', 'short': 'PBS', + 'channels': {'pbs-enterprise', 'pbs-no-subscription', 'pbs-test', 'pbstest'}, + 'enterprise': 'pbs-enterprise', 'public': 'pbs-no-subscription', + 'test': {'pbs-no-subscription', 'pbs-test', 'pbstest'}, + 'ui': 'Administration > Repositories', 'ceph': False}, +} def run(args): @@ -21,7 +35,16 @@ def run(args): raise ValueError(f'Unable to query or change Proxmox repository state ({args[0]}). Check the service and retry.') from exc -def subscription_status(): +def subscription_status(product='pve'): + if product == 'pbs': + # Only the status field is read; the answer also carries the server ID. + try: + status = json.loads(run(['proxmox-backup-manager', 'subscription', 'get', '--output-format', 'json'])).get('status') + except (ValueError, AttributeError): + status = None + if status not in ('active', 'notfound', 'expired'): + raise ValueError('Subscription status is not unambiguously active, notfound or expired. Check proxmox-backup-manager subscription get locally; no repository changed.') + return status # pvesubscription's CLI get printer emits sorted "key: value" lines, # including a secret key and server ID. Only parse the exact status line. output = run(['pvesubscription', 'get']) @@ -31,9 +54,10 @@ def subscription_status(): return statuses[0] -def repository_state(suite): +def repository_state(suite, product='pve'): + p = PRODUCTS[product] try: - data = json.loads(run(['pvesh', 'get', ENDPOINT, '--output-format', 'json'])) + data = json.loads(run([*p['api'], 'get', ENDPOINT, '--output-format', 'json'])) if not isinstance(data, dict) or not isinstance(data['files'], list) or not isinstance(data['errors'], list) or not isinstance(data['digest'], str) or not isinstance(data['standard-repos'], list): raise ValueError() if data['errors']: @@ -86,13 +110,14 @@ def repository_state(suite): entries.append((file['path'], index, row)) return data, entries except (KeyError, TypeError, ValueError, IndexError) as exc: - raise ValueError('Proxmox APT repository inventory is invalid or reports parse errors. Fix sources in Node > Updates > Repositories; no repository changed.') from exc + raise ValueError(f"Proxmox APT repository inventory is invalid or reports parse errors. Fix sources in {p['ui']}; no repository changed.") from exc -def evaluate(suite, apply=False): +def evaluate(suite, apply=False, product='pve'): if suite not in ('bookworm', 'trixie'): raise ValueError('Unsupported Proxmox suite; no repository changed.') - data, entries = repository_state(suite) + p = PRODUCTS[product] + data, entries = repository_state(suite, product) pve = [] ceph = [] debian = False @@ -102,40 +127,40 @@ def evaluate(suite, apply=False): if not row['Enabled'] or 'deb' not in row['Types']: continue components = set(row['Components']) - if components & PVE_CHANNELS: + if components & p['channels']: if suite not in row['Suites']: - wrong_pve.append(f"{' '.join(sorted(components & PVE_CHANNELS))} in {path} (suite {' '.join(row['Suites'])})") + wrong_pve.append(f"{' '.join(sorted(components & p['channels']))} in {path} (suite {' '.join(row['Suites'])})") pve.append((path, index, row)) - if 'main' in components and suite in row['Suites'] and not components & PVE_CHANNELS: + if 'main' in components and suite in row['Suites'] and not components & p['channels']: debian = True - if 'enterprise' in components and any('/ceph-' in uri for uri in row['URIs']): + if p['ceph'] and 'enterprise' in components and any('/ceph-' in uri for uri in row['URIs']): if suite not in row['Suites']: wrong_ceph.append(f"{path} (suite {' '.join(row['Suites'])})") ceph.append((path, index, row)) # Name the stale sources, never their URIs: those can carry credentials. if wrong_pve: - raise ValueError(f"This host runs Proxmox VE on {suite}, but these Proxmox VE repositories are for another suite: " - f"{'; '.join(wrong_pve)}. Disable or remove them in Node > Updates > Repositories and run the update again; no repository changed.") + raise ValueError(f"This host runs {p['name']} on {suite}, but these {p['name']} repositories are for another suite: " + f"{'; '.join(wrong_pve)}. Disable or remove them in {p['ui']} and run the update again; no repository changed.") if wrong_ceph: raise ValueError(f"This host runs Proxmox VE on {suite}, but these Enterprise Ceph repositories are for another suite: " f"{'; '.join(wrong_ceph)}. Correct them in Node > Updates > Repositories before switching; no repository changed.") - if any(set(row['Components']) & {'pve-no-subscription', 'pve-test', 'pvetest'} for _, _, row in pve): + if any(set(row['Components']) & p['test'] for _, _, row in pve): return 'preserve' # Entitlement lookup is only needed when considering a switch. Existing # public/test channels are the operator's choice, even if lookup fails. - status = subscription_status() # fail closed before any write + status = subscription_status(product) # fail closed before any write if status == 'active': if not pve: - raise ValueError('Host has an active subscription but no active PVE repository. Configure its Enterprise source in Node > Updates > Repositories; no repository changed.') + raise ValueError(f"Host has an active subscription but no active {p['short']} repository. Configure its Enterprise source in {p['ui']}; no repository changed.") return 'preserve' if not debian: raise ValueError('No active Debian base repository for this suite; configure it in the Proxmox repository UI before continuing.') - # No active PVE source or only inaccessible Enterprise. A mixed stanza + # No active product source or only inaccessible Enterprise. A mixed stanza # cannot be disabled without also disabling an unrelated component. - disable = [item for item in pve if 'pve-enterprise' in item[2]['Components']] + ceph + disable = [item for item in pve if p['enterprise'] in item[2]['Components']] + ceph for _, _, row in disable: - expected = {'pve-enterprise'} if 'pve-enterprise' in row['Components'] else {'enterprise'} + expected = {p['enterprise']} if p['enterprise'] in row['Components'] else {'enterprise'} if set(row['Components']) != expected: raise ValueError('Enterprise shares an APT stanza with other components. Split it in the Proxmox repository UI; no repository changed.') handles = [r.get('handle') for r in data['standard-repos'] if r.get('handle') == 'no-subscription'] @@ -149,9 +174,9 @@ def evaluate(suite, apply=False): try: for path, index, original in disable: next(file for file in expected if file['path'] == path)['repositories'][index]['Enabled'] = False - run(['pvesh', 'create', ENDPOINT, '--path', path, '--index', str(index), + run([*p['api'], 'create', ENDPOINT, '--path', path, '--index', str(index), '--enabled', '0', '--digest', data['digest']]) - refreshed, current = repository_state(suite) + refreshed, current = repository_state(suite, product) # Per-file digests change when Proxmox serializes the disabled entry. inventory = lambda files: sorted( ({key: value for key, value in file.items() if key != 'digest'} for file in files), @@ -159,23 +184,25 @@ def evaluate(suite, apply=False): if inventory(refreshed['files']) != inventory(expected): raise ValueError('Could not verify the expected repository inventory after Enterprise disable; possible concurrent edit. Inspect the Proxmox repository UI before retrying.') data = refreshed - run(['pvesh', 'set', ENDPOINT, '--handle', 'no-subscription', '--digest', data['digest']]) - _, current = repository_state(suite) + run([*p['api'], 'set', ENDPOINT, '--handle', 'no-subscription', '--digest', data['digest']]) + _, current = repository_state(suite, product) if not any(row['Enabled'] and 'deb' in row['Types'] and suite in row['Suites'] - and 'pve-no-subscription' in row['Components'] for _, _, row in current) \ + and p['public'] in row['Components'] for _, _, row in current) \ or any(p == path and i == index and row['Enabled'] for path, index, _ in disable for p, i, row in current): raise ValueError('Could not verify the switched repositories; inspect the Proxmox repository UI before retrying.') except ValueError as exc: - raise ValueError('Could not complete or verify the repository switch; changes may be partial and repository state is unknown. Inspect Node > Updates > Repositories before retrying; no automatic rollback was attempted.') from exc + raise ValueError(f"Could not complete or verify the repository switch; changes may be partial and repository state is unknown. Inspect {p['ui']} before retrying; no automatic rollback was attempted.") from exc return 'changed' def main(): try: - if len(sys.argv) != 3 or sys.argv[1] not in ('plan', 'apply'): - raise ValueError('Usage: repository_policy.py plan|apply bookworm|trixie') - print(evaluate(sys.argv[2], apply=sys.argv[1] == 'apply')) + if len(sys.argv) not in (3, 4) or sys.argv[1] not in ('plan', 'apply') \ + or (len(sys.argv) == 4 and sys.argv[3] not in PRODUCTS): + raise ValueError('Usage: repository_policy.py plan|apply bookworm|trixie [pve|pbs]') + product = sys.argv[3] if len(sys.argv) == 4 else 'pve' + print(evaluate(sys.argv[2], apply=sys.argv[1] == 'apply', product=product)) except ValueError as exc: print(str(exc), file=sys.stderr) return 1 diff --git a/scripts/global/update-pbs.sh b/scripts/global/update-pbs.sh new file mode 100644 index 00000000..54574676 --- /dev/null +++ b/scripts/global/update-pbs.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# ========================================================== +# Proxmox Backup Server Update Script (post-install flows) +# ========================================================== + +LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +APT_ENV="env DEBIAN_FRONTEND=noninteractive LC_ALL=C LANG=C" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi +if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then + source "$LOCAL_SCRIPTS/global/pmx_journal.sh" +fi +source "$LOCAL_SCRIPTS/global/common-functions.sh" +source "$LOCAL_SCRIPTS/global/repository-functions.sh" + +load_language +initialize_cache + +update_pbs() { + local FUNC_VERSION="1.0" + pmx_journal_context "update_pbs" "$FUNC_VERSION" + + if ! pmx_is_pbs || [[ "$(pmx_platform_field supported)" != true ]]; then + msg_error "$(translate 'This operation supports PBS 3 on Debian 12 and PBS 4 on Debian 13.')" + return 1 + fi + + local start_time + start_time=$(date +%s) + local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log" + local screen_capture="/tmp/proxmenux_screen_capture_$$.txt" + local OS_CODENAME + OS_CODENAME=$(pmx_platform_field codename) + + { + echo "=== ProxMenux — Proxmox Backup Server update ===" + echo "Started: $(date -Iseconds)" + echo "Host: $(hostname)" + echo "Running: proxmox-backup-server $(dpkg-query -W -f='${Version}' proxmox-backup-server 2>/dev/null)" + } > "$log_file" + + if ! ensure_pbs_repositories; then + msg_error "$(translate "Failed to configure Proxmox Backup Server repositories")" + rm -f "$screen_capture" + return 1 + fi + + local available_space + available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}') + if [ "$available_space" -lt 1024 ]; then + msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")" + return 1 + fi + + local update_output update_exit_code + update_output=$(apt-get -o APT::Update::Error-Mode=any update 2>&1) + update_exit_code=$? + { + echo + echo "--- apt-get update (exit $update_exit_code) ---" + printf '%s\n' "$update_output" + } >> "$log_file" + if [ $update_exit_code -ne 0 ]; then + msg_error "$(translate "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:") $log_file" + return 1 + fi + msg_ok "$(translate "Package lists updated successfully")" | tee -a "$screen_capture" + + local current_version available_version + current_version=$(dpkg-query -W -f='${Version}' proxmox-backup-server 2>/dev/null) + available_version=$(apt-cache policy proxmox-backup-server 2>/dev/null | awk '/Candidate:/ {print $2; exit}') + + local upgradable upgradable_raw upgradable_list security_updates + upgradable_raw=$($APT_ENV apt list --upgradable 2>/dev/null \ + | sed '1d' \ + | sed '/^\s*$/d') + upgradable=$(printf '%s' "$upgradable_raw" | grep -c . ) + security_updates=$(printf '%s\n' "$upgradable_raw" | grep -ci '\-security') + upgradable_list=$(printf '%s\n' "$upgradable_raw" | pmx_format_upgradable) + + { + echo + echo "--- Packages to upgrade ($upgradable) ---" + [ "$upgradable" -gt 0 ] && printf '%s\n' "$upgradable_list" + } >> "$log_file" + + local menu_text + menu_text="$(translate "System Update Information")\n\n" + menu_text+="$(translate "Current PBS Version"): $current_version\n" + if [ -n "$available_version" ] && [ "$available_version" != "$current_version" ]; then + menu_text+="$(translate "Available PBS Version"): $available_version\n" + fi + menu_text+="\n$(translate "Package Updates Available"): $upgradable\n" + menu_text+="$(translate "Security Updates"): $security_updates\n\n" + + if [ "$upgradable" -eq 0 ]; then + menu_text+="$(translate "System is already up to date")" + whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70 + clear + show_proxmenux_logo + msg_title "$(translate "$SCRIPT_TITLE")" + cat "$screen_capture" + msg_ok "$(translate "System is already up to date. No update needed.")" + echo -e "\nSystem is already up to date." >> "$log_file" + rm -f "$screen_capture" + return 0 + fi + + menu_text+="$(translate "Packages to be upgraded"):\n$upgradable_list\n\n" + menu_text+="$(translate "Do you want to proceed with the system update?")" + local proceed=0 + whiptail --title "$(translate "Proxmox Backup Server Update")" --scrolltext --yesno "$menu_text" 24 78 || proceed=1 + + clear + show_proxmenux_logo + msg_title "$(translate "$SCRIPT_TITLE")" + cat "$screen_capture" + + if [ $proceed -ne 0 ]; then + msg_info2 "$(translate "Update cancelled by user")" + rm -f "$screen_capture" + return 0 + fi + + pmx_record_execution "Upgrade Proxmox Backup Server packages" "apt -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold full-upgrade" + local dpkg_mark upgrade_exit_code + dpkg_mark=$(date '+%Y-%m-%d %H:%M:%S') + echo -e "\n--- apt full-upgrade ---" >> "$log_file" + DEBIAN_FRONTEND=noninteractive apt -y \ + -o Dpkg::Options::='--force-confdef' \ + -o Dpkg::Options::='--force-confold' \ + full-upgrade 2> >(tee -a "$log_file" >&2) + upgrade_exit_code=$? + echo -e + { + echo + echo "--- Packages changed (exit $upgrade_exit_code) ---" + pmx_dpkg_changes_since "$dpkg_mark" + } >> "$log_file" + + clear + show_proxmenux_logo + msg_title "$(translate "$SCRIPT_TITLE")" + cat "$screen_capture" + rm -f "$screen_capture" + + if [ $upgrade_exit_code -ne 0 ]; then + msg_error "$(translate "System upgrade failed. Check log: $log_file")" + echo "Finished: $(date -Iseconds) — upgrade failed (exit $upgrade_exit_code)" >> "$log_file" + return 1 + fi + + pmx_record_execution "Remove unused packages" "apt-get -y autoremove" + apt-get -y autoremove > /dev/null 2>&1 || true + apt-get -y autoclean > /dev/null 2>&1 || true + msg_ok "$(translate "Cleanup finished")" + + local end_time duration minutes seconds + end_time=$(date +%s) + duration=$((end_time - start_time)) + minutes=$((duration / 60)) + seconds=$((duration % 60)) + + echo -e "${TAB}${BGN}$(translate "====== PBS UPDATE COMPLETED ======")${CL}" + echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}" + echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}" + echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}" + echo -e "${TAB}${GN}🗄️ $(translate "Proxmox Backup Server")${CL}: ${BL}$(dpkg-query -W -f='${Version}' proxmox-backup-server 2>/dev/null) (Debian $OS_CODENAME)${CL}" + + { + echo + echo "Finished: $(date -Iseconds) — ${minutes}m ${seconds}s" + echo "Running: proxmox-backup-server $(dpkg-query -W -f='${Version}' proxmox-backup-server 2>/dev/null)" + } >> "$log_file" + + if [ -f /var/run/reboot-required ] || [ "$(uname -r)" != "$(ls -1 /boot/vmlinuz-* 2>/dev/null | sed 's|/boot/vmlinuz-||' | sort -V | tail -1)" ]; then + msg_warn "$(translate "A newer kernel is installed. Reboot before the next backup window to use it.")" + fi + msg_ok "$(translate "Proxmox Backup Server update completed.")" +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + update_pbs +fi diff --git a/scripts/menus/menu_post_install.sh b/scripts/menus/menu_post_install.sh index 447583e7..d4e63cda 100644 --- a/scripts/menus/menu_post_install.sh +++ b/scripts/menus/menu_post_install.sh @@ -28,6 +28,12 @@ fi load_language initialize_cache +PMX_ON_PBS=false +if [[ -f "$LOCAL_SCRIPTS/global/platform-functions.sh" ]]; then + source "$LOCAL_SCRIPTS/global/platform-functions.sh" + pmx_is_pbs && PMX_ON_PBS=true +fi + # ========================================================== confirm_and_run() { local name="$1" @@ -55,8 +61,13 @@ confirm_automated_script() { local script_info="" - script_info+="$(translate "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server"):\n\n" - script_info+="• $(translate "Configure") \Z4free repositories\Z0 $(translate "and upgrade the system (disables the enterprise repo)")\n" + if [[ "$PMX_ON_PBS" == true ]]; then + script_info+="$(translate "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server"):\n\n" + script_info+="• $(translate "Upgrade the system, offering the") \Z4no-subscription repository\Z0 $(translate "when the server has no subscription (you will be asked)")\n" + else + script_info+="$(translate "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server"):\n\n" + script_info+="• $(translate "Configure") \Z4free repositories\Z0 $(translate "and upgrade the system (disables the enterprise repo)")\n" + fi script_info+="• $(translate "Optionally remove") \Z4subscription banner\Z0 $(translate "from Proxmox web interface (you will be asked)")\n" script_info+="• $(translate "Optimize") \Z4memory\Z0, \Z4kernel\Z0, $(translate "and") \Z4network\Z0 $(translate "for better performance and stability")\n" script_info+="• $(translate "Install and configure") \Z4Log2RAM\Z0 $(translate "(only on SSD/NVMe) to protect your disk")\n" @@ -229,6 +240,12 @@ declare -a COMMUNITY_SCRIPTS=( "Proxmox VE Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\"" "Proxmox VE Microcode|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/microcode.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\"" ) +if [[ "$PMX_ON_PBS" == true ]]; then + COMMUNITY_SCRIPTS=( + "Proxmox Backup Server Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pbs-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\"" + "Proxmox Backup Server Microcode|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/pbs-microcode.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\"" + ) +fi # ========================================================== @@ -271,6 +288,12 @@ post_install_label() { "Proxmox VE Microcode") echo "Proxmox VE Microcode" ;; + "Proxmox Backup Server Post Install") + echo "Proxmox Backup Server Post Install" + ;; + "Proxmox Backup Server Microcode") + echo "Proxmox Backup Server Microcode" + ;; "Community Scripts") translate "Community Scripts" ;; diff --git a/scripts/menus/pbs_menu.sh b/scripts/menus/pbs_menu.sh index 86f30ca2..99a8d76c 100644 --- a/scripts/menus/pbs_menu.sh +++ b/scripts/menus/pbs_menu.sh @@ -2,8 +2,8 @@ # ========================================================== # ProxMenux - Proxmox Backup Server menu # ========================================================== -# PBS exposes compatible host tools; PVE guest/repository optimizations -# stay in the PVE menu. +# PBS exposes compatible host tools and its own post-install profile; +# PVE guest optimizations stay in the PVE menu. # ========================================================== BASE_DIR="/usr/local/share/proxmenux" LOCAL_SCRIPTS="$BASE_DIR/scripts" @@ -31,20 +31,22 @@ pbs_show() { while true; do choice=$(dialog --clear --backtitle "ProxMenux" --title "Proxmox Backup Server" \ - --menu "$(translate 'Select an option:')" 18 75 8 \ - 1 "$(translate 'PBS version and packages')" \ - 2 "$(translate 'PBS datastores')" \ - 3 "$(translate 'Recent PBS tasks')" \ - 4 "$(translate 'System Utilities Installer')" \ - 5 "$(translate 'Update PBS using configured repositories')" \ + --menu "$(translate 'Select an option:')" 19 75 9 \ + 1 "$(translate "Settings post-install Proxmox")" \ + 2 "$(translate 'PBS version and packages')" \ + 3 "$(translate 'PBS datastores')" \ + 4 "$(translate 'Recent PBS tasks')" \ + 5 "$(translate 'System Utilities Installer')" \ + 6 "$(translate 'Update PBS using configured repositories')" \ s "$(translate 'Settings')" \ 0 "$(translate 'Exit')" 2>&1 >/dev/tty) || break case "$choice" in - 1) pbs_show "$(translate 'PBS version and packages')" proxmox-backup-manager versions --verbose ;; - 2) pbs_show "$(translate 'PBS datastores')" proxmox-backup-manager datastore list ;; - 3) pbs_show "$(translate 'Recent PBS tasks')" proxmox-backup-manager task list --all true --limit 20 ;; - 4) bash "$LOCAL_SCRIPTS/utilities/system_utils.sh" ;; - 5) bash "$LOCAL_SCRIPTS/utilities/pbs_update.sh" ;; + 1) exec bash "$LOCAL_SCRIPTS/menus/menu_post_install.sh" ;; + 2) pbs_show "$(translate 'PBS version and packages')" proxmox-backup-manager versions --verbose ;; + 3) pbs_show "$(translate 'PBS datastores')" proxmox-backup-manager datastore list ;; + 4) pbs_show "$(translate 'Recent PBS tasks')" proxmox-backup-manager task list --all true --limit 20 ;; + 5) bash "$LOCAL_SCRIPTS/utilities/system_utils.sh" ;; + 6) bash "$LOCAL_SCRIPTS/utilities/pbs_update.sh" ;; s) exec bash "$LOCAL_SCRIPTS/menus/config_menu.sh" ;; 0) break ;; esac diff --git a/scripts/post_install/auto_post_install.sh b/scripts/post_install/auto_post_install.sh index 25e6289f..83f84a82 100644 --- a/scripts/post_install/auto_post_install.sh +++ b/scripts/post_install/auto_post_install.sh @@ -59,7 +59,12 @@ initialize_cache if [[ -f "$LOCAL_SCRIPTS/global/platform-functions.sh" ]]; then source "$LOCAL_SCRIPTS/global/platform-functions.sh" - pmx_require_pve || exit 1 +fi +# Proxmox Backup Server runs the same host optimizations; repositories, +# package upgrade and the subscription notice follow its own tooling. +PMX_ON_PBS=false +if declare -F pmx_is_pbs >/dev/null && pmx_is_pbs; then + PMX_ON_PBS=true fi # Global variables @@ -142,6 +147,10 @@ $(translate "Do you want to continue anyway?")" 13 70 apt_upgrade() { + if [[ "$PMX_ON_PBS" == true ]]; then + bash "$LOCAL_SCRIPTS/global/update-pbs.sh" + return + fi local pve_version pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1) @@ -171,6 +180,20 @@ apt_upgrade() { remove_subscription_banner() { local FUNC_VERSION="1.0" # description: Patch the Proxmox web UI to suppress the subscription dialog and register a successful patch. + if [[ "$PMX_ON_PBS" == true ]]; then + stop_spinner + if ! whiptail --title "Proxmox Backup Server Subscription Banner Removal" \ + --yesno "$(translate "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?")\n\n$(translate "Attention: Removing the subscription banner may cause issues in the web interface after a future update.")\n\n$(translate "If this happens, you can restore the backup from the 'Subscription Banner Removal' option in 'Uninstall optimizations'.")\n\n$(translate "Are you sure you want to continue?")" 14 75; then + msg_warn "Banner removal cancelled by user." + return 1 + fi + if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then + msg_error "$(translate "Subscription banner removal failed")" + return 1 + fi + register_tool "subscription_banner" true "$FUNC_VERSION" + return 0 + fi local pve_version pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1) @@ -225,6 +248,17 @@ setup_proxmox_repositories() { msg_info2 "$(translate "Configuring Proxmox APT repositories...")" + if [[ "$PMX_ON_PBS" == true ]]; then + if ! ensure_pbs_repositories; then + msg_error "$(translate "Failed to configure Proxmox Backup Server repositories")" + register_tool "proxmox_repos" false "$FUNC_VERSION" + return 1 + fi + register_tool "proxmox_repos" true "$FUNC_VERSION" + msg_success "$(translate "Proxmox Backup Server APT repositories configured")" + return 0 + fi + if ! ensure_repositories; then msg_error "$(translate "Failed to configure Proxmox repositories")" register_tool "proxmox_repos" false "$FUNC_VERSION" @@ -524,7 +558,7 @@ force_apt_ipv4() { # ========================================================== apply_network_optimizations() { - local FUNC_VERSION="1.1" + local FUNC_VERSION="1.2" pmx_journal_context "apply_network_optimizations" "$FUNC_VERSION" # description: Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible). msg_info "$(translate "Optimizing network settings...")" @@ -622,14 +656,16 @@ RemainAfterExit=yes WantedBy=multi-user.target EOF - pmx_write_file /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules <<'EOF' + pmx_remove_file /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules + + pmx_write_file /etc/udev/rules.d/99-zz-proxmenux-fwbr-tune.rules <<'EOF' ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwbr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k" ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwln*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k" ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwpr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k" ACTION=="add", SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k" EOF - chmod 0644 /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules - chown root:root /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules + chmod 0644 /etc/udev/rules.d/99-zz-proxmenux-fwbr-tune.rules + chown root:root /etc/udev/rules.d/99-zz-proxmenux-fwbr-tune.rules systemctl daemon-reload >/dev/null 2>&1 || true udevadm control --reload-rules >/dev/null 2>&1 || true diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index ca60b752..39274146 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -53,7 +53,12 @@ initialize_cache if [[ -f "$LOCAL_SCRIPTS/global/platform-functions.sh" ]]; then source "$LOCAL_SCRIPTS/global/platform-functions.sh" - pmx_require_pve || exit 1 +fi +# On Proxmox Backup Server the menu lists only the options that apply to +# it; repositories, upgrade and the subscription notice use its tooling. +PMX_ON_PBS=false +if declare -F pmx_is_pbs >/dev/null && pmx_is_pbs; then + PMX_ON_PBS=true fi # Load shared global functions @@ -122,6 +127,17 @@ setup_proxmox_repositories() { msg_info2 "$(translate "Configuring Proxmox APT repositories...")" + if [[ "$PMX_ON_PBS" == true ]]; then + if ! ensure_pbs_repositories; then + msg_error "$(translate "Failed to configure Proxmox Backup Server repositories")" + register_tool "proxmox_repos" false "$FUNC_VERSION" + return 1 + fi + register_tool "proxmox_repos" true "$FUNC_VERSION" + msg_success "$(translate "Proxmox Backup Server APT repositories configured")" + return 0 + fi + if ! ensure_repositories; then msg_error "$(translate "Failed to configure Proxmox repositories")" register_tool "proxmox_repos" false "$FUNC_VERSION" @@ -248,6 +264,10 @@ EOF apt_upgrade() { + if [[ "$PMX_ON_PBS" == true ]]; then + bash "$LOCAL_SCRIPTS/global/update-pbs.sh" + return + fi local pve_version pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1) @@ -2376,6 +2396,14 @@ EOF remove_subscription_banner() { local FUNC_VERSION="1.0" # description: Patch the Proxmox web UI to suppress the "no valid subscription" dialog (PVE 8 + 9 variants supported). + if [[ "$PMX_ON_PBS" == true ]]; then + if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then + msg_error "$(translate "Subscription banner removal failed")" + return 1 + fi + register_tool "subscription_banner" true "$FUNC_VERSION" + return 0 + fi local pve_version pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1) @@ -3143,8 +3171,11 @@ EOF } - ensure_aliases - msg_ok "$(translate "Aliases added to .bashrc")" + # The aliases call Proxmox VE tools. + if [[ "$PMX_ON_PBS" != true ]]; then + ensure_aliases + msg_ok "$(translate "Aliases added to .bashrc")" + fi msg_success "$(translate "Figurine installation and configuration completed successfully.")" register_tool "figurine" true "$FUNC_VERSION" @@ -3993,6 +4024,19 @@ main_menu() { "Optional|Install and configure Log2RAM|LOG2RAM" ) + if [[ "$PMX_ON_PBS" == true ]]; then + local -A pve_only=( + [GUESTAGENT]=1 [VFIO_IOMMU]=1 [OPENVSWITCH]=1 [VZDUMP]=1 [PVEAM]=1 + [CEPH]=1 [ENABLE_HA]=1 [OVHRTM]=1 [PIGZ]=1 + ) + local pbs_options=() option_key + for option in "${options[@]}"; do + option_key="${option##*|}" + [[ -n "${pve_only[$option_key]:-}" ]] || pbs_options+=("$option") + done + options=("${pbs_options[@]}") + fi + IFS=$'\n' sorted_options=($(for option in "${options[@]}"; do IFS='|' read -r category description function_name <<< "$option" printf "%d|%s|%s|%s\n" "${category_order[$category]:-999}" "$category" "$description" "$function_name" diff --git a/scripts/post_install/uninstall-tools.sh b/scripts/post_install/uninstall-tools.sh index 63b22fa1..1724f849 100644 --- a/scripts/post_install/uninstall-tools.sh +++ b/scripts/post_install/uninstall-tools.sh @@ -266,6 +266,12 @@ uninstall_subscription_banner() { fi fi + # Proxmox Backup Server ships the same widget toolkit without the PVE packages. + local -a ui_packages=(pve-manager proxmox-widget-toolkit libjs-extjs libpve-http-server-perl) + if [[ "$(dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null)" == "install ok installed" ]]; then + ui_packages=(proxmox-widget-toolkit libjs-extjs) + fi + if [[ "$restored" == false ]]; then msg_info "$(translate "Performing complete package reinstallation...")" @@ -274,7 +280,7 @@ uninstall_subscription_banner() { # Reinstall packages with force-confnew to restore original configs if apt-get --reinstall -o Dpkg::Options::="--force-confnew" install \ - pve-manager proxmox-widget-toolkit libjs-extjs libpve-http-server-perl -y >/dev/null 2>&1; then + "${ui_packages[@]}" -y >/dev/null 2>&1; then msg_ok "$(translate "Reinstalled Proxmox packages successfully")" restored=true else @@ -286,7 +292,7 @@ uninstall_subscription_banner() { # Second pass reinstallation to ensure everything is clean apt-get update >/dev/null 2>&1 - apt-get --reinstall install proxmox-widget-toolkit pve-manager libjs-extjs libpve-http-server-perl -y >/dev/null 2>&1 || true + apt-get --reinstall install "${ui_packages[@]}" -y >/dev/null 2>&1 || true fi msg_info "$(translate "Cleaning cached files...")" diff --git a/tests/test_repository_policy_pbs.py b/tests/test_repository_policy_pbs.py new file mode 100644 index 00000000..e0a71d90 --- /dev/null +++ b/tests/test_repository_policy_pbs.py @@ -0,0 +1,103 @@ +"""Fixture-only PBS policy checks: never call host proxmox-backup-* tools or edit host APT sources.""" +import importlib.util +import json +from pathlib import Path +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +POLICY = ROOT / 'scripts/global/repository_policy.py' + + +def repo(components, *, uri='https://enterprise.proxmox.com/debian/pbs', suite='trixie', enabled=True): + return {'Types': ['deb'], 'URIs': [uri], 'Suites': [suite], + 'Components': components.split(), 'Enabled': enabled} + + +class PBSRepositoryPolicyTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + spec = importlib.util.spec_from_file_location('repository_policy', POLICY) + cls.module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cls.module) + + def setUp(self): + self.calls = [] + self.status = 'notfound' + self.repositories = { + 'files': [ + {'path': '/etc/apt/sources.list.d/pbs-enterprise.sources', 'repositories': [repo('pbs-enterprise')]}, + {'path': '/etc/apt/sources.list.d/debian.sources', + 'repositories': [repo('main contrib', uri='http://deb.debian.org/debian')]}, + ], + 'errors': [], 'digest': 'fixture-digest', + 'standard-repos': [{'handle': 'enterprise'}, {'handle': 'no-subscription'}, {'handle': 'test'}], + } + + def run_policy(self, apply=False): + def run(args): + self.calls.append(args) + if args[:3] == ['proxmox-backup-manager', 'subscription', 'get']: + return json.dumps({'status': self.status, 'serverid': 'FIXTURE-SECRET'}) + self.assertEqual(args[:2], ['proxmox-backup-debug', 'api']) + action = args[2] + if action == 'get': + return json.dumps(self.repositories) + self.assertEqual(args[args.index('--digest') + 1], self.repositories['digest']) + if action == 'create': + path = args[args.index('--path') + 1] + index = int(args[args.index('--index') + 1]) + next(f for f in self.repositories['files'] if f['path'] == path)['repositories'][index]['Enabled'] = False + elif action == 'set': + self.repositories['files'].append({'path': '/etc/apt/sources.list.d/proxmox.sources', + 'repositories': [repo('pbs-no-subscription', uri='http://download.proxmox.com/debian/pbs')]}) + self.repositories['digest'] += '+' + return '' + with patch.object(self.module, 'run', side_effect=run): + return self.module.evaluate('trixie', apply=apply, product='pbs') + + def writes(self): + return [c for c in self.calls if c[:2] == ['proxmox-backup-debug', 'api'] and c[2] in ('create', 'set')] + + def test_fresh_pbs_offers_switch_without_writing(self): + self.assertEqual(self.run_policy(), 'offer') + self.assertEqual(self.writes(), []) + self.assertFalse(any(c[0] in ('pvesh', 'pvesubscription') for c in self.calls)) + + def test_consented_switch_disables_pbs_enterprise_then_adds_no_subscription(self): + self.assertEqual(self.run_policy(apply=True), 'changed') + writes = self.writes() + self.assertEqual([c[2] for c in writes], ['create', 'set']) + self.assertEqual(writes[0][writes[0].index('--path') + 1], '/etc/apt/sources.list.d/pbs-enterprise.sources') + self.assertEqual(writes[0][writes[0].index('--enabled') + 1], '0') + self.assertEqual(writes[1][writes[1].index('--handle') + 1], 'no-subscription') + + def test_active_subscription_preserves_enterprise(self): + self.status = 'active' + self.assertEqual(self.run_policy(apply=True), 'preserve') + self.assertEqual(self.writes(), []) + + def test_existing_no_subscription_is_preserved_without_lookup(self): + self.repositories['files'][0]['repositories'] = [repo('pbs-no-subscription')] + self.assertEqual(self.run_policy(apply=True), 'preserve') + self.assertFalse(any(c[0] == 'proxmox-backup-manager' for c in self.calls)) + + def test_unrecognized_subscription_status_cannot_mutate(self): + self.status = 'invalid' + with self.assertRaisesRegex(ValueError, 'proxmox-backup-manager'): + self.run_policy(apply=True) + self.assertEqual(self.writes(), []) + + def test_pve_channels_do_not_count_as_pbs_sources(self): + self.repositories['files'][0]['repositories'] = [repo('pve-no-subscription')] + self.assertEqual(self.run_policy(), 'offer') + + def test_wrong_suite_points_to_pbs_interface(self): + self.repositories['files'][0]['repositories'] = [repo('pbs-enterprise', suite='bookworm')] + with self.assertRaisesRegex(ValueError, 'Administration > Repositories'): + self.run_policy() + self.assertEqual(self.writes(), []) + + +if __name__ == '__main__': + unittest.main() diff --git a/web/app/[locale]/docs/post-install/page.tsx b/web/app/[locale]/docs/post-install/page.tsx index e4bc8304..89332b87 100644 --- a/web/app/[locale]/docs/post-install/page.tsx +++ b/web/app/[locale]/docs/post-install/page.tsx @@ -89,10 +89,12 @@ export default async function PostInstallPage({ docs: { postInstall: { routes: Route[] related: { items: RelatedItem[] } + pbs?: { items: string[] } } } } const routes = messages.docs.postInstall.routes const relatedItems = messages.docs.postInstall.related.items + const pbsItems = messages.docs.postInstall.pbs?.items ?? [] const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} @@ -210,6 +212,14 @@ export default async function PostInstallPage({ {t.rich("community.body", { em, code, a: communityAnchor, link: externalRepoLink })}

+

{t("pbs.heading")}

+

{t.rich("pbs.body", { strong })}

+
    + {pbsItems.map((_, i) => ( +
  • {t.rich(`pbs.items.${i}`, { strong, em })}
  • + ))} +
+

{t("related.heading")}

    {relatedItems.map((item) => ( diff --git a/web/messages/en/docs/post-install/index.json b/web/messages/en/docs/post-install/index.json index e6e60c1f..46ab94f8 100644 --- a/web/messages/en/docs/post-install/index.json +++ b/web/messages/en/docs/post-install/index.json @@ -78,6 +78,17 @@ "heading": "Community scripts", "body": "The menu also exposes two third-party scripts from the community-scripts project: Proxmox VE Post Install and Proxmox VE Microcode. These are wrappers that wget | bash the original authors' scripts. They are not maintained by ProxMenux. See External Repositories for details and trust considerations before running them." }, + "pbs": { + "heading": "On Proxmox Backup Server", + "body": "On a Proxmox Backup Server host, the same menu opens from Settings post-install Proxmox in the PBS menu. Automated, Customizable, Apply Available Updates and Uninstall Optimizations work as on Proxmox VE, with these differences:", + "items": [ + "Repositories and upgrade: when the server has no subscription and only the Enterprise PBS repository is active, ProxMenux asks before switching to the PBS no-subscription repository. The switch goes through the server's own repository API, the same one behind Administration > Repositories. With an active subscription, or with a no-subscription or test repository already configured, the sources stay as they are.", + "Customizable list: the options that only apply to Proxmox VE are not listed: guest agent, VFIO IOMMU, Open vSwitch, vzdump backup speed, pigz, Appliance Manager, Ceph, High Availability and OVH Real Time Monitoring.", + "Subscription banner: the patch applies to the Proxmox Backup Server web interface, and Uninstall Optimizations restores it.", + "Figurine: installs without the Proxmox VE aliases.", + "Community scripts: the section offers Proxmox Backup Server Post Install and Proxmox Backup Server Microcode in place of the Proxmox VE scripts." + ] + }, "related": { "heading": "Related", "items": [ diff --git a/web/messages/es/docs/post-install/index.json b/web/messages/es/docs/post-install/index.json index 05bd570f..699abbdd 100644 --- a/web/messages/es/docs/post-install/index.json +++ b/web/messages/es/docs/post-install/index.json @@ -78,6 +78,17 @@ "heading": "Community scripts", "body": "El menú también expone dos scripts de terceros del proyecto community-scripts: Proxmox VE Post Install y Proxmox VE Microcode. Son wrappers que hacen wget | bash de los scripts de los autores originales. No los mantiene ProxMenux. Mira External Repositories para detalles y consideraciones de confianza antes de ejecutarlos." }, + "pbs": { + "heading": "En Proxmox Backup Server", + "body": "En un host Proxmox Backup Server, el mismo menú se abre desde Settings post-install Proxmox en el menú de PBS. Automatizado, Personalizable, Apply Available Updates y Uninstall Optimizations funcionan igual que en Proxmox VE, con estas diferencias:", + "items": [ + "Repositorios y actualización: cuando el servidor no tiene suscripción y solo está activo el repositorio Enterprise de PBS, ProxMenux pregunta antes de cambiar al repositorio sin suscripción de PBS. El cambio se hace con la API de repositorios del propio servidor, la misma que usa Administration > Repositories. Con una suscripción activa, o con un repositorio sin suscripción o de pruebas ya configurado, las fuentes se mantienen como están.", + "Lista personalizable: no aparecen las opciones que solo se aplican a Proxmox VE: agente de invitado, VFIO IOMMU, Open vSwitch, velocidad de backups vzdump, pigz, Appliance Manager, Ceph, alta disponibilidad y OVH Real Time Monitoring.", + "Banner de suscripción: el parche se aplica a la interfaz web de Proxmox Backup Server, y Uninstall Optimizations lo restaura.", + "Figurine: se instala sin los alias de Proxmox VE.", + "Scripts de la comunidad: la sección ofrece Proxmox Backup Server Post Install y Proxmox Backup Server Microcode en lugar de los scripts de Proxmox VE." + ] + }, "related": { "heading": "Relacionado", "items": [ From 74c0321956db1ebc722ecacef5b5b348a21ff3aa Mon Sep 17 00:00:00 2001 From: ProxMenuxBot Date: Sat, 17 Oct 2026 16:20:37 +0000 Subject: [PATCH 26/53] chore(i18n): auto-fill missing translations in messages/{locale}/common.json Source: 98ae1f3 Triggered by: push --- AppImage/messages/de/common.json | 55 +++++++++++++++++++++++++++++--- AppImage/messages/fr/common.json | 55 +++++++++++++++++++++++++++++--- AppImage/messages/it/common.json | 55 +++++++++++++++++++++++++++++--- AppImage/messages/pt/common.json | 55 +++++++++++++++++++++++++++++--- AppImage/messages/sk/common.json | 55 +++++++++++++++++++++++++++++--- AppImage/messages/sv/common.json | 55 +++++++++++++++++++++++++++++--- 6 files changed, 300 insertions(+), 30 deletions(-) diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index 9fa0c7cf..37f27e74 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -8,7 +8,8 @@ "unknown": "Unbekannt", "notAvailable": "N/A", "serverOffline": "Server offline", - "supportProject": "Unterstützen Sie das Projekt und tragen Sie dazu bei" + "supportProject": "Unterstützen Sie das Projekt und tragen Sie dazu bei", + "descriptionPbs": "Proxmox Backup Server Dashboard" }, "common": { "notAvailable": "N/A", @@ -165,7 +166,8 @@ }, "runningCountOne": "{count} läuft", "runningCountFew": "{count} laufen", - "runningCountMany": "{count} laufen" + "runningCountMany": "{count} laufen", + "pbsVersion": "PBS-Version:" }, "storage": { "loadingTitle": "Speicherdaten werden geladen...", @@ -1831,7 +1833,8 @@ "logs": "Systemprotokolle", "updates": "Systemaktualisierungen", "security": "Sicherheit & Zertifikate", - "temperature": "Temperatur" + "temperature": "Temperatur", + "pbsServices": "PBS Services" } }, "remoteStorage": { @@ -4824,7 +4827,8 @@ "services": "PVE-Dienste", "logs": "Systemprotokolle", "updates": "Systemaktualisierungen", - "security": "Sicherheit & Zertifikate" + "security": "Sicherheit & Zertifikate", + "pbsServices": "PBS-Dienste" }, "checks": { "cpu_usage": "CPU-Auslastung", @@ -7320,6 +7324,47 @@ "navigation": "PBS-Navigation", "cpu": "CPU", "memory": "Arbeitsspeicher", - "uptime": "Betriebszeit" + "uptime": "Betriebszeit", + "capacityUnavailable": "Kapazität nicht verfügbar.", + "datastoresUnavailable": "{count} nicht verfügbar", + "datastoresOnline": "Alle verfügbar", + "moreDatastores": "+{count} weitere Datenspeicher", + "estimatedFull": "Voraussichtlich voll", + "estimatedFullUnknown": "Noch nicht genügend Daten", + "duration": "Dauer", + "noJobs": "Es sind keine Synchronisierungs-, Überprüfungs- oder Löschzeitpläne konfiguriert." + }, + "monitorLinks": { + "groups": { + "cluster": "Cluster", + "standalone": "Eigenständige Knoten", + "pbs": "Proxmox Sicherungsserver" + }, + "thisNode": "Dieser Knoten", + "checking": "Knoten werden überprüft…", + "loadFailed": "Die Knotenliste konnte nicht geladen werden.", + "empty": "Noch keine anderen Knoten.Fügen Sie einen hinzu, um den Monitor von hier aus zu öffnen.", + "noResponse": "Keine Antwort", + "pbsInterface": "PBS-Schnittstelle", + "openNewTab": "In einem neuen Tab öffnen", + "manage": "Knoten hinzufügen oder verwalten…", + "dialogTitle": "Verknüpfte Knoten", + "dialogDescription": "Clusterknoten und konfigurierte Proxmox-Sicherungsserver werden automatisch erkannt.Jeder Monitor behält seine eigene Anmeldung.", + "addTitle": "Einen Knoten hinzufügen", + "name": "Name", + "address": "Adresse überwachen", + "type": "Geben Sie ein", + "add": "Hinzufügen", + "save": "Speichern", + "remove": "Entfernen", + "visible": "Sichtbar", + "autoAddress": "Automatisch", + "detected": "Erkannt", + "added": "Hinzugefügt", + "saveFailed": "Die Knotenliste konnte nicht gespeichert werden.", + "backTo": "Zurück zu {name}", + "cameFromTitle": "Monitorknoten hinzufügen?", + "cameFromBody": "{name} zu den verknüpften Knoten dieses Monitors hinzufügen.", + "notNow": "Nicht jetzt" } } diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 7dea3a29..2b95595a 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -8,7 +8,8 @@ "unknown": "Inconnu", "notAvailable": "N/A", "serverOffline": "Serveur hors ligne", - "supportProject": "Soutenir et contribuer au projet" + "supportProject": "Soutenir et contribuer au projet", + "descriptionPbs": "Tableau de bord du serveur de sauvegarde Proxmox" }, "common": { "notAvailable": "N/A", @@ -165,7 +166,8 @@ }, "runningCountOne": "{count} en cours d'exécution", "runningCountFew": "{count} en cours d'exécution", - "runningCountMany": "{count} en cours d'exécution" + "runningCountMany": "{count} en cours d'exécution", + "pbsVersion": "Version PBS :" }, "storage": { "loadingTitle": "Chargement des données de stockage...", @@ -1831,7 +1833,8 @@ "logs": "Journaux système", "updates": "Mises à jour du système", "security": "Sécurité et certificats", - "temperature": "Température" + "temperature": "Température", + "pbsServices": "Services PBS" } }, "remoteStorage": { @@ -4824,7 +4827,8 @@ "services": "Services PVE", "logs": "Journaux système", "updates": "Mises à jour du système", - "security": "Sécurité et certificats" + "security": "Sécurité et certificats", + "pbsServices": "services PBS" }, "checks": { "cpu_usage": "Utilisation du processeur", @@ -7320,6 +7324,47 @@ "navigation": "Navigation PBS", "cpu": "CPU", "memory": "Mémoire", - "uptime": "Durée de fonctionnement" + "uptime": "Durée de fonctionnement", + "capacityUnavailable": "Capacité non disponible.", + "datastoresUnavailable": "{count} indisponible", + "datastoresOnline": "Tous disponibles", + "moreDatastores": "+{count} banques de données supplémentaires", + "estimatedFull": "Estimé complet", + "estimatedFullUnknown": "Pas encore assez de données", + "duration": "Durée", + "noJobs": "Aucune planification de synchronisation, de vérification ou d'élagage n'est configurée." + }, + "monitorLinks": { + "groups": { + "cluster": "Cluster", + "standalone": "nœuds autonomes", + "pbs": "Serveur de sauvegarde Proxmox" + }, + "thisNode": "Ce nœud", + "checking": "Vérification des nœuds…", + "loadFailed": "Impossible de charger la liste des nœuds.", + "empty": "aucun autre nœud pour l'instant.Ajoutez-en un pour ouvrir son moniteur à partir d'ici.", + "noResponse": "Pas de réponse", + "pbsInterface": "interface PBS", + "openNewTab": "Ouvrir dans un nouvel onglet", + "manage": "Ajouter ou gérer des nœuds…", + "dialogTitle": "nœuds liés", + "dialogDescription": "Les nœuds de cluster et les serveurs de sauvegarde Proxmox configurés sont détectés automatiquement.Chaque moniteur conserve sa propre connexion.", + "addTitle": "ajouter un nœud", + "name": "Nom", + "address": "Adresse du moniteur", + "type": "Type", + "add": "Ajouter", + "save": "Enregistrer", + "remove": "Supprimer", + "visible": "Visible", + "autoAddress": "Automatique", + "detected": "Détecté", + "added": "ajouté", + "saveFailed": "Impossible d'enregistrer la liste des nœuds.", + "backTo": "Retour à {name}", + "cameFromTitle": "Ajouter un nœud Moniteur ?", + "cameFromBody": "Ajoutez {name} aux nœuds liés de ce moniteur.", + "notNow": "Pas maintenant" } } diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 9fb2d83e..3a09e45d 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -8,7 +8,8 @@ "unknown": "Sconosciuto", "notAvailable": "N/A", "serverOffline": "Server offline", - "supportProject": "Sostieni e contribuisci al progetto" + "supportProject": "Sostieni e contribuisci al progetto", + "descriptionPbs": "Dashboard del server di backup Proxmox" }, "common": { "notAvailable": "N/A", @@ -165,7 +166,8 @@ }, "runningCountOne": "{count} in esecuzione", "runningCountFew": "{count} in esecuzione", - "runningCountMany": "{count} in esecuzione" + "runningCountMany": "{count} in esecuzione", + "pbsVersion": "Versione PBS:" }, "storage": { "loadingTitle": "Caricamento dati storage...", @@ -1831,7 +1833,8 @@ "logs": "Log di sistema", "updates": "Aggiornamenti di sistema", "security": "Sicurezza e certificati", - "temperature": "Temperatura" + "temperature": "Temperatura", + "pbsServices": "Servizi PBS" } }, "remoteStorage": { @@ -4824,7 +4827,8 @@ "services": "Servizi PVE", "logs": "Log di sistema", "updates": "Aggiornamenti di sistema", - "security": "Sicurezza e certificati" + "security": "Sicurezza e certificati", + "pbsServices": "servizi PBS" }, "checks": { "cpu_usage": "Utilizzo CPU", @@ -7320,6 +7324,47 @@ "navigation": "Navigazione PBS", "cpu": "CPU", "memory": "Memoria", - "uptime": "Tempo di attività" + "uptime": "Tempo di attività", + "capacityUnavailable": "Capacità non disponibile.", + "datastoresUnavailable": "{count} non disponibile", + "datastoresOnline": "Tutti disponibili", + "moreDatastores": "+{count} altri archivi dati", + "estimatedFull": "Pieno stimato", + "estimatedFullUnknown": "dati non ancora sufficienti", + "duration": "Durata", + "noJobs": "non è configurata alcuna pianificazione di sincronizzazione, verifica o eliminazione." + }, + "monitorLinks": { + "groups": { + "cluster": "cluster", + "standalone": "nodi autonomi", + "pbs": "Server di backup Proxmox" + }, + "thisNode": "questo nodo", + "checking": "Controllo dei nodi…", + "loadFailed": "impossibile caricare l'elenco dei nodi.", + "empty": "ancora nessun altro nodo.Aggiungine uno per aprire il suo Monitor da qui.", + "noResponse": "Nessuna risposta", + "pbsInterface": "interfaccia PBS", + "openNewTab": "apri in una nuova scheda", + "manage": "aggiungi o gestisci nodi...", + "dialogTitle": "nodi collegati", + "dialogDescription": "i nodi del cluster e i server di backup Proxmox configurati vengono rilevati automaticamente.Ogni monitor mantiene il proprio accesso.", + "addTitle": "aggiungi un nodo", + "name": "Nome", + "address": "monitora l'indirizzo", + "type": "digita", + "add": "aggiungi", + "save": "salva", + "remove": "rimuovere", + "visible": "visibile", + "autoAddress": "automatico", + "detected": "rilevato", + "added": "aggiunto", + "saveFailed": "impossibile salvare l'elenco dei nodi.", + "backTo": "torna a {name}", + "cameFromTitle": "aggiungi il nodo Monitor?", + "cameFromBody": "aggiungi {name} ai nodi collegati di questo monitor.", + "notNow": "Non ora" } } diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index d60c1806..90a1f595 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -8,7 +8,8 @@ "unknown": "Desconhecido", "notAvailable": "N/D", "serverOffline": "Servidor off-line", - "supportProject": "Apoie e contribua para o projeto" + "supportProject": "Apoie e contribua para o projeto", + "descriptionPbs": "Painel do servidor de backup Proxmox" }, "common": { "notAvailable": "N/D", @@ -165,7 +166,8 @@ }, "runningCountOne": "{count} em execução", "runningCountFew": "{count} em execução", - "runningCountMany": "{count} em execução" + "runningCountMany": "{count} em execução", + "pbsVersion": "Versão PBS:" }, "storage": { "loadingTitle": "Carregando dados de armazenamento...", @@ -1831,7 +1833,8 @@ "logs": "Registros do sistema", "updates": "Atualizações do sistema", "security": "Segurança e Certificados", - "temperature": "Temperatura" + "temperature": "Temperatura", + "pbsServices": "Serviços PBS" } }, "remoteStorage": { @@ -4824,7 +4827,8 @@ "services": "Serviços PVE", "logs": "Registros do sistema", "updates": "Atualizações do sistema", - "security": "Segurança e certificados" + "security": "Segurança e certificados", + "pbsServices": "serviços PBS" }, "checks": { "cpu_usage": "Uso da CPU", @@ -7320,6 +7324,47 @@ "navigation": "Navegação PBS", "cpu": "CPU", "memory": "Memória", - "uptime": "Tempo de atividade" + "uptime": "Tempo de atividade", + "capacityUnavailable": "Capacidade não disponível.", + "datastoresUnavailable": "{count} indisponível", + "datastoresOnline": "Todos disponíveis", + "moreDatastores": "+{count} mais armazenamentos de dados", + "estimatedFull": "estimativa completa", + "estimatedFullUnknown": "Not enough data yet", + "duration": "Duração", + "noJobs": "nenhuma programação de sincronização, verificação ou remoção está configurada." + }, + "monitorLinks": { + "groups": { + "cluster": "Cluster", + "standalone": "nós autônomos", + "pbs": "Servidor de backup Proxmox" + }, + "thisNode": "Este nó", + "checking": "Verificando nós…", + "loadFailed": "não foi possível carregar a lista de nós.", + "empty": "Nenhum outro nó ainda.Adicione um para abrir seu Monitor aqui.", + "noResponse": "Sem resposta", + "pbsInterface": "interface PBS", + "openNewTab": "abrir em uma nova guia", + "manage": "Adicionar ou gerenciar nós…", + "dialogTitle": "nós vinculados", + "dialogDescription": "os nós do cluster e os servidores de backup Proxmox configurados são detectados automaticamente.Cada monitor mantém seu próprio login.", + "addTitle": "Adicionar um nó", + "name": "Nome", + "address": "endereço do monitor", + "type": "Tipo", + "add": "Adicionar", + "save": "Salvar", + "remove": "Remover", + "visible": "Visível", + "autoAddress": "Automático", + "detected": "Detectado", + "added": "Adicionado", + "saveFailed": "não foi possível salvar a lista de nós.", + "backTo": "Voltar para {name}", + "cameFromTitle": "Adicionar nó Monitor?", + "cameFromBody": "Adicione {name} aos nós vinculados deste Monitor.", + "notNow": "agora não" } } diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 692fcce6..34a6107a 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -7,7 +7,8 @@ "unknown": "Neznáme", "notAvailable": "Nedostupné", "serverOffline": "Server je offline", - "supportProject": "Podporte projekt alebo prispejte vlastnými úpravami" + "supportProject": "Podporte projekt alebo prispejte vlastnými úpravami", + "descriptionPbs": "Proxmox Backup Server Dashboard" }, "common": { "notAvailable": "Nedostupné", @@ -164,7 +165,8 @@ "errors": { "serverUnavailableTitle": "Server Monitora nie je dostupný", "serverUnavailableDescription": "Nedá sa pripojiť k serveru Monitora. Skontrolujte, či služba beží, a skúste to znova." - } + }, + "pbsVersion": "Verzia PBS:" }, "storage": { "loadingTitle": "Načítavam úložiská...", @@ -1830,7 +1832,8 @@ "logs": "Systémové záznamy", "updates": "Aktualizácie systému", "security": "Bezpečnosť a certifikáty", - "temperature": "Teplota" + "temperature": "Teplota", + "pbsServices": "Služby PBS" } }, "remoteStorage": { @@ -4823,7 +4826,8 @@ "services": "Služby PVE", "logs": "Systémové záznamy", "updates": "Aktualizácie systému", - "security": "Bezpečnosť a certifikáty" + "security": "Bezpečnosť a certifikáty", + "pbsServices": "Služby PBS" }, "checks": { "cpu_usage": "Využitie CPU", @@ -7319,6 +7323,47 @@ "navigation": "Navigácia PBS", "cpu": "CPU", "memory": "Pamäť", - "uptime": "Čas prevádzky" + "uptime": "Čas prevádzky", + "capacityUnavailable": "Kapacita nie je k dispozícii.", + "datastoresUnavailable": "{count} nie je k dispozícii", + "datastoresOnline": "Všetky dostupné", + "moreDatastores": "+{count} ďalších dátových úložísk", + "estimatedFull": "Odhadovaný plný", + "estimatedFullUnknown": "Zatiaľ nie je dostatok údajov", + "duration": "Trvanie", + "noJobs": "Nie sú nakonfigurované žiadne plány synchronizácie, overovania ani odstraňovania." + }, + "monitorLinks": { + "groups": { + "cluster": "Cluster", + "standalone": "Samostatné uzly", + "pbs": "Záložný server Proxmox" + }, + "thisNode": "Tento uzol", + "checking": "Kontrolujú sa uzly…", + "loadFailed": "Nepodarilo sa načítať zoznam uzlov.", + "empty": "Zatiaľ žiadne ďalšie uzly.Pridajte jeden, aby ste odtiaľto otvorili jeho monitor.", + "noResponse": "Žiadna odpoveď", + "pbsInterface": "rozhranie PBS", + "openNewTab": "Otvoriť na novej karte", + "manage": "Pridať alebo spravovať uzly…", + "dialogTitle": "Prepojené uzly", + "dialogDescription": "Uzly klastra a nakonfigurované záložné servery Proxmox sa detegujú automaticky.Každý monitor si uchováva svoje vlastné prihlásenie.", + "addTitle": "Pridať uzol", + "name": "Meno", + "address": "Adresa monitora", + "type": "Typ", + "add": "Pridať", + "save": "Uložiť", + "remove": "Odstrániť", + "visible": "Viditeľné", + "autoAddress": "Automaticky", + "detected": "Zistené", + "added": "Pridané", + "saveFailed": "Nepodarilo sa uložiť zoznam uzlov.", + "backTo": "Späť na {name}", + "cameFromTitle": "Pridať uzol monitora?", + "cameFromBody": "Pridajte {name} do prepojených uzlov tohto monitora.", + "notNow": "Teraz nie" } } diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index 5a74449a..20df8611 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -8,7 +8,8 @@ "unknown": "Okänd", "notAvailable": "N/A", "serverOffline": "Servern är frånkopplad", - "supportProject": "Stöd projektet och bidra" + "supportProject": "Stöd projektet och bidra", + "descriptionPbs": "Proxmox Backup Server Dashboard" }, "common": { "notAvailable": "N/A", @@ -165,7 +166,8 @@ }, "runningCountOne": "{count} körs", "runningCountFew": "{count} körs", - "runningCountMany": "{count} körs" + "runningCountMany": "{count} körs", + "pbsVersion": "PBS-version:" }, "storage": { "loadingTitle": "Laddar lagringsdata...", @@ -1831,7 +1833,8 @@ "logs": "Systemloggar", "updates": "Systemuppdateringar", "security": "Säkerhet och certifikat", - "temperature": "Temperatur" + "temperature": "Temperatur", + "pbsServices": "PBS Services" } }, "remoteStorage": { @@ -4824,7 +4827,8 @@ "services": "PVE-tjänster", "logs": "Systemloggar", "updates": "Systemuppdateringar", - "security": "Säkerhet & certifikat" + "security": "Säkerhet & certifikat", + "pbsServices": "PBS-tjänster" }, "checks": { "cpu_usage": "CPU-användning", @@ -7320,6 +7324,47 @@ "navigation": "PBS-navigering", "cpu": "CPU", "memory": "Minne", - "uptime": "Drifttid" + "uptime": "Drifttid", + "capacityUnavailable": "Kapacitet inte tillgänglig.", + "datastoresUnavailable": "{count} inte tillgänglig", + "datastoresOnline": "Alla tillgängliga", + "moreDatastores": "+{count} fler databutiker", + "estimatedFull": "Beräknad full", + "estimatedFullUnknown": "Inte tillräckligt med data ännu", + "duration": "Varaktighet", + "noJobs": "Inga synkroniserings-, verifierings- eller beskärningsscheman är konfigurerade." + }, + "monitorLinks": { + "groups": { + "cluster": "Kluster", + "standalone": "Fristående noder", + "pbs": "Proxmox Backup Server" + }, + "thisNode": "Denna nod", + "checking": "Kontrollerar noder...", + "loadFailed": "Kunde inte ladda nodlistan.", + "empty": "Inga andra noder än.Lägg till en för att öppna monitorn härifrån.", + "noResponse": "Inget svar", + "pbsInterface": "PBS-gränssnitt", + "openNewTab": "Öppna i en ny flik", + "manage": "Lägg till eller hantera noder...", + "dialogTitle": "Länkade noder", + "dialogDescription": "Klusternoder och konfigurerade Proxmox Backup-servrar upptäcks automatiskt.Varje bildskärm behåller sin egen inloggning.", + "addTitle": "Lägg till en nod", + "name": "Namn", + "address": "Övervaka adress", + "type": "Typ", + "add": "Lägg till", + "save": "Spara", + "remove": "Ta bort", + "visible": "Synlig", + "autoAddress": "Automatisk", + "detected": "Upptäckt", + "added": "Tillagd", + "saveFailed": "Kunde inte spara nodlistan.", + "backTo": "Tillbaka till {name}", + "cameFromTitle": "Lägg till monitornod?", + "cameFromBody": "Lägg till {name} till de länkade noderna på denna monitor.", + "notNow": "Inte nu" } } From 6157f34ced40c214eaf2a88cafd0ffad162ec902 Mon Sep 17 00:00:00 2001 From: ProxMenuxBot Date: Sat, 17 Oct 2026 16:21:00 +0000 Subject: [PATCH 27/53] chore(lang): auto-rebuild translation cache Source: 98ae1f3 Triggered by: push --- lang/de.json | 18 ++++++++++++++++++ lang/es.json | 1 + lang/fr.json | 18 ++++++++++++++++++ lang/it.json | 18 ++++++++++++++++++ lang/pt.json | 18 ++++++++++++++++++ lang/sk.json | 18 ++++++++++++++++++ lang/sv.json | 16 ++++++++++++++++ 7 files changed, 107 insertions(+) diff --git a/lang/de.json b/lang/de.json index 8d643f1a..2a460ed1 100644 --- a/lang/de.json +++ b/lang/de.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (Proxmox-Standard-vzdump-Pfad)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (Proxmox-Standard)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = Sticky Bit + RWX für alle. Keine gemeinsame Gruppe erforderlich.", + "====== PBS UPDATE COMPLETED ======": "====== PBS-UPDATE ABGESCHLOSSEN ======", "====== PVE UPDATE COMPLETED ======": "====== PVE-UPDATE ABGESCHLOSSEN ======", ">> Create a new backup job <<": ">> Einen neuen Sicherungsauftrag erstellen <<", ">> Save and return <<": ">> Speichern und zurück <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Eine Multi-Container-Anwendung wird nicht neu erstellt: Ihre Container werden zusammen aktualisiert.", "A new ProxMenux version is available:": "Eine neue ProxMenux-Version ist verfügbar:", "A new kernel is staged for the next boot:": "Ein neuer Kernel wird für den nächsten Start bereitgestellt:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "Ein neuerer Kernel ist installiert. Starten Sie vor dem nächsten Backup-Fenster einen Neustart, um es zu verwenden.", "A newer version is available:": "Eine neuere Version ist verfügbar:", "A pending operation exists for": "Eine ausstehende operation existiert für", "A pending stack assembly already exists; it is not overwritten": "Eine ausstehende Stapelanordnung existiert bereits; sie wird nicht überschrieben", @@ -169,6 +171,7 @@ "APT language configuration updated": "APT-Sprachkonfiguration aktualisiert", "APT language downloads restored": "APT-Sprachdownloads wiederhergestellt", "APT package lists updated": "APT-Paketlisten aktualisiert", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "APT-Aktualisierung fehlgeschlagen. Überprüfen Sie das PBS-Abonnement und die Repositorys unter Administration > Repositorys. Protokoll:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "APT-Aktualisierung fehlgeschlagen. Prüfen Sie das PBS-Abonnement und die Repositories in der nativen PBS-Oberfläche; es wurden keine Paketquellen geändert.", "APT update failed. Check repository access; NVIDIA installation stopped.": "APT-Update fehlgeschlagen. Überprüfen Sie den Repository-Zugriff. Die Installation von NVIDIA wurde gestoppt.", "Aborted": "Abgebrochen", @@ -538,6 +541,7 @@ "Available Borg targets:": "Verfügbare Borg-Ziele:", "Available Disks on Host": "Verfügbare Festplatten auf dem Host", "Available ISO Images": "Verfügbare ISO-Images", + "Available PBS Version": "Verfügbare PBS-Version", "Available PBS repositories:": "Verfügbare PBS-Repositories:", "Available PVE Version": "Verfügbare PVE-Version", "Available Shares:": "Verfügbare Aktien:", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Aktuelles NVIDIA-Inventar behoben: ein Refresh ist required", "Current NVIDIA inventory resolved: no refresh is required": "Aktuelles NVIDIA-Inventar behoben: kein Refresh ist required", "Current Network Configuration": "Aktuelle Netzwerkkonfiguration", + "Current PBS Version": "Aktuelle PBS-Version", "Current PVE Version": "Aktuelle PVE-Version", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Aktuelle ProxMenux-Hostskripte registrieren Remote-Freigaben mithilfe von pvesm als Proxmox-Speicher.", "Current ProxMenux version:": "Aktuelle ProxMenux-Version:", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Erkannte Coral-Hardware:", "Detected GPU(s):": "Erkannte GPU(s):", "Detected Intel CPU": "Intel-CPU erkannt", + "Detected Proxmox Backup Server": "Proxmox-Sicherungsserver erkannt", "Detected Proxmox VE": "Proxmox VE erkannt", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Proxmox VE 8.x erkannt – Ceph Squid für Debian installieren", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Proxmox VE 9.x erkannt – Ceph Squid für Debian Trixie installieren", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Möchten Sie mit der Systemaktualisierung fortfahren?", "Do you want to proceed?": "Möchten Sie fortfahren?", "Do you want to reboot now?": "Möchten Sie jetzt neu starten?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Möchten Sie das Proxmox-Abonnementbanner von der Proxmox Backup Server-Weboberfläche entfernen?", "Do you want to remove the stale entry from vfio.conf and continue?": "Möchten Sie den veralteten Eintrag aus vfio.conf entfernen und fortfahren?", "Do you want to remove the stale entry from vfio.conf?": "Möchten Sie den veralteten Eintrag aus vfio.conf entfernen?", "Do you want to restart the network service now to apply changes?": "Möchten Sie den Netzwerkdienst jetzt neu starten, um die Änderungen zu übernehmen?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Das Klonen des log2ram-Repositorys ist fehlgeschlagen. Überprüfen Sie /tmp/log2ram_install.log", "Failed to configure EFI disk": "Die EFI-Festplatte konnte nicht konfiguriert werden", "Failed to configure IOMMU automatically.": "IOMMU konnte nicht automatisch konfiguriert werden.", + "Failed to configure Proxmox Backup Server repositories": "Proxmox Backup Server-Repositorys konnten nicht konfiguriert werden", "Failed to configure Proxmox repositories": "Proxmox-Repositorys konnten nicht konfiguriert werden", "Failed to configure TPM device in VM": "Das TPM-Gerät konnte in der VM nicht konfiguriert werden", "Failed to configure repositories.": "Die Repositorys konnten nicht konfiguriert werden.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Kein aktiver Proxmox-Speicher akzeptiert OCI-Vorlagenarchive.", "No active exports or showmount not available": "Keine aktiven Exporte oder Showmount nicht verfügbar", "No active session": "Keine aktive Sitzung", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "Kein aktives Abonnement und kein verwendbares PBS-Repository. Im nicht interaktiven Modus können APT-Quellen nicht geändert werden. Konfigurieren Sie sie unter Administration > Repositorys.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Kein aktives Abonnement und kein nutzbares PVE-Repository. Im nicht interaktiven Modus können APT-Quellen nicht geändert werden. Konfigurieren Sie sie unter Knoten > Updates > Repositorys.", "No additional GPU can be added.": "Es kann keine zusätzliche GPU hinzugefügt werden.", "No additional device needs to be added.": "Es muss kein zusätzliches Gerät hinzugefügt werden.", @@ -4874,7 +4883,11 @@ "Proxmology logo applied": "Proxmology-Logo angewendet", "Proxmox 9 system update allready": "Proxmox 9 Systemupdate bereits", "Proxmox APT repositories configured": "Proxmox APT-Repositorys konfiguriert", + "Proxmox Backup Server": "Proxmox Sicherungsserver", "Proxmox Backup Server (PBS) destinations": "Proxmox Backup Server (PBS)-Ziele", + "Proxmox Backup Server APT repositories configured": "Proxmox Backup Server APT-Repositorys konfiguriert", + "Proxmox Backup Server Update": "Proxmox Backup-Server-Update", + "Proxmox Backup Server update completed.": "Aktualisierung des Proxmox-Sicherungsservers abgeschlossen.", "Proxmox CIFS Storage Status:": "Proxmox CIFS-Speicherstatus:", "Proxmox NFS Storage Status:": "Proxmox NFS-Speicherstatus:", "Proxmox System Update": "Proxmox-Systemupdate", @@ -5234,6 +5247,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Repository-Helfer nicht verfügbar. Die Installation von NVIDIA wurde gestoppt.", "Repository policy returned an unexpected result.": "Die Repository-Richtlinie hat ein unerwartetes Ergebnis zurückgegeben.", "Repository ready.": "Repository bereit.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Repository-Quellen geändert, aber die Aktualisierung der APT-Paketliste ist fehlgeschlagen. Betrieb gestoppt;Quellen wurden nicht zurückgesetzt. Überprüfen Sie „Administration > Repositorys“ und wiederholen Sie apt-get update, bevor Sie fortfahren.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Die Repository-Quellen wurden geändert, aber die Aktualisierung der APT-Paketliste ist fehlgeschlagen. Vorgang gestoppt; die Quellen wurden nicht zurückgesetzt. Überprüfen Sie Knoten > Updates > Repositorys und wiederholen Sie apt-get update, bevor Sie fortfahren.", "Repository switch declined; no APT source changed.": "Repository-Wechsel abgelehnt; es wurde keine APT-Quelle geändert.", "Repository:": "Repository:", @@ -6783,6 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Diese CIFS-Freigabe wird mit restriktiven Berechtigungen gemountet.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Diese GPU gilt als inkompatibel mit GPU-Passthrough zu einer VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Diese NFS-Freigabe ist vollständig eingeschränkt – selbst der Host-Root kann nicht darauf schreiben.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Dieser Proxmox-Sicherungsserver hat kein aktives Abonnement. Wechseln Sie zum Repository ohne Abonnement? Die unzugängliche Enterprise PBS-Quelle wird deaktiviert.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Für diese VM sind zusätzliche Installationsschritte erforderlich. Weitere Informationen finden Sie im Installationshandbuch unter:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Dieses Konto ist für {registry}. Geben Sie ein Bild aus dieser Registrierung ein.", "This action will:": "Diese Aktion wird:", @@ -6866,6 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Dieses Skript kann nicht über das Proxmox-Webterminal ausgeführt werden.", "This script must be run as root.": "Dieses Skript muss als Root ausgeführt werden.", "This script must be run on a Proxmox host.": "Dieses Skript muss auf einem Proxmox-Host ausgeführt werden.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox-Sicherungsserver an", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox VE-Server an", "This script will update your Proxmox VE system with advanced options:": "Dieses Skript aktualisiert Ihr Proxmox VE-System mit erweiterten Optionen:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Diese Sitzung wird im Monitor-Terminal ausgeführt. Wenn Sie es von hier aus ausführen, wird die Verbindung während der Installation unterbrochen und der Switch bleibt in einem defekten Zustand.", @@ -7192,6 +7208,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Upgrade-Assistent: Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Upgrade vom Benutzer abgebrochen", "Upgrade one node at a time": "Aktualisieren Sie jeweils einen Knoten", + "Upgrade the system, offering the": "Aktualisieren Sie das System und bieten Sie das an", "Upgrade to Proxmox VE 9": "Upgrade auf Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "NVIDIA-Dienstprogramme werden aktualisiert (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Eine verschlüsselte Kopie des Schlüssels auf PBS hochladen, damit Sie ihn auf einem neu installierten Host mit nur einer Passphrase wiederherstellen können?", @@ -8111,6 +8128,7 @@ "was": "war", "was installed, but the kernel reports:": "wurde installiert, aber der Kernel meldet:", "when finished": "wenn fertig", + "when the server has no subscription (you will be asked)": "Wenn der Server kein Abonnement hat (Sie werden gefragt)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "Bindet die GPU beim nächsten Neustart erneut an vfio-pci, wodurch der zu installierende Treiber beschädigt wird.", "wipefs failed on": "Wipefs sind fehlgeschlagen", "with": "mit", diff --git a/lang/es.json b/lang/es.json index c0605465..92976bfb 100644 --- a/lang/es.json +++ b/lang/es.json @@ -4874,6 +4874,7 @@ "Proxmology logo applied": "Logotipo de Proxmology aplicado", "Proxmox 9 system update allready": "Actualización del sistema Proxmox 9 ya", "Proxmox APT repositories configured": "Repositorios Proxmox APT configurados", + "Proxmox Backup Server": "Servidor de respaldo Proxmox", "Proxmox Backup Server (PBS) destinations": "Destinos de Proxmox Backup Server (PBS)", "Proxmox Backup Server APT repositories configured": "Repositorios APT de Proxmox Backup Server configurados", "Proxmox Backup Server Update": "Actualización de Proxmox Backup Server", diff --git a/lang/fr.json b/lang/fr.json index 2d3be751..94ee253c 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (chemin vzdump par défaut de Proxmox)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (Proxmox par défaut)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = bit collant + rwx pour tous. Aucun groupe partagé n’est nécessaire.", + "====== PBS UPDATE COMPLETED ======": "====== MISE À JOUR PBS TERMINÉE ======", "====== PVE UPDATE COMPLETED ======": "====== MISE À JOUR PVE TERMINÉE ======", ">> Create a new backup job <<": ">> Créer une nouvelle tâche de sauvegarde <<", ">> Save and return <<": ">> Enregistrer et retourner <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Une application multi-conteneurs n'est pas recréée : ses conteneurs sont mis à jour ensemble.", "A new ProxMenux version is available:": "Une nouvelle version de ProxMenux est disponible :", "A new kernel is staged for the next boot:": "Un nouveau noyau est préparé pour le prochain démarrage :", + "A newer kernel is installed. Reboot before the next backup window to use it.": "Un noyau plus récent est installé. Redémarrez avant la prochaine fenêtre de sauvegarde pour l'utiliser.", "A newer version is available:": "Une version plus récente est disponible :", "A pending operation exists for": "Un opera en attente existe pour", "A pending stack assembly already exists; it is not overwritten": "Une pile en attente existe déjà; elle n'est pas écrasée", @@ -169,6 +171,7 @@ "APT language configuration updated": "Configuration du langage APT mise à jour", "APT language downloads restored": "Téléchargements de langues APT restaurés", "APT package lists updated": "Listes de packages APT mises à jour", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "L’actualisation APT a échoué. Vérifiez l'abonnement PBS et les référentiels dans Administration > Référentiels. Enregistrer:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "Échec de l'actualisation APT. Vérifiez l'abonnement et les dépôts de PBS dans son interface native ; aucune source n'a été modifiée.", "APT update failed. Check repository access; NVIDIA installation stopped.": "La mise à jour APT a échoué. Vérifiez l'accès au référentiel ; l'installation de NVIDIA s'est arrêtée.", "Aborted": "Avorté", @@ -538,6 +541,7 @@ "Available Borg targets:": "Cibles Borg disponibles :", "Available Disks on Host": "Disques disponibles sur l'hôte", "Available ISO Images": "Images ISO disponibles", + "Available PBS Version": "version PBS disponible", "Available PBS repositories:": "Dépôts PBS disponibles :", "Available PVE Version": "Version PVE disponible", "Available Shares:": "Actions disponibles :", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "L'inventaire actuel de NVIDIA résolu: un rafraîchissement est required", "Current NVIDIA inventory resolved: no refresh is required": "L'inventaire actuel de NVIDIA résolu: aucun rafraîchissement n'est required", "Current Network Configuration": "Configuration réseau actuelle", + "Current PBS Version": "version actuelle de PBS", "Current PVE Version": "Version PVE actuelle", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Les scripts hôtes ProxMenux actuels enregistrent les partages distants en tant que stockages Proxmox à l'aide de pvesm.", "Current ProxMenux version:": "Version actuelle de ProxMenux :", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Matériel Coral détecté :", "Detected GPU(s):": "GPU(s) détecté(s) :", "Detected Intel CPU": "Processeur Intel détecté", + "Detected Proxmox Backup Server": "Serveur de sauvegarde Proxmox détecté", "Detected Proxmox VE": "Proxmox VE détecté", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Proxmox VE 8.x détecté - Installation de Ceph Squid pour Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Proxmox VE 9.x détecté - Installation de Ceph Squid pour Debian Trixie", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Voulez-vous procéder à la mise à jour du système ?", "Do you want to proceed?": "Voulez-vous continuer ?", "Do you want to reboot now?": "Voulez-vous redémarrer maintenant ?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Voulez-vous supprimer la bannière d'abonnement Proxmox de l'interface Web du serveur de sauvegarde Proxmox ?", "Do you want to remove the stale entry from vfio.conf and continue?": "Voulez-vous supprimer l'entrée obsolète de vfio.conf et continuer ?", "Do you want to remove the stale entry from vfio.conf?": "Voulez-vous supprimer l'entrée obsolète de vfio.conf ?", "Do you want to restart the network service now to apply changes?": "Voulez-vous redémarrer le service réseau maintenant pour appliquer les modifications ?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Échec du clonage du référentiel log2ram. Vérifiez /tmp/log2ram_install.log", "Failed to configure EFI disk": "Échec de la configuration du disque EFI", "Failed to configure IOMMU automatically.": "Échec de la configuration automatique d'IOMMU.", + "Failed to configure Proxmox Backup Server repositories": "Échec de la configuration des référentiels du serveur de sauvegarde Proxmox", "Failed to configure Proxmox repositories": "Échec de la configuration des référentiels Proxmox", "Failed to configure TPM device in VM": "Échec de la configuration du périphérique TPM dans la VM", "Failed to configure repositories.": "Échec de la configuration des référentiels.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Aucun stockage Proxmox actif n'accepte les archives de modèles OCI.", "No active exports or showmount not available": "Aucune exportation active ou showmount non disponible", "No active session": "Aucune session active", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "Aucun abonnement actif et aucun référentiel PBS utilisable. Le mode non interactif ne peut pas modifier les sources APT ;configurez-les dans Administration > Dépôts.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Aucun abonnement actif et aucun référentiel PVE utilisable. Le mode non interactif ne peut pas modifier les sources APT ; configurez-les dans Nœud > Mises à jour > Dépôts.", "No additional GPU can be added.": "Aucun GPU supplémentaire ne peut être ajouté.", "No additional device needs to be added.": "Aucun appareil supplémentaire ne doit être ajouté.", @@ -4874,7 +4883,11 @@ "Proxmology logo applied": "Logo Proxmologie appliqué", "Proxmox 9 system update allready": "La mise à jour du système Proxmox 9 est déjà terminée", "Proxmox APT repositories configured": "Dépôts Proxmox APT configurés", + "Proxmox Backup Server": "Serveur de sauvegarde Proxmox", "Proxmox Backup Server (PBS) destinations": "Destinations Proxmox Backup Server (PBS)", + "Proxmox Backup Server APT repositories configured": "Dépôts APT du serveur de sauvegarde Proxmox configurés", + "Proxmox Backup Server Update": "Mise à jour du serveur de sauvegarde Proxmox", + "Proxmox Backup Server update completed.": "Mise à jour du serveur de sauvegarde Proxmox terminée.", "Proxmox CIFS Storage Status:": "État de stockage Proxmox CIFS :", "Proxmox NFS Storage Status:": "Statut de stockage Proxmox NFS :", "Proxmox System Update": "Mise à jour du système Proxmox", @@ -5234,6 +5247,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Assistant du référentiel indisponible. L'installation de NVIDIA s'est arrêtée.", "Repository policy returned an unexpected result.": "La stratégie du référentiel a renvoyé un résultat inattendu.", "Repository ready.": "Référentiel prêt.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "les sources du référentiel ont été modifiées, mais l'actualisation de la liste des packages APT a échoué. Opération arrêtée ;les sources n’ont pas été annulées. Inspectez Administration > Référentiels et réessayez apt-get update avant de continuer.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Les sources du référentiel ont été modifiées, mais l'actualisation de la liste des paquets APT a échoué. Opération arrêtée ; les sources n'ont pas été restaurées. Vérifiez Nœud > Mises à jour > Dépôts et relancez apt-get update avant de continuer.", "Repository switch declined; no APT source changed.": "Le changement de référentiel a été refusé ; aucune source APT n'a été modifiée.", "Repository:": "Dépôt:", @@ -6782,6 +6796,7 @@ "This CIFS share is mounted with restrictive permissions.": "Ce partage CIFS est monté avec des autorisations restrictives.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Ce GPU est considéré comme incompatible avec le relais GPU vers une VM dans ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Ce partage NFS est entièrement restreint : même la racine de l'hôte ne peut pas y écrire.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Ce serveur de sauvegarde Proxmox n'a pas d'abonnement actif, passer au référentiel sans abonnement ?La source Enterprise PBS inaccessible sera désactivée.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Cette VM nécessite des étapes d'installation supplémentaires. Consultez le guide d'installation à l'adresse :\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Ce compte concerne {registry}. Entrez une image de ce registre.", "This action will:": "Cette action va :", @@ -6865,6 +6880,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Ce script ne peut pas être exécuté depuis le terminal web Proxmox.", "This script must be run as root.": "Ce script doit être exécuté en tant que root.", "This script must be run on a Proxmox host.": "Ce script doit être exécuté sur un hôte Proxmox.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur de sauvegarde Proxmox", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Ce script mettra à jour votre système Proxmox VE avec des options avancées :", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Cette session est en cours d'exécution dans le terminal Monitor. L’exécuter à partir d’ici couperait la connexion en cours d’installation et laisserait le commutateur dans un état cassé.", @@ -7191,6 +7207,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Assistant de mise à niveau : Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Mise à niveau annulée par l'utilisateur", "Upgrade one node at a time": "Mettre à niveau un nœud à la fois", + "Upgrade the system, offering the": "mettre à niveau le système, en offrant le", "Upgrade to Proxmox VE 9": "Mise à niveau vers Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Mise à niveau des utilitaires NVIDIA (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Télécharger une copie cryptée de la clé sur PBS afin de pouvoir la récupérer sur un hôte réinstallé avec juste une phrase secrète ?", @@ -8110,6 +8127,7 @@ "was": "était", "was installed, but the kernel reports:": "a été installé, mais le noyau rapporte :", "when finished": "à la fin", + "when the server has no subscription (you will be asked)": "lorsque le serveur n'a pas d'abonnement (il vous sera demandé)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "reliera le GPU à vfio-pci au prochain redémarrage, cassant ainsi le pilote qui est sur le point d'être installé.", "wipefs failed on": "les wipefs ont échoué", "with": "avec", diff --git a/lang/it.json b/lang/it.json index 13e3c788..17313126 100644 --- a/lang/it.json +++ b/lang/it.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (percorso vzdump predefinito di Proxmox)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (predefinito di Proxmox)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = sticky bit + rwx per tutti. Non è necessario alcun gruppo condiviso.", + "====== PBS UPDATE COMPLETED ======": "====== AGGIORNAMENTO PBS COMPLETATO ======", "====== PVE UPDATE COMPLETED ======": "====== AGGIORNAMENTO PVE COMPLETATO ======", ">> Create a new backup job <<": ">> Crea un nuovo job di backup <<", ">> Save and return <<": ">> Salva e torna indietro <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Un’applicazione con più container non viene ricreata: i suoi container vengono aggiornati insieme.", "A new ProxMenux version is available:": "È disponibile una nuova versione di ProxMenux:", "A new kernel is staged for the next boot:": "Un nuovo kernel è predisposto per il prossimo avvio:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "è installato un kernel più recente. Riavviare prima della finestra di backup successiva per utilizzarlo.", "A newer version is available:": "È disponibile una versione più recente:", "A pending operation exists for": "Operazione in sospeso per il", "A pending stack assembly already exists; it is not overwritten": "Esiste già un assemblaggio dello stack in sospeso; non verrà sovrascritto", @@ -169,6 +171,7 @@ "APT language configuration updated": "Configurazione lingue APT aggiornata", "APT language downloads restored": "Download dei file lingua APT ripristinati", "APT package lists updated": "Elenchi pacchetti APT aggiornati", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "aggiornamento APT non riuscito. Controllare l'abbonamento e i repository PBS in Amministrazione > Repository. Tronco d'albero:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "Aggiornamento degli indici APT non riuscito. Controlla l'abbonamento e i repository PBS nell'interfaccia nativa di PBS; nessuna sorgente è stata modificata.", "APT update failed. Check repository access; NVIDIA installation stopped.": "Aggiornamento APT non riuscito. Controlla l’accesso al repository; installazione NVIDIA interrotta.", "Aborted": "Interrotto", @@ -538,6 +541,7 @@ "Available Borg targets:": "Destinazioni Borg disponibili:", "Available Disks on Host": "Dischi disponibili sull'host", "Available ISO Images": "Immagini ISO disponibili", + "Available PBS Version": "versione PBS disponibile", "Available PBS repositories:": "Repository PBS disponibili:", "Available PVE Version": "Versione PVE disponibile", "Available Shares:": "Condivisioni disponibili:", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Inventario NVIDIA attuale acquisito: è necessario un aggiornamento", "Current NVIDIA inventory resolved: no refresh is required": "Inventario NVIDIA corrente acquisito: non serve aggiornarlo", "Current Network Configuration": "Configurazione di rete corrente", + "Current PBS Version": "versione attuale di PBS", "Current PVE Version": "Versione PVE attuale", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Gli attuali script host ProxMenux registrano le condivisioni remote come storage Proxmox con pvesm.", "Current ProxMenux version:": "Versione attuale di ProxMenux:", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Hardware Coral rilevato:", "Detected GPU(s):": "GPU rilevate:", "Detected Intel CPU": "Rilevata CPU Intel", + "Detected Proxmox Backup Server": "Messaggio tecnico per Proxmox e IT.Traduzione: rilevato server di backup Proxmox", "Detected Proxmox VE": "Rilevato Proxmox VE", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Rilevato Proxmox VE 8.x - Installazione di Ceph Squid per Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Rilevato Proxmox VE 9.x - Installazione di Ceph Squid per Debian Trixie", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Vuoi procedere con l'aggiornamento del sistema?", "Do you want to proceed?": "Vuoi procedere?", "Do you want to reboot now?": "Vuoi riavviare ora?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Vuoi rimuovere il banner di abbonamento Proxmox dall'interfaccia web del server di backup Proxmox?", "Do you want to remove the stale entry from vfio.conf and continue?": "Vuoi rimuovere la voce obsoleta da vfio.conf e continuare?", "Do you want to remove the stale entry from vfio.conf?": "Vuoi rimuovere la voce obsoleta da vfio.conf?", "Do you want to restart the network service now to apply changes?": "Riavviare ora il servizio di rete per applicare le modifiche?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Impossibile clonare il repository log2ram. Controlla /tmp/log2ram_install.log", "Failed to configure EFI disk": "Impossibile configurare il disco EFI", "Failed to configure IOMMU automatically.": "Impossibile configurare IOMMU automaticamente.", + "Failed to configure Proxmox Backup Server repositories": "Messaggio tecnico per Proxmox e IT.Traduzione: impossibile configurare i repository del server di backup Proxmox", "Failed to configure Proxmox repositories": "Impossibile configurare i repository Proxmox", "Failed to configure TPM device in VM": "Impossibile configurare il dispositivo TPM nella VM", "Failed to configure repositories.": "Impossibile configurare i repository.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Nessuno spazio di archiviazione Proxmox attivo accetta archivi modello OCI.", "No active exports or showmount not available": "Nessuna esportazione attiva o showmount non disponibile", "No active session": "Nessuna sessione attiva", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "nessun abbonamento attivo e nessun repository PBS utilizzabile. La modalità non interattiva non può modificare le origini APT;configurarli in Amministrazione > Repository.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Nessun abbonamento attivo e nessun repository PVE utilizzabile. La modalità non interattiva non può modificare le origini APT; configurarle in Nodo > Aggiornamenti > Repository.", "No additional GPU can be added.": "Non è possibile aggiungere alcuna GPU aggiuntiva.", "No additional device needs to be added.": "Non è necessario aggiungere alcun dispositivo aggiuntivo.", @@ -4874,7 +4883,11 @@ "Proxmology logo applied": "Logo Proxmology applicato", "Proxmox 9 system update allready": "Il sistema è già aggiornato a Proxmox 9", "Proxmox APT repositories configured": "Repository APT Proxmox configurati", + "Proxmox Backup Server": "Server di backup Proxmox", "Proxmox Backup Server (PBS) destinations": "Destinazioni Proxmox Backup Server (PBS)", + "Proxmox Backup Server APT repositories configured": "Proxmox Repository APT del server di backup configurati", + "Proxmox Backup Server Update": "Aggiornamento del server di backup Proxmox", + "Proxmox Backup Server update completed.": "aggiornamento del server di backup Proxmox completato.", "Proxmox CIFS Storage Status:": "Stato storage CIFS Proxmox:", "Proxmox NFS Storage Status:": "Stato storage NFS Proxmox:", "Proxmox System Update": "Aggiornamento sistema Proxmox", @@ -5234,6 +5247,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Funzione di gestione dei repository non disponibile. L’installazione di NVIDIA è stata interrotta.", "Repository policy returned an unexpected result.": "La policy del repository ha restituito un risultato imprevisto.", "Repository ready.": "Repository pronto.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Messaggio tecnico per Proxmox e IT.Traduzione: le origini del repository sono cambiate, ma l'aggiornamento dell'elenco dei pacchetti APT non è riuscito. Operazione interrotta;le fonti non sono state ripristinate. Ispezionare Amministrazione > Repository e riprovare apt-get update prima di continuare.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Le origini del repository sono cambiate, ma l'aggiornamento dell'elenco dei pacchetti APT non è riuscito. Operazione interrotta; le origini non sono state ripristinate. Controlla Nodo > Aggiornamenti > Repository e riprova apt-get update prima di continuare.", "Repository switch declined; no APT source changed.": "Cambio di repository rifiutato; nessuna origine APT è stata modificata.", "Repository:": "Repository:", @@ -6783,6 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Questa condivisione CIFS è montata con autorizzazioni restrittive.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Questa GPU è considerata incompatibile con il passthrough GPU a una VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Questa condivisione NFS è completamente limitata: nemmeno l'utente root dell'host può scrivervi.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "questo server di backup Proxmox non ha un abbonamento attivo, passare al repository senza abbonamento? L'origine Enterprise PBS inaccessibile verrà disabilitata.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Questa VM richiede ulteriori passaggi di installazione; vedere la guida:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Questo account è per {registry}. Inserisci un'immagine da quel registro.", "This action will:": "Questa azione:", @@ -6866,6 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Questo script non può essere eseguito dal terminale web Proxmox.", "This script must be run as root.": "Questo script deve essere eseguito come root.", "This script must be run on a Proxmox host.": "Questo script deve essere eseguito su un host Proxmox.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al server di backup Proxmox", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al server Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Questo script aggiornerà il sistema Proxmox VE con opzioni avanzate:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Questa sessione è in esecuzione nel terminale Monitor. Eseguirlo da qui interromperebbe la connessione a metà installazione e lascerebbe il cambio di canale incompleto.", @@ -7192,6 +7208,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Assistente aggiornamento: Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Aggiornamento annullato dall'utente", "Upgrade one node at a time": "Aggiorna un nodo alla volta", + "Upgrade the system, offering the": "aggiorna il sistema, offrendo il", "Upgrade to Proxmox VE 9": "Aggiorna a Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Aggiornamento utilità NVIDIA (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Caricare una copia crittografata della chiave su PBS per recuperarla su un host reinstallato con la sola passphrase?", @@ -8111,6 +8128,7 @@ "was": "era", "was installed, but the kernel reports:": "è stato installato, ma il kernel segnala:", "when finished": "al termine", + "when the server has no subscription (you will be asked)": "quando il server non ha abbonamento (ti verrà chiesto)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "riassocerà la GPU a vfio-pci al prossimo riavvio, impedendo il funzionamento del driver che sta per essere installato.", "wipefs failed on": "wipefs non è riuscito su", "with": "con", diff --git a/lang/pt.json b/lang/pt.json index e95b4413..469dca2d 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (caminho vzdump padrão do Proxmox)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (padrão Proxmox)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = sticky bit + rwx para todos. Nenhum grupo compartilhado é necessário.", + "====== PBS UPDATE COMPLETED ======": "====== ATUALIZAÇÃO PBS CONCLUÍDA ======", "====== PVE UPDATE COMPLETED ======": "====== ATUALIZAÇÃO PVE CONCLUÍDA ======", ">> Create a new backup job <<": ">> Crie uma nova tarefa de backup <<", ">> Save and return <<": ">> Salvar e retornar <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Um aplicativo com vários contêineres não é recriado: seus contêineres são atualizados juntos.", "A new ProxMenux version is available:": "Uma nova versão do ProxMenux está disponível:", "A new kernel is staged for the next boot:": "Um novo kernel está preparado para a próxima inicialização:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "Um kernel mais recente está instalado. Reinicie antes da próxima janela de backup para usá-lo.", "A newer version is available:": "Uma versão mais recente está disponível:", "A pending operation exists for": "Existe uma operation pendente para", "A pending stack assembly already exists; it is not overwritten": "Já existe um conjunto de pilha pendente; não é substituído", @@ -169,6 +171,7 @@ "APT language configuration updated": "Configuração de idioma APT atualizada", "APT language downloads restored": "Downloads de idiomas APT restaurados", "APT package lists updated": "Listas de pacotes APT atualizadas", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "falha na atualização do APT.Verifique a assinatura e os repositórios do PBS em Administração > Repositórios. Registro:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "Falha ao atualizar os índices APT. Verifique a subscrição e os repositórios PBS na interface nativa do PBS; nenhuma fonte foi alterada.", "APT update failed. Check repository access; NVIDIA installation stopped.": "Falha na atualização do APT. Verifique o acesso ao repositório; a instalação do NVIDIA foi interrompida.", "Aborted": "Abortado", @@ -538,6 +541,7 @@ "Available Borg targets:": "Alvos Borg disponíveis:", "Available Disks on Host": "Discos disponíveis no host", "Available ISO Images": "Imagens ISO disponíveis", + "Available PBS Version": "versão PBS disponível", "Available PBS repositories:": "Repositórios PBS disponíveis:", "Available PVE Version": "Versão PVE disponível", "Available Shares:": "Ações disponíveis:", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Inventário NVIDIA atual resolvido: uma atualização é required", "Current NVIDIA inventory resolved: no refresh is required": "Inventário NVIDIA atual resolvido: nenhuma atualização é required", "Current Network Configuration": "Configuração de rede atual", + "Current PBS Version": "versão atual do PBS", "Current PVE Version": "Versão atual do PVE", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Os scripts atuais do host ProxMenux registram compartilhamentos remotos como armazenamentos Proxmox usando pvesm.", "Current ProxMenux version:": "Versão atual do ProxMenux:", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Hardware Coral detectado:", "Detected GPU(s):": "GPU(s) detectada(s):", "Detected Intel CPU": "CPU Intel detectada", + "Detected Proxmox Backup Server": "Servidor de backup Proxmox detectado", "Detected Proxmox VE": "Proxmox VE detectado", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Detectado Proxmox VE 8.x - Instalando Ceph Squid para Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Detectado Proxmox VE 9.x - Instalando Ceph Squid para Debian Trixie", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Deseja prosseguir com a atualização do sistema?", "Do you want to proceed?": "Você quer prosseguir?", "Do you want to reboot now?": "Você quer reiniciar agora?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Você deseja remover o banner de assinatura Proxmox da interface da web do Servidor de Backup Proxmox?", "Do you want to remove the stale entry from vfio.conf and continue?": "Deseja remover a entrada obsoleta do vfio.conf e continuar?", "Do you want to remove the stale entry from vfio.conf?": "Você deseja remover a entrada obsoleta do vfio.conf?", "Do you want to restart the network service now to apply changes?": "Deseja reiniciar o serviço de rede agora para aplicar as alterações?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Falha ao clonar o repositório log2ram. Verifique /tmp/log2ram_install.log", "Failed to configure EFI disk": "Falha ao configurar o disco EFI", "Failed to configure IOMMU automatically.": "Falha ao configurar o IOMMU automaticamente.", + "Failed to configure Proxmox Backup Server repositories": "Falha ao configurar repositórios do servidor de backup Proxmox", "Failed to configure Proxmox repositories": "Falha ao configurar repositórios Proxmox", "Failed to configure TPM device in VM": "Falha ao configurar o dispositivo TPM na VM", "Failed to configure repositories.": "Falha ao configurar repositórios.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Nenhum armazenamento Proxmox ativo aceita arquivos de modelo OCI.", "No active exports or showmount not available": "Nenhuma exportação ativa ou showmount não disponível", "No active session": "Nenhuma sessão ativa", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "Nenhuma assinatura ativa e nenhum repositório PBS utilizável. O modo não interativo não pode alterar fontes APT;configure-os em Administração > Repositórios.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Nenhuma assinatura ativa e nenhum repositório PVE utilizável. O modo não interativo não pode alterar fontes APT; configure-as em Nó > Atualizações > Repositórios.", "No additional GPU can be added.": "Nenhuma GPU adicional pode ser adicionada.", "No additional device needs to be added.": "Nenhum dispositivo adicional precisa ser adicionado.", @@ -4874,7 +4883,11 @@ "Proxmology logo applied": "Logotipo da Proxmologia aplicado", "Proxmox 9 system update allready": "Atualização do sistema Proxmox 9 já", "Proxmox APT repositories configured": "repositórios Proxmox APT configurados", + "Proxmox Backup Server": "Servidor de backup Proxmox", "Proxmox Backup Server (PBS) destinations": "Destinos Proxmox Backup Server (PBS)", + "Proxmox Backup Server APT repositories configured": "repositórios APT do servidor de backup Proxmox configurados", + "Proxmox Backup Server Update": "Atualização do servidor de backup Proxmox", + "Proxmox Backup Server update completed.": "atualização do servidor de backup Proxmox concluída.", "Proxmox CIFS Storage Status:": "Status de armazenamento CIFS do Proxmox:", "Proxmox NFS Storage Status:": "Status de armazenamento NFS do Proxmox:", "Proxmox System Update": "Atualização do sistema Proxmox", @@ -5234,6 +5247,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Auxiliar de repositório indisponível. A instalação do NVIDIA foi interrompida.", "Repository policy returned an unexpected result.": "A política do repositório retornou um resultado inesperado.", "Repository ready.": "Repositório pronto.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "as fontes do repositório foram alteradas, mas a atualização da lista de pacotes APT falhou. Operação interrompida;fontes não foram revertidas. Inspecione Administração > Repositórios e tente apt-get update novamente antes de continuar.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "As fontes do repositório foram alteradas, mas a atualização da lista de pacotes APT falhou. Operação interrompida; as fontes não foram revertidas. Verifique Nó > Atualizações > Repositórios e tente apt-get update novamente antes de continuar.", "Repository switch declined; no APT source changed.": "Troca de repositório recusada; nenhuma fonte APT foi alterada.", "Repository:": "Repositório:", @@ -6783,6 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Este compartilhamento CIFS é montado com permissões restritivas.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU é considerada incompatível com a passagem de GPU para uma VM no ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este compartilhamento NFS é totalmente restrito — até mesmo o host root não pode gravar nele.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Este servidor de backup Proxmox não tem assinatura ativa, mudar para o repositório sem assinatura? A fonte inacessível do Enterprise PBS será desativada.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Esta VM requer etapas extras de instalação. Consulte o guia de instalação em:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Esta conta é para {registry}. Insira uma imagem desse registro.", "This action will:": "Esta ação irá:", @@ -6866,6 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Este script não pode ser executado no terminal web Proxmox.", "This script must be run as root.": "Este script deve ser executado como root.", "This script must be run on a Proxmox host.": "Este script deve ser executado em um host Proxmox.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor de backup Proxmox", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Este script atualizará seu sistema Proxmox VE com opções avançadas:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Esta sessão está sendo executada no terminal Monitor. Executá-lo a partir daqui cortaria a conexão no meio da instalação e deixaria o switch quebrado.", @@ -7192,6 +7208,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Assistente de atualização: Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Atualização cancelada pelo usuário", "Upgrade one node at a time": "Atualize um nó por vez", + "Upgrade the system, offering the": "Atualize o sistema, oferecendo o", "Upgrade to Proxmox VE 9": "Atualize para Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Atualizando utilitários NVIDIA (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Carregar uma cópia criptografada da chave para o PBS para que você possa recuperá-la em um host reinstalado com apenas uma senha?", @@ -8111,6 +8128,7 @@ "was": "era", "was installed, but the kernel reports:": "foi instalado, mas o kernel informa:", "when finished": "quando terminado", + "when the server has no subscription (you will be asked)": "quando o servidor não tem assinatura (será solicitado)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "irá religar a GPU ao vfio-pci na próxima reinicialização, quebrando o driver que está prestes a ser instalado.", "wipefs failed on": "Wipefs falhou em", "with": "com", diff --git a/lang/sk.json b/lang/sk.json index c29bb126..a6e9042a 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (predvolená cesta Proxmox vzdump)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (predvolené v Proxmoxe)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = sticky bit + rwx pre všetkých. Zdieľaná skupina nie je potrebná.", + "====== PBS UPDATE COMPLETED ======": "====== AKTUALIZÁCIA PBS DOKONČENÁ ======", "====== PVE UPDATE COMPLETED ======": "====== AKTUALIZÁCIA PVE DOKONČENÁ ======", ">> Create a new backup job <<": ">> Vytvoriť novú úlohu zálohovania <<", ">> Save and return <<": ">> Uložiť a vrátiť <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Aplikácia s viacerými kontajnermi sa nevytvorí znova: jej kontajnery sa aktualizujú spoločne.", "A new ProxMenux version is available:": "Je dostupná nová verzia ProxMenux:", "A new kernel is staged for the next boot:": "Na najbližší štart je pripravený nový kernel:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "Je nainštalované novšie jadro. Aby ste ho mohli použiť, reštartujte ho pred ďalším oknom zálohy.", "A newer version is available:": "Je dostupná novšia verzia:", "A pending operation exists for": "Čaká na operation existuje", "A pending stack assembly already exists; it is not overwritten": "Nevyriešená zostava stohov už existuje; nie je prepísaná", @@ -169,6 +171,7 @@ "APT language configuration updated": "Nastavenie jazykov pre APT bolo aktualizované", "APT language downloads restored": "Sťahovanie jazykov cez APT bolo obnovené", "APT package lists updated": "Zoznamy APT balíkov aktualizované", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "Obnovenie APT zlyhalo. Skontrolujte predplatné a repozitáre PBS v časti Správa > Repozitáre. Denník:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "Obnovenie indexov APT zlyhalo. Skontrolujte predplatné a repozitáre PBS v pôvodnom rozhraní PBS; žiadne zdroje sa nezmenili.", "APT update failed. Check repository access; NVIDIA installation stopped.": "Aktualizácia APT zlyhala. Skontrolujte prístup k úložisku; inštalácia NVIDIA sa zastavila.", "Aborted": "Zrušené", @@ -538,6 +541,7 @@ "Available Borg targets:": "Dostupné Borg ciele:", "Available Disks on Host": "Dostupné disky na hostovi", "Available ISO Images": "Dostupné ISO obrazy", + "Available PBS Version": "Dostupná verzia PBS", "Available PBS repositories:": "Dostupné PBS repozitáre:", "Available PVE Version": "Dostupná verzia PVE", "Available Shares:": "Dostupné zdieľania:", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Aktuálne NVIDIA inventár vyriešený: obnovovanie je required", "Current NVIDIA inventory resolved: no refresh is required": "Aktuálne NVIDIA inventár vyriešený: žiadne obnovenie je required", "Current Network Configuration": "Aktuálne nastavenie siete", + "Current PBS Version": "Aktuálna verzia PBS", "Current PVE Version": "Aktuálna verzia PVE", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Aktuálne host skripty ProxMenux registrujú vzdialené zdieľania ako úložiská Proxmoxu pomocou pvesm.", "Current ProxMenux version:": "Aktuálna verzia ProxMenux:", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Nájdený Coral hardvér:", "Detected GPU(s):": "Nájdené GPU:", "Detected Intel CPU": "Zistený Intel procesor", + "Detected Proxmox Backup Server": "Zistený záložný server Proxmox", "Detected Proxmox VE": "Zistený Proxmox VE", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Zistený Proxmox VE 8.x - inštalujem Ceph Squid pre Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Zistený Proxmox VE 9.x - inštalujem Ceph Squid pre Debian Trixie", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Chcete pokračovať v aktualizácii systému?", "Do you want to proceed?": "Chcete pokračovať?", "Do you want to reboot now?": "Chcete reštartovať teraz?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Chcete odstrániť banner predplatného Proxmox z webového rozhrania záložného servera Proxmox?", "Do you want to remove the stale entry from vfio.conf and continue?": "Chcete odstrániť zastaraný záznam z vfio.conf a pokračovať?", "Do you want to remove the stale entry from vfio.conf?": "Chcete odstrániť zastaraný záznam z vfio.conf?", "Do you want to restart the network service now to apply changes?": "Chcete teraz reštartovať sieťovú službu, aby sa zmeny prejavili?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Repozitár log2ram sa nepodarilo stiahnuť. Skontrolujte /tmp/log2ram_install.log", "Failed to configure EFI disk": "EFI disk sa nepodarilo nastaviť", "Failed to configure IOMMU automatically.": "IOMMU sa nepodarilo nastaviť automaticky.", + "Failed to configure Proxmox Backup Server repositories": "Nepodarilo sa nakonfigurovať archívy záložného servera Proxmox", "Failed to configure Proxmox repositories": "Repozitáre Proxmoxu sa nepodarilo nastaviť", "Failed to configure TPM device in VM": "TPM zariadenie sa vo VM nepodarilo nastaviť", "Failed to configure repositories.": "Repozitáre sa nepodarilo nastaviť.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Žiadne aktívne úložisko Proxmox neakceptuje archívy šablón OCI.", "No active exports or showmount not available": "Nie sú aktívne exporty alebo príkaz showmount nie je dostupný", "No active session": "Žiadna aktívna relácia", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "Žiadne aktívne predplatné a žiadne použiteľné úložisko PBS.Neinteraktívny režim nemôže zmeniť zdroje APT;nakonfigurujte ich v Administrácia > Repozitáre.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Žiadne aktívne predplatné a žiadne použiteľné úložisko PVE. Neinteraktívny režim nemôže zmeniť zdroje APT; nakonfigurujte ich v časti Uzol > Aktualizácie > Úložiská.", "No additional GPU can be added.": "Nie je možné pridať ďalšiu GPU.", "No additional device needs to be added.": "Netreba pridávať žiadne ďalšie zariadenie.", @@ -4876,7 +4885,11 @@ "Proxmology logo applied": "Logo Proxmology bolo použité", "Proxmox 9 system update allready": "Systém Proxmox 9 je už aktualizovaný", "Proxmox APT repositories configured": "APT repozitáre Proxmoxu sú nastavené", + "Proxmox Backup Server": "Záložný server Proxmox", "Proxmox Backup Server (PBS) destinations": "Ciele Proxmox Backup Servera (PBS)", + "Proxmox Backup Server APT repositories configured": "Záložné úložiská APT servera Proxmox sú nakonfigurované", + "Proxmox Backup Server Update": "Aktualizácia záložného servera Proxmox", + "Proxmox Backup Server update completed.": "Aktualizácia záložného servera Proxmox bola dokončená.", "Proxmox CIFS Storage Status:": "Stav CIFS úložiska v Proxmoxe:", "Proxmox NFS Storage Status:": "Stav NFS úložiska v Proxmoxe:", "Proxmox System Update": "Aktualizácia systému Proxmox", @@ -5236,6 +5249,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Pomocník úložiska nie je k dispozícii. Inštalácia NVIDIA sa zastavila.", "Repository policy returned an unexpected result.": "Politika úložiska vrátila neočakávaný výsledok.", "Repository ready.": "Repozitár je pripravený.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Zdroje úložiska sa zmenili, ale obnovenie zoznamu balíkov APT zlyhalo. Prevádzka zastavená;zdroje neboli vrátené späť. Pred pokračovaním skontrolujte Administrácia > Repozitáre a zopakujte apt-get update.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Zdroje úložiska sa zmenili, ale obnovenie zoznamu balíkov APT zlyhalo. Prevádzka zastavená; zdroje neboli vrátené späť. Pred pokračovaním skontrolujte Node > Updates > Repository a zopakujte apt-get update.", "Repository switch declined; no APT source changed.": "Prepnutie úložiska bolo odmietnuté; žiadny zdroj APT sa nezmenil.", "Repository:": "Repozitár:", @@ -6784,6 +6798,7 @@ "This CIFS share is mounted with restrictive permissions.": "Toto CIFS zdieľanie je pripojené s obmedzujúcimi oprávneniami.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Táto GPU sa v ProxMenux považuje za nekompatibilnú s GPU passthrough do VM.", "This NFS share is fully restricted — even the host root cannot write to it.": "Toto NFS zdieľanie je úplne obmedzené - ani root na hostovi doň nevie zapisovať.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Tento záložný server Proxmox nemá žiadne aktívne predplatné, prepnúť na úložisko bez predplatného? Neprístupný zdroj Enterprise PBS bude zakázaný.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Táto VM vyžaduje ďalšie kroky inštalácie. Návod nájdete tu:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Tento účet je pre {registry}. Zadajte obrázok z tohto registra.", "This action will:": "Táto akcia vykoná:", @@ -6867,6 +6882,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Tento skript sa nedá spustiť z webového terminálu Proxmoxu.", "This script must be run as root.": "Tento skript musí byť spustený ako root.", "This script must be run on a Proxmox host.": "Tento skript musí byť spustený na Proxmox serveri.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Tento skript použije nasledujúce optimalizácie a pokročilé úpravy na váš záložný server Proxmox", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Tento skript použije tieto optimalizácie a pokročilé úpravy na vašom serveri Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Tento skript aktualizuje váš Proxmox VE systém s pokročilými možnosťami:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Táto relácia beží v termináli Monitora. Ak by sa to spustilo odtiaľto, pripojenie sa počas inštalácie preruší a prepnutie môže zostať v zlom stave.", @@ -7193,6 +7209,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Asistent aktualizácie: Proxmox VE 8 → 9 (Trixie)", "Upgrade canceled by user": "Aktualizácia zrušená používateľom", "Upgrade one node at a time": "Aktualizujte vždy iba jeden uzol naraz", + "Upgrade the system, offering the": "Aktualizujte systém a ponúknite", "Upgrade to Proxmox VE 9": "Aktualizovať na Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Aktualizujem NVIDIA nástroje (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Nahrať šifrovanú kópiu kľúča do PBS, aby ste ju vedeli obnoviť na preinštalovanom hostovi iba pomocou frázy?", @@ -8112,6 +8129,7 @@ "was": "bolo", "was installed, but the kernel reports:": "bol nainštalovaný, ale jadro hlási:", "when finished": "po dokončení", + "when the server has no subscription (you will be asked)": "keď server nemá predplatné (budete požiadaný)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "pri ďalšom reštarte znova naviaže GPU na vfio-pci, čím sa preruší ovládač, ktorý sa má nainštalovať.", "wipefs failed on": "wipefs zlyhalo na", "with": "za", diff --git a/lang/sv.json b/lang/sv.json index 377b3f16..57a2c152 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -37,6 +37,7 @@ "/var/lib/vz/dump (Proxmox default vzdump path)": "/var/lib/vz/dump (Proxmox standard vzdump sökväg)", "/var/lib/vz/dump (Proxmox default)": "/var/lib/vz/dump (Proxmox standard)", "1777 = sticky bit + rwx for all. No shared group needed.": "1777 = sticky bit + rwx för alla. Ingen delad grupp behövs.", + "====== PBS UPDATE COMPLETED ======": "====== PBS UPPDATERING AVSLUTAD ======", "====== PVE UPDATE COMPLETED ======": "====== PVE UPPDATERING AVSLUTAD ======", ">> Create a new backup job <<": ">> Skapa ett nytt säkerhetskopieringsjobb <<", ">> Save and return <<": ">> Spara och returnera <<", @@ -87,6 +88,7 @@ "A multi-container application is not recreated: its containers are updated together.": "Ett program med flera behållare återskapas inte: dess behållare uppdateras tillsammans.", "A new ProxMenux version is available:": "En ny ProxMenux-version är tillgänglig:", "A new kernel is staged for the next boot:": "En ny kärna är iscensatt för nästa uppstart:", + "A newer kernel is installed. Reboot before the next backup window to use it.": "En nyare kärna är installerad. Starta om innan nästa säkerhetskopieringsfönster för att använda det.", "A newer version is available:": "En nyare version finns tillgänglig:", "A pending operation exists for": "En väntande operation finns för", "A pending stack assembly already exists; it is not overwritten": "En väntande stack församling existerar redan; den är inte överskriven", @@ -169,6 +171,7 @@ "APT language configuration updated": "APT-språkkonfigurationen uppdaterad", "APT language downloads restored": "APT-språknedladdningar återställda", "APT package lists updated": "APT-paketlistor uppdaterade", + "APT refresh failed. Check the PBS subscription and repositories in Administration > Repositories. Log:": "APT-uppdatering misslyckades. Kontrollera PBS-prenumerationen och arkiven i Administration > Repositories. Logga:", "APT refresh failed. Check the PBS subscription and repositories in the native PBS interface; no source was changed.": "APT-uppdateringen misslyckades. Kontrollera PBS-prenumerationen och paketförråden i PBS eget gränssnitt; inga paketkällor ändrades.", "APT update failed. Check repository access; NVIDIA installation stopped.": "APT-uppdateringen misslyckades. Kontrollera åtkomsten till arkiven; NVIDIA-installationen stoppades.", "Aborted": "Avbruten", @@ -538,6 +541,7 @@ "Available Borg targets:": "Tillgängliga Borg-mål:", "Available Disks on Host": "Tillgängliga diskar på värd", "Available ISO Images": "Tillgängliga ISO-bilder", + "Available PBS Version": "Tillgänglig PBS-version", "Available PBS repositories:": "Tillgängliga PBS-lager:", "Available PVE Version": "Tillgänglig PVE-version", "Available Shares:": "Tillgängliga andelar:", @@ -1520,6 +1524,7 @@ "Current NVIDIA inventory resolved: a refresh is required": "Nuvarande NVIDIA inventering löst: en uppfriskning är required", "Current NVIDIA inventory resolved: no refresh is required": "Nuvarande NVIDIA inventering löst: ingen uppfriskning är required", "Current Network Configuration": "Aktuell nätverkskonfiguration", + "Current PBS Version": "Aktuell PBS-version", "Current PVE Version": "Aktuell PVE-version", "Current ProxMenux host scripts register remote shares as Proxmox storages using pvesm.": "Aktuella ProxMenux-värdskript registrerar fjärrresurser som Proxmox-lagringar med hjälp av pvesm.", "Current ProxMenux version:": "Aktuell ProxMenux-version:", @@ -1685,6 +1690,7 @@ "Detected Coral hardware:": "Upptäckt Coral-hårdvara:", "Detected GPU(s):": "Upptäckta GPU:er:", "Detected Intel CPU": "Upptäckte Intel CPU", + "Detected Proxmox Backup Server": "Upptäckte Proxmox Backup Server", "Detected Proxmox VE": "Detekterade Proxmox VE", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Upptäckte Proxmox VE 8.x - Installerar Ceph Squid för Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Upptäckte Proxmox VE 9.x - Installerar Ceph Squid för Debian Trixie", @@ -1845,6 +1851,7 @@ "Do you want to proceed with the system update?": "Vill du fortsätta med systemuppdateringen?", "Do you want to proceed?": "Vill du fortsätta?", "Do you want to reboot now?": "Vill du starta om nu?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Vill du ta bort Proxmox-prenumerationsbannern från Proxmox Backup Server-webbgränssnittet?", "Do you want to remove the stale entry from vfio.conf and continue?": "Vill du ta bort den inaktuella posten från vfio.conf och fortsätta?", "Do you want to remove the stale entry from vfio.conf?": "Vill du ta bort den inaktuella posten från vfio.conf?", "Do you want to restart the network service now to apply changes?": "Vill du starta om nätverkstjänsten nu för att tillämpa ändringar?", @@ -2270,6 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Det gick inte att klona log2ram-förrådet. Kontrollera /tmp/log2ram_install.log", "Failed to configure EFI disk": "Det gick inte att konfigurera EFI-disken", "Failed to configure IOMMU automatically.": "Det gick inte att konfigurera IOMMU automatiskt.", + "Failed to configure Proxmox Backup Server repositories": "Det gick inte att konfigurera Proxmox Backup Server-förråd", "Failed to configure Proxmox repositories": "Det gick inte att konfigurera Proxmox-förråd", "Failed to configure TPM device in VM": "Det gick inte att konfigurera TPM-enhet i virtuell dator", "Failed to configure repositories.": "Det gick inte att konfigurera förråd.", @@ -4157,6 +4165,7 @@ "No active Proxmox storage accepts OCI template archives.": "Ingen aktiv Proxmox-lagring accepterar OCI-mallarkiv.", "No active exports or showmount not available": "Inga aktiva exporter eller showmount är inte tillgängligt", "No active session": "Ingen aktiv session", + "No active subscription and no usable PBS repository. Noninteractive mode cannot change APT sources; configure them in Administration > Repositories.": "Inget aktivt abonnemang och inget användbart PBS-förråd. Icke-interaktivt läge kan inte ändra APT-källor;konfigurera dem i Administration > Repositories.", "No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.": "Ingen aktiv prenumeration och inget användbart PVE-arkiv. Icke-interaktivt läge kan inte ändra APT-källor; konfigurera dem i Node > Updates > Repositories.", "No additional GPU can be added.": "Ingen ytterligare GPU kan läggas till.", "No additional device needs to be added.": "Ingen ytterligare enhet behöver läggas till.", @@ -4875,6 +4884,8 @@ "Proxmox 9 system update allready": "Proxmox 9 systemuppdatering redan", "Proxmox APT repositories configured": "Proxmox APT-förråd konfigurerade", "Proxmox Backup Server (PBS) destinations": "Destinationer för Proxmox Backup Server (PBS)", + "Proxmox Backup Server APT repositories configured": "Proxmox Backup Server APT-förråd konfigurerade", + "Proxmox Backup Server update completed.": "Proxmox Backup Server-uppdatering slutförd.", "Proxmox CIFS Storage Status:": "Proxmox CIFS-lagringsstatus:", "Proxmox NFS Storage Status:": "Proxmox NFS-lagringsstatus:", "Proxmox System Update": "Proxmox systemuppdatering", @@ -5234,6 +5245,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Arkivhjälparen är inte tillgänglig. NVIDIA-installationen stoppades.", "Repository policy returned an unexpected result.": "Arkivpolicyn gav ett oväntat resultat.", "Repository ready.": "Förvaret klart.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Lagringskällorna har ändrats, men uppdateringen av APT-paketlistan misslyckades. Drift stoppad;källor rullades inte tillbaka. Inspektera Administration > Repositories och försök apt-get update igen innan du fortsätter.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Arkivkällorna har ändrats, men uppdateringen av APT-paketlistan misslyckades. Åtgärden stoppades; källorna återställdes inte. Kontrollera Node > Updates > Repositories och försök apt-get update igen innan du fortsätter.", "Repository switch declined; no APT source changed.": "Arkivbytet avvisades; ingen APT-källa har ändrats.", "Repository:": "Förvar:", @@ -6783,6 +6795,7 @@ "This CIFS share is mounted with restrictive permissions.": "Denna CIFS-andel är monterad med restriktiva behörigheter.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Denna GPU anses vara inkompatibel med GPU-genomföring till en virtuell dator i ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Denna NFS-resurs är helt begränsad — även värdroten kan inte skriva till den.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Denna Proxmox Backup Server har ingen aktiv prenumeration, byta till arkivet utan prenumeration? Den otillgängliga Enterprise PBS-källan kommer att inaktiveras.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Denna virtuella dator kräver extra installationssteg, se installationsguiden på:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Detta konto är för {registry}. Ange en bild från det registret.", "This action will:": "Denna åtgärd kommer att:", @@ -6866,6 +6879,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Detta skript kan inte köras från Proxmox webbterminal.", "This script must be run as root.": "Detta skript måste köras som root.", "This script must be run on a Proxmox host.": "Detta skript måste köras på en Proxmox-värd.", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Detta skript kommer att tillämpa följande optimeringar och avancerade justeringar på din Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Detta skript kommer att tillämpa följande optimeringar och avancerade justeringar på din Proxmox VE-server", "This script will update your Proxmox VE system with advanced options:": "Detta skript kommer att uppdatera ditt Proxmox VE-system med avancerade alternativ:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Den här sessionen körs i Monitor-terminalen. Om du kör den härifrån bryts anslutningen mitt under installationen och läget förblir felaktigt.", @@ -7192,6 +7206,7 @@ "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)": "Uppgraderingsassistent: Proxmox VE 8 → 9 (Ttrixie)", "Upgrade canceled by user": "Uppgraderingen avbröts av användaren", "Upgrade one node at a time": "Uppgradera en nod i taget", + "Upgrade the system, offering the": "Uppgradera systemet och erbjuder", "Upgrade to Proxmox VE 9": "Uppgradera till Proxmox VE 9", "Upgrading NVIDIA utils (Arch)...": "Uppgraderar NVIDIA utils (Arch)...", "Upload an encrypted copy of the key to PBS so you can recover it on a reinstalled host with just a passphrase?": "Ladda upp en krypterad kopia av nyckeln till PBS så att du kan återställa den på en ominstallerad värd med bara en lösenfras?", @@ -8111,6 +8126,7 @@ "was": "var", "was installed, but the kernel reports:": "installerades, men kärnan rapporterar:", "when finished": "När den är färdig", + "when the server has no subscription (you will be asked)": "när servern inte har något abonnemang (du kommer att bli tillfrågad)", "will rebind the GPU to vfio-pci on the next reboot, breaking the driver that is about to be installed.": "kommer att binda om GPU:n till vfio-pci vid nästa omstart, vilket bryter drivrutinen som är på väg att installeras.", "wipefs failed on": "wipefs misslyckades", "with": "med", From 592354b0e0e1bc8b1d842958310837bce369aa0f Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 17 Oct 2026 18:28:54 +0200 Subject: [PATCH 28/53] keep the Proxmox Backup Server name in PBS and node selector texts --- AppImage/messages/de/common.json | 6 +++--- AppImage/messages/fr/common.json | 8 ++++---- AppImage/messages/it/common.json | 8 ++++---- AppImage/messages/pt/common.json | 8 ++++---- AppImage/messages/sk/common.json | 6 +++--- AppImage/messages/sv/common.json | 4 ++-- lang/de.json | 12 ++++++------ lang/es.json | 2 +- lang/fr.json | 18 +++++++++--------- lang/it.json | 20 ++++++++++---------- lang/pt.json | 18 +++++++++--------- lang/sk.json | 18 +++++++++--------- 12 files changed, 64 insertions(+), 64 deletions(-) diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index 37f27e74..eb4fc2ec 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -7338,18 +7338,18 @@ "groups": { "cluster": "Cluster", "standalone": "Eigenständige Knoten", - "pbs": "Proxmox Sicherungsserver" + "pbs": "Proxmox Backup Server" }, "thisNode": "Dieser Knoten", "checking": "Knoten werden überprüft…", "loadFailed": "Die Knotenliste konnte nicht geladen werden.", - "empty": "Noch keine anderen Knoten.Fügen Sie einen hinzu, um den Monitor von hier aus zu öffnen.", + "empty": "Noch keine anderen Knoten. Fügen Sie einen hinzu, um seinen Monitor von hier aus zu öffnen.", "noResponse": "Keine Antwort", "pbsInterface": "PBS-Schnittstelle", "openNewTab": "In einem neuen Tab öffnen", "manage": "Knoten hinzufügen oder verwalten…", "dialogTitle": "Verknüpfte Knoten", - "dialogDescription": "Clusterknoten und konfigurierte Proxmox-Sicherungsserver werden automatisch erkannt.Jeder Monitor behält seine eigene Anmeldung.", + "dialogDescription": "Clusterknoten und konfigurierte Proxmox Backup Server werden automatisch erkannt. Jeder Monitor behält seine eigene Anmeldung.", "addTitle": "Einen Knoten hinzufügen", "name": "Name", "address": "Adresse überwachen", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 2b95595a..5b30c969 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -9,7 +9,7 @@ "notAvailable": "N/A", "serverOffline": "Serveur hors ligne", "supportProject": "Soutenir et contribuer au projet", - "descriptionPbs": "Tableau de bord du serveur de sauvegarde Proxmox" + "descriptionPbs": "Tableau de bord Proxmox Backup Server" }, "common": { "notAvailable": "N/A", @@ -7338,18 +7338,18 @@ "groups": { "cluster": "Cluster", "standalone": "nœuds autonomes", - "pbs": "Serveur de sauvegarde Proxmox" + "pbs": "Proxmox Backup Server" }, "thisNode": "Ce nœud", "checking": "Vérification des nœuds…", "loadFailed": "Impossible de charger la liste des nœuds.", - "empty": "aucun autre nœud pour l'instant.Ajoutez-en un pour ouvrir son moniteur à partir d'ici.", + "empty": "Aucun autre nœud pour l'instant. Ajoutez-en un pour ouvrir son Monitor depuis ici.", "noResponse": "Pas de réponse", "pbsInterface": "interface PBS", "openNewTab": "Ouvrir dans un nouvel onglet", "manage": "Ajouter ou gérer des nœuds…", "dialogTitle": "nœuds liés", - "dialogDescription": "Les nœuds de cluster et les serveurs de sauvegarde Proxmox configurés sont détectés automatiquement.Chaque moniteur conserve sa propre connexion.", + "dialogDescription": "Les nœuds du cluster et les Proxmox Backup Server configurés sont détectés automatiquement. Chaque Monitor conserve sa propre connexion.", "addTitle": "ajouter un nœud", "name": "Nom", "address": "Adresse du moniteur", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 3a09e45d..b595a4cc 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -9,7 +9,7 @@ "notAvailable": "N/A", "serverOffline": "Server offline", "supportProject": "Sostieni e contribuisci al progetto", - "descriptionPbs": "Dashboard del server di backup Proxmox" + "descriptionPbs": "Dashboard di Proxmox Backup Server" }, "common": { "notAvailable": "N/A", @@ -7338,18 +7338,18 @@ "groups": { "cluster": "cluster", "standalone": "nodi autonomi", - "pbs": "Server di backup Proxmox" + "pbs": "Proxmox Backup Server" }, "thisNode": "questo nodo", "checking": "Controllo dei nodi…", "loadFailed": "impossibile caricare l'elenco dei nodi.", - "empty": "ancora nessun altro nodo.Aggiungine uno per aprire il suo Monitor da qui.", + "empty": "Ancora nessun altro nodo. Aggiungine uno per aprire il suo Monitor da qui.", "noResponse": "Nessuna risposta", "pbsInterface": "interfaccia PBS", "openNewTab": "apri in una nuova scheda", "manage": "aggiungi o gestisci nodi...", "dialogTitle": "nodi collegati", - "dialogDescription": "i nodi del cluster e i server di backup Proxmox configurati vengono rilevati automaticamente.Ogni monitor mantiene il proprio accesso.", + "dialogDescription": "I nodi del cluster e i Proxmox Backup Server configurati vengono rilevati automaticamente. Ogni Monitor mantiene il proprio accesso.", "addTitle": "aggiungi un nodo", "name": "Nome", "address": "monitora l'indirizzo", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index 90a1f595..b64fa2d5 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -9,7 +9,7 @@ "notAvailable": "N/D", "serverOffline": "Servidor off-line", "supportProject": "Apoie e contribua para o projeto", - "descriptionPbs": "Painel do servidor de backup Proxmox" + "descriptionPbs": "Painel do Proxmox Backup Server" }, "common": { "notAvailable": "N/D", @@ -7338,18 +7338,18 @@ "groups": { "cluster": "Cluster", "standalone": "nós autônomos", - "pbs": "Servidor de backup Proxmox" + "pbs": "Proxmox Backup Server" }, "thisNode": "Este nó", "checking": "Verificando nós…", "loadFailed": "não foi possível carregar a lista de nós.", - "empty": "Nenhum outro nó ainda.Adicione um para abrir seu Monitor aqui.", + "empty": "Ainda não há outros nós. Adicione um para abrir o seu Monitor a partir daqui.", "noResponse": "Sem resposta", "pbsInterface": "interface PBS", "openNewTab": "abrir em uma nova guia", "manage": "Adicionar ou gerenciar nós…", "dialogTitle": "nós vinculados", - "dialogDescription": "os nós do cluster e os servidores de backup Proxmox configurados são detectados automaticamente.Cada monitor mantém seu próprio login.", + "dialogDescription": "Os nós do cluster e os Proxmox Backup Server configurados são detectados automaticamente. Cada Monitor mantém o seu próprio login.", "addTitle": "Adicionar um nó", "name": "Nome", "address": "endereço do monitor", diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 34a6107a..4b91a2cd 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -7337,18 +7337,18 @@ "groups": { "cluster": "Cluster", "standalone": "Samostatné uzly", - "pbs": "Záložný server Proxmox" + "pbs": "Proxmox Backup Server" }, "thisNode": "Tento uzol", "checking": "Kontrolujú sa uzly…", "loadFailed": "Nepodarilo sa načítať zoznam uzlov.", - "empty": "Zatiaľ žiadne ďalšie uzly.Pridajte jeden, aby ste odtiaľto otvorili jeho monitor.", + "empty": "Zatiaľ žiadne ďalšie uzly. Pridajte jeden, aby ste odtiaľto otvorili jeho Monitor.", "noResponse": "Žiadna odpoveď", "pbsInterface": "rozhranie PBS", "openNewTab": "Otvoriť na novej karte", "manage": "Pridať alebo spravovať uzly…", "dialogTitle": "Prepojené uzly", - "dialogDescription": "Uzly klastra a nakonfigurované záložné servery Proxmox sa detegujú automaticky.Každý monitor si uchováva svoje vlastné prihlásenie.", + "dialogDescription": "Uzly klastra a nakonfigurované Proxmox Backup Server sa zisťujú automaticky. Každý Monitor má vlastné prihlásenie.", "addTitle": "Pridať uzol", "name": "Meno", "address": "Adresa monitora", diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index 20df8611..7b8cab64 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -7343,13 +7343,13 @@ "thisNode": "Denna nod", "checking": "Kontrollerar noder...", "loadFailed": "Kunde inte ladda nodlistan.", - "empty": "Inga andra noder än.Lägg till en för att öppna monitorn härifrån.", + "empty": "Inga andra noder än. Lägg till en för att öppna dess Monitor härifrån.", "noResponse": "Inget svar", "pbsInterface": "PBS-gränssnitt", "openNewTab": "Öppna i en ny flik", "manage": "Lägg till eller hantera noder...", "dialogTitle": "Länkade noder", - "dialogDescription": "Klusternoder och konfigurerade Proxmox Backup-servrar upptäcks automatiskt.Varje bildskärm behåller sin egen inloggning.", + "dialogDescription": "Klusternoder och konfigurerade Proxmox Backup Server identifieras automatiskt. Varje Monitor behåller sin egen inloggning.", "addTitle": "Lägg till en nod", "name": "Namn", "address": "Övervaka adress", diff --git a/lang/de.json b/lang/de.json index 2a460ed1..1bc1956d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1690,7 +1690,7 @@ "Detected Coral hardware:": "Erkannte Coral-Hardware:", "Detected GPU(s):": "Erkannte GPU(s):", "Detected Intel CPU": "Intel-CPU erkannt", - "Detected Proxmox Backup Server": "Proxmox-Sicherungsserver erkannt", + "Detected Proxmox Backup Server": "Proxmox Backup Server erkannt", "Detected Proxmox VE": "Proxmox VE erkannt", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Proxmox VE 8.x erkannt – Ceph Squid für Debian installieren", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Proxmox VE 9.x erkannt – Ceph Squid für Debian Trixie installieren", @@ -4883,11 +4883,11 @@ "Proxmology logo applied": "Proxmology-Logo angewendet", "Proxmox 9 system update allready": "Proxmox 9 Systemupdate bereits", "Proxmox APT repositories configured": "Proxmox APT-Repositorys konfiguriert", - "Proxmox Backup Server": "Proxmox Sicherungsserver", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Proxmox Backup Server (PBS)-Ziele", "Proxmox Backup Server APT repositories configured": "Proxmox Backup Server APT-Repositorys konfiguriert", - "Proxmox Backup Server Update": "Proxmox Backup-Server-Update", - "Proxmox Backup Server update completed.": "Aktualisierung des Proxmox-Sicherungsservers abgeschlossen.", + "Proxmox Backup Server Update": "Aktualisierung von Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Aktualisierung von Proxmox Backup Server abgeschlossen.", "Proxmox CIFS Storage Status:": "Proxmox CIFS-Speicherstatus:", "Proxmox NFS Storage Status:": "Proxmox NFS-Speicherstatus:", "Proxmox System Update": "Proxmox-Systemupdate", @@ -6797,7 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Diese CIFS-Freigabe wird mit restriktiven Berechtigungen gemountet.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Diese GPU gilt als inkompatibel mit GPU-Passthrough zu einer VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Diese NFS-Freigabe ist vollständig eingeschränkt – selbst der Host-Root kann nicht darauf schreiben.", - "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Dieser Proxmox-Sicherungsserver hat kein aktives Abonnement. Wechseln Sie zum Repository ohne Abonnement? Die unzugängliche Enterprise PBS-Quelle wird deaktiviert.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Dieser Proxmox Backup Server hat kein aktives Abonnement. Zum Repository ohne Abonnement wechseln? Die nicht erreichbare Enterprise-PBS-Quelle wird deaktiviert.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Für diese VM sind zusätzliche Installationsschritte erforderlich. Weitere Informationen finden Sie im Installationshandbuch unter:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Dieses Konto ist für {registry}. Geben Sie ein Bild aus dieser Registrierung ein.", "This action will:": "Diese Aktion wird:", @@ -6881,7 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Dieses Skript kann nicht über das Proxmox-Webterminal ausgeführt werden.", "This script must be run as root.": "Dieses Skript muss als Root ausgeführt werden.", "This script must be run on a Proxmox host.": "Dieses Skript muss auf einem Proxmox-Host ausgeführt werden.", - "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox-Sicherungsserver an", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox Backup Server an", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox VE-Server an", "This script will update your Proxmox VE system with advanced options:": "Dieses Skript aktualisiert Ihr Proxmox VE-System mit erweiterten Optionen:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Diese Sitzung wird im Monitor-Terminal ausgeführt. Wenn Sie es von hier aus ausführen, wird die Verbindung während der Installation unterbrochen und der Switch bleibt in einem defekten Zustand.", diff --git a/lang/es.json b/lang/es.json index 92976bfb..0ddda5c8 100644 --- a/lang/es.json +++ b/lang/es.json @@ -4874,7 +4874,7 @@ "Proxmology logo applied": "Logotipo de Proxmology aplicado", "Proxmox 9 system update allready": "Actualización del sistema Proxmox 9 ya", "Proxmox APT repositories configured": "Repositorios Proxmox APT configurados", - "Proxmox Backup Server": "Servidor de respaldo Proxmox", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Destinos de Proxmox Backup Server (PBS)", "Proxmox Backup Server APT repositories configured": "Repositorios APT de Proxmox Backup Server configurados", "Proxmox Backup Server Update": "Actualización de Proxmox Backup Server", diff --git a/lang/fr.json b/lang/fr.json index 94ee253c..5fa19db0 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -1690,7 +1690,7 @@ "Detected Coral hardware:": "Matériel Coral détecté :", "Detected GPU(s):": "GPU(s) détecté(s) :", "Detected Intel CPU": "Processeur Intel détecté", - "Detected Proxmox Backup Server": "Serveur de sauvegarde Proxmox détecté", + "Detected Proxmox Backup Server": "Proxmox Backup Server détecté", "Detected Proxmox VE": "Proxmox VE détecté", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Proxmox VE 8.x détecté - Installation de Ceph Squid pour Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Proxmox VE 9.x détecté - Installation de Ceph Squid pour Debian Trixie", @@ -1851,7 +1851,7 @@ "Do you want to proceed with the system update?": "Voulez-vous procéder à la mise à jour du système ?", "Do you want to proceed?": "Voulez-vous continuer ?", "Do you want to reboot now?": "Voulez-vous redémarrer maintenant ?", - "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Voulez-vous supprimer la bannière d'abonnement Proxmox de l'interface Web du serveur de sauvegarde Proxmox ?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Voulez-vous supprimer la bannière d'abonnement Proxmox de l'interface web de Proxmox Backup Server ?", "Do you want to remove the stale entry from vfio.conf and continue?": "Voulez-vous supprimer l'entrée obsolète de vfio.conf et continuer ?", "Do you want to remove the stale entry from vfio.conf?": "Voulez-vous supprimer l'entrée obsolète de vfio.conf ?", "Do you want to restart the network service now to apply changes?": "Voulez-vous redémarrer le service réseau maintenant pour appliquer les modifications ?", @@ -2277,7 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Échec du clonage du référentiel log2ram. Vérifiez /tmp/log2ram_install.log", "Failed to configure EFI disk": "Échec de la configuration du disque EFI", "Failed to configure IOMMU automatically.": "Échec de la configuration automatique d'IOMMU.", - "Failed to configure Proxmox Backup Server repositories": "Échec de la configuration des référentiels du serveur de sauvegarde Proxmox", + "Failed to configure Proxmox Backup Server repositories": "Échec de la configuration des dépôts de Proxmox Backup Server", "Failed to configure Proxmox repositories": "Échec de la configuration des référentiels Proxmox", "Failed to configure TPM device in VM": "Échec de la configuration du périphérique TPM dans la VM", "Failed to configure repositories.": "Échec de la configuration des référentiels.", @@ -4883,11 +4883,11 @@ "Proxmology logo applied": "Logo Proxmologie appliqué", "Proxmox 9 system update allready": "La mise à jour du système Proxmox 9 est déjà terminée", "Proxmox APT repositories configured": "Dépôts Proxmox APT configurés", - "Proxmox Backup Server": "Serveur de sauvegarde Proxmox", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Destinations Proxmox Backup Server (PBS)", - "Proxmox Backup Server APT repositories configured": "Dépôts APT du serveur de sauvegarde Proxmox configurés", - "Proxmox Backup Server Update": "Mise à jour du serveur de sauvegarde Proxmox", - "Proxmox Backup Server update completed.": "Mise à jour du serveur de sauvegarde Proxmox terminée.", + "Proxmox Backup Server APT repositories configured": "Dépôts APT de Proxmox Backup Server configurés", + "Proxmox Backup Server Update": "Mise à jour de Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Mise à jour de Proxmox Backup Server terminée.", "Proxmox CIFS Storage Status:": "État de stockage Proxmox CIFS :", "Proxmox NFS Storage Status:": "Statut de stockage Proxmox NFS :", "Proxmox System Update": "Mise à jour du système Proxmox", @@ -6796,7 +6796,7 @@ "This CIFS share is mounted with restrictive permissions.": "Ce partage CIFS est monté avec des autorisations restrictives.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Ce GPU est considéré comme incompatible avec le relais GPU vers une VM dans ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Ce partage NFS est entièrement restreint : même la racine de l'hôte ne peut pas y écrire.", - "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Ce serveur de sauvegarde Proxmox n'a pas d'abonnement actif, passer au référentiel sans abonnement ?La source Enterprise PBS inaccessible sera désactivée.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Ce Proxmox Backup Server n'a pas d'abonnement actif. Passer au dépôt sans abonnement ? La source Enterprise PBS inaccessible sera désactivée.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Cette VM nécessite des étapes d'installation supplémentaires. Consultez le guide d'installation à l'adresse :\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Ce compte concerne {registry}. Entrez une image de ce registre.", "This action will:": "Cette action va :", @@ -6880,7 +6880,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Ce script ne peut pas être exécuté depuis le terminal web Proxmox.", "This script must be run as root.": "Ce script doit être exécuté en tant que root.", "This script must be run on a Proxmox host.": "Ce script doit être exécuté sur un hôte Proxmox.", - "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur de sauvegarde Proxmox", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Ce script appliquera les optimisations et réglages avancés suivants à votre Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Ce script mettra à jour votre système Proxmox VE avec des options avancées :", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Cette session est en cours d'exécution dans le terminal Monitor. L’exécuter à partir d’ici couperait la connexion en cours d’installation et laisserait le commutateur dans un état cassé.", diff --git a/lang/it.json b/lang/it.json index 17313126..db7b48a6 100644 --- a/lang/it.json +++ b/lang/it.json @@ -1690,7 +1690,7 @@ "Detected Coral hardware:": "Hardware Coral rilevato:", "Detected GPU(s):": "GPU rilevate:", "Detected Intel CPU": "Rilevata CPU Intel", - "Detected Proxmox Backup Server": "Messaggio tecnico per Proxmox e IT.Traduzione: rilevato server di backup Proxmox", + "Detected Proxmox Backup Server": "Proxmox Backup Server rilevato", "Detected Proxmox VE": "Rilevato Proxmox VE", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Rilevato Proxmox VE 8.x - Installazione di Ceph Squid per Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Rilevato Proxmox VE 9.x - Installazione di Ceph Squid per Debian Trixie", @@ -1851,7 +1851,7 @@ "Do you want to proceed with the system update?": "Vuoi procedere con l'aggiornamento del sistema?", "Do you want to proceed?": "Vuoi procedere?", "Do you want to reboot now?": "Vuoi riavviare ora?", - "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Vuoi rimuovere il banner di abbonamento Proxmox dall'interfaccia web del server di backup Proxmox?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Vuoi rimuovere il banner dell'abbonamento Proxmox dall'interfaccia web di Proxmox Backup Server?", "Do you want to remove the stale entry from vfio.conf and continue?": "Vuoi rimuovere la voce obsoleta da vfio.conf e continuare?", "Do you want to remove the stale entry from vfio.conf?": "Vuoi rimuovere la voce obsoleta da vfio.conf?", "Do you want to restart the network service now to apply changes?": "Riavviare ora il servizio di rete per applicare le modifiche?", @@ -2277,7 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Impossibile clonare il repository log2ram. Controlla /tmp/log2ram_install.log", "Failed to configure EFI disk": "Impossibile configurare il disco EFI", "Failed to configure IOMMU automatically.": "Impossibile configurare IOMMU automaticamente.", - "Failed to configure Proxmox Backup Server repositories": "Messaggio tecnico per Proxmox e IT.Traduzione: impossibile configurare i repository del server di backup Proxmox", + "Failed to configure Proxmox Backup Server repositories": "Impossibile configurare i repository di Proxmox Backup Server", "Failed to configure Proxmox repositories": "Impossibile configurare i repository Proxmox", "Failed to configure TPM device in VM": "Impossibile configurare il dispositivo TPM nella VM", "Failed to configure repositories.": "Impossibile configurare i repository.", @@ -4883,11 +4883,11 @@ "Proxmology logo applied": "Logo Proxmology applicato", "Proxmox 9 system update allready": "Il sistema è già aggiornato a Proxmox 9", "Proxmox APT repositories configured": "Repository APT Proxmox configurati", - "Proxmox Backup Server": "Server di backup Proxmox", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Destinazioni Proxmox Backup Server (PBS)", - "Proxmox Backup Server APT repositories configured": "Proxmox Repository APT del server di backup configurati", - "Proxmox Backup Server Update": "Aggiornamento del server di backup Proxmox", - "Proxmox Backup Server update completed.": "aggiornamento del server di backup Proxmox completato.", + "Proxmox Backup Server APT repositories configured": "Repository APT di Proxmox Backup Server configurati", + "Proxmox Backup Server Update": "Aggiornamento di Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Aggiornamento di Proxmox Backup Server completato.", "Proxmox CIFS Storage Status:": "Stato storage CIFS Proxmox:", "Proxmox NFS Storage Status:": "Stato storage NFS Proxmox:", "Proxmox System Update": "Aggiornamento sistema Proxmox", @@ -5247,7 +5247,7 @@ "Repository helper unavailable. NVIDIA installation stopped.": "Funzione di gestione dei repository non disponibile. L’installazione di NVIDIA è stata interrotta.", "Repository policy returned an unexpected result.": "La policy del repository ha restituito un risultato imprevisto.", "Repository ready.": "Repository pronto.", - "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Messaggio tecnico per Proxmox e IT.Traduzione: le origini del repository sono cambiate, ma l'aggiornamento dell'elenco dei pacchetti APT non è riuscito. Operazione interrotta;le fonti non sono state ripristinate. Ispezionare Amministrazione > Repository e riprovare apt-get update prima di continuare.", + "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Administration > Repositories and retry apt-get update before continuing.": "Le sorgenti dei repository sono cambiate, ma l'aggiornamento dell'elenco dei pacchetti APT non è riuscito. Operazione interrotta; le sorgenti non sono state ripristinate. Controlla Administration > Repositories e riesegui apt-get update prima di continuare.", "Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.": "Le origini del repository sono cambiate, ma l'aggiornamento dell'elenco dei pacchetti APT non è riuscito. Operazione interrotta; le origini non sono state ripristinate. Controlla Nodo > Aggiornamenti > Repository e riprova apt-get update prima di continuare.", "Repository switch declined; no APT source changed.": "Cambio di repository rifiutato; nessuna origine APT è stata modificata.", "Repository:": "Repository:", @@ -6797,7 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Questa condivisione CIFS è montata con autorizzazioni restrittive.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Questa GPU è considerata incompatibile con il passthrough GPU a una VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Questa condivisione NFS è completamente limitata: nemmeno l'utente root dell'host può scrivervi.", - "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "questo server di backup Proxmox non ha un abbonamento attivo, passare al repository senza abbonamento? L'origine Enterprise PBS inaccessibile verrà disabilitata.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Questo Proxmox Backup Server non ha un abbonamento attivo. Passare al repository senza abbonamento? La sorgente Enterprise PBS non accessibile verrà disattivata.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Questa VM richiede ulteriori passaggi di installazione; vedere la guida:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Questo account è per {registry}. Inserisci un'immagine da quel registro.", "This action will:": "Questa azione:", @@ -6881,7 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Questo script non può essere eseguito dal terminale web Proxmox.", "This script must be run as root.": "Questo script deve essere eseguito come root.", "This script must be run on a Proxmox host.": "Questo script deve essere eseguito su un host Proxmox.", - "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al server di backup Proxmox", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al tuo Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al server Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Questo script aggiornerà il sistema Proxmox VE con opzioni avanzate:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Questa sessione è in esecuzione nel terminale Monitor. Eseguirlo da qui interromperebbe la connessione a metà installazione e lascerebbe il cambio di canale incompleto.", diff --git a/lang/pt.json b/lang/pt.json index 469dca2d..b7d5011d 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -1690,7 +1690,7 @@ "Detected Coral hardware:": "Hardware Coral detectado:", "Detected GPU(s):": "GPU(s) detectada(s):", "Detected Intel CPU": "CPU Intel detectada", - "Detected Proxmox Backup Server": "Servidor de backup Proxmox detectado", + "Detected Proxmox Backup Server": "Proxmox Backup Server detectado", "Detected Proxmox VE": "Proxmox VE detectado", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Detectado Proxmox VE 8.x - Instalando Ceph Squid para Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Detectado Proxmox VE 9.x - Instalando Ceph Squid para Debian Trixie", @@ -1851,7 +1851,7 @@ "Do you want to proceed with the system update?": "Deseja prosseguir com a atualização do sistema?", "Do you want to proceed?": "Você quer prosseguir?", "Do you want to reboot now?": "Você quer reiniciar agora?", - "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Você deseja remover o banner de assinatura Proxmox da interface da web do Servidor de Backup Proxmox?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Deseja remover o banner de assinatura do Proxmox da interface web do Proxmox Backup Server?", "Do you want to remove the stale entry from vfio.conf and continue?": "Deseja remover a entrada obsoleta do vfio.conf e continuar?", "Do you want to remove the stale entry from vfio.conf?": "Você deseja remover a entrada obsoleta do vfio.conf?", "Do you want to restart the network service now to apply changes?": "Deseja reiniciar o serviço de rede agora para aplicar as alterações?", @@ -2277,7 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Falha ao clonar o repositório log2ram. Verifique /tmp/log2ram_install.log", "Failed to configure EFI disk": "Falha ao configurar o disco EFI", "Failed to configure IOMMU automatically.": "Falha ao configurar o IOMMU automaticamente.", - "Failed to configure Proxmox Backup Server repositories": "Falha ao configurar repositórios do servidor de backup Proxmox", + "Failed to configure Proxmox Backup Server repositories": "Falha ao configurar os repositórios do Proxmox Backup Server", "Failed to configure Proxmox repositories": "Falha ao configurar repositórios Proxmox", "Failed to configure TPM device in VM": "Falha ao configurar o dispositivo TPM na VM", "Failed to configure repositories.": "Falha ao configurar repositórios.", @@ -4883,11 +4883,11 @@ "Proxmology logo applied": "Logotipo da Proxmologia aplicado", "Proxmox 9 system update allready": "Atualização do sistema Proxmox 9 já", "Proxmox APT repositories configured": "repositórios Proxmox APT configurados", - "Proxmox Backup Server": "Servidor de backup Proxmox", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Destinos Proxmox Backup Server (PBS)", - "Proxmox Backup Server APT repositories configured": "repositórios APT do servidor de backup Proxmox configurados", - "Proxmox Backup Server Update": "Atualização do servidor de backup Proxmox", - "Proxmox Backup Server update completed.": "atualização do servidor de backup Proxmox concluída.", + "Proxmox Backup Server APT repositories configured": "Repositórios APT do Proxmox Backup Server configurados", + "Proxmox Backup Server Update": "Atualização do Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Atualização do Proxmox Backup Server concluída.", "Proxmox CIFS Storage Status:": "Status de armazenamento CIFS do Proxmox:", "Proxmox NFS Storage Status:": "Status de armazenamento NFS do Proxmox:", "Proxmox System Update": "Atualização do sistema Proxmox", @@ -6797,7 +6797,7 @@ "This CIFS share is mounted with restrictive permissions.": "Este compartilhamento CIFS é montado com permissões restritivas.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU é considerada incompatível com a passagem de GPU para uma VM no ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este compartilhamento NFS é totalmente restrito — até mesmo o host root não pode gravar nele.", - "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Este servidor de backup Proxmox não tem assinatura ativa, mudar para o repositório sem assinatura? A fonte inacessível do Enterprise PBS será desativada.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Este Proxmox Backup Server não tem assinatura ativa. Mudar para o repositório sem assinatura? A fonte Enterprise do PBS, inacessível, será desativada.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Esta VM requer etapas extras de instalação. Consulte o guia de instalação em:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Esta conta é para {registry}. Insira uma imagem desse registro.", "This action will:": "Esta ação irá:", @@ -6881,7 +6881,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Este script não pode ser executado no terminal web Proxmox.", "This script must be run as root.": "Este script deve ser executado como root.", "This script must be run on a Proxmox host.": "Este script deve ser executado em um host Proxmox.", - "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor de backup Proxmox", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Este script atualizará seu sistema Proxmox VE com opções avançadas:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Esta sessão está sendo executada no terminal Monitor. Executá-lo a partir daqui cortaria a conexão no meio da instalação e deixaria o switch quebrado.", diff --git a/lang/sk.json b/lang/sk.json index a6e9042a..ccb23c92 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -1690,7 +1690,7 @@ "Detected Coral hardware:": "Nájdený Coral hardvér:", "Detected GPU(s):": "Nájdené GPU:", "Detected Intel CPU": "Zistený Intel procesor", - "Detected Proxmox Backup Server": "Zistený záložný server Proxmox", + "Detected Proxmox Backup Server": "Zistený Proxmox Backup Server", "Detected Proxmox VE": "Zistený Proxmox VE", "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian": "Zistený Proxmox VE 8.x - inštalujem Ceph Squid pre Debian", "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie": "Zistený Proxmox VE 9.x - inštalujem Ceph Squid pre Debian Trixie", @@ -1851,7 +1851,7 @@ "Do you want to proceed with the system update?": "Chcete pokračovať v aktualizácii systému?", "Do you want to proceed?": "Chcete pokračovať?", "Do you want to reboot now?": "Chcete reštartovať teraz?", - "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Chcete odstrániť banner predplatného Proxmox z webového rozhrania záložného servera Proxmox?", + "Do you want to remove the Proxmox subscription banner from the Proxmox Backup Server web interface?": "Chcete odstrániť banner predplatného Proxmox z webového rozhrania Proxmox Backup Server?", "Do you want to remove the stale entry from vfio.conf and continue?": "Chcete odstrániť zastaraný záznam z vfio.conf a pokračovať?", "Do you want to remove the stale entry from vfio.conf?": "Chcete odstrániť zastaraný záznam z vfio.conf?", "Do you want to restart the network service now to apply changes?": "Chcete teraz reštartovať sieťovú službu, aby sa zmeny prejavili?", @@ -2277,7 +2277,7 @@ "Failed to clone log2ram repository. Check /tmp/log2ram_install.log": "Repozitár log2ram sa nepodarilo stiahnuť. Skontrolujte /tmp/log2ram_install.log", "Failed to configure EFI disk": "EFI disk sa nepodarilo nastaviť", "Failed to configure IOMMU automatically.": "IOMMU sa nepodarilo nastaviť automaticky.", - "Failed to configure Proxmox Backup Server repositories": "Nepodarilo sa nakonfigurovať archívy záložného servera Proxmox", + "Failed to configure Proxmox Backup Server repositories": "Repozitáre Proxmox Backup Server sa nepodarilo nastaviť", "Failed to configure Proxmox repositories": "Repozitáre Proxmoxu sa nepodarilo nastaviť", "Failed to configure TPM device in VM": "TPM zariadenie sa vo VM nepodarilo nastaviť", "Failed to configure repositories.": "Repozitáre sa nepodarilo nastaviť.", @@ -4885,11 +4885,11 @@ "Proxmology logo applied": "Logo Proxmology bolo použité", "Proxmox 9 system update allready": "Systém Proxmox 9 je už aktualizovaný", "Proxmox APT repositories configured": "APT repozitáre Proxmoxu sú nastavené", - "Proxmox Backup Server": "Záložný server Proxmox", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Ciele Proxmox Backup Servera (PBS)", - "Proxmox Backup Server APT repositories configured": "Záložné úložiská APT servera Proxmox sú nakonfigurované", - "Proxmox Backup Server Update": "Aktualizácia záložného servera Proxmox", - "Proxmox Backup Server update completed.": "Aktualizácia záložného servera Proxmox bola dokončená.", + "Proxmox Backup Server APT repositories configured": "APT repozitáre Proxmox Backup Server sú nastavené", + "Proxmox Backup Server Update": "Aktualizácia Proxmox Backup Server", + "Proxmox Backup Server update completed.": "Aktualizácia Proxmox Backup Server bola dokončená.", "Proxmox CIFS Storage Status:": "Stav CIFS úložiska v Proxmoxe:", "Proxmox NFS Storage Status:": "Stav NFS úložiska v Proxmoxe:", "Proxmox System Update": "Aktualizácia systému Proxmox", @@ -6798,7 +6798,7 @@ "This CIFS share is mounted with restrictive permissions.": "Toto CIFS zdieľanie je pripojené s obmedzujúcimi oprávneniami.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Táto GPU sa v ProxMenux považuje za nekompatibilnú s GPU passthrough do VM.", "This NFS share is fully restricted — even the host root cannot write to it.": "Toto NFS zdieľanie je úplne obmedzené - ani root na hostovi doň nevie zapisovať.", - "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Tento záložný server Proxmox nemá žiadne aktívne predplatné, prepnúť na úložisko bez predplatného? Neprístupný zdroj Enterprise PBS bude zakázaný.", + "This Proxmox Backup Server has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PBS source will be disabled.": "Tento Proxmox Backup Server nemá aktívne predplatné. Prepnúť na repozitár bez predplatného? Nedostupný zdroj Enterprise PBS bude vypnutý.", "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144": "Táto VM vyžaduje ďalšie kroky inštalácie. Návod nájdete tu:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144", "This account is for {registry}. Enter an image from that registry.": "Tento účet je pre {registry}. Zadajte obrázok z tohto registra.", "This action will:": "Táto akcia vykoná:", @@ -6882,7 +6882,7 @@ "This script cannot be executed from the Proxmox web terminal.": "Tento skript sa nedá spustiť z webového terminálu Proxmoxu.", "This script must be run as root.": "Tento skript musí byť spustený ako root.", "This script must be run on a Proxmox host.": "Tento skript musí byť spustený na Proxmox serveri.", - "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Tento skript použije nasledujúce optimalizácie a pokročilé úpravy na váš záložný server Proxmox", + "This script will apply the following optimizations and advanced adjustments to your Proxmox Backup Server": "Tento skript použije nasledujúce optimalizácie a pokročilé úpravy na váš Proxmox Backup Server", "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": "Tento skript použije tieto optimalizácie a pokročilé úpravy na vašom serveri Proxmox VE", "This script will update your Proxmox VE system with advanced options:": "Tento skript aktualizuje váš Proxmox VE systém s pokročilými možnosťami:", "This session is running in the Monitor terminal. Running it from here would cut the connection mid-install and leave the switch in a broken state.": "Táto relácia beží v termináli Monitora. Ak by sa to spustilo odtiaľto, pripojenie sa počas inštalácie preruší a prepnutie môže zostať v zlom stave.", From 1e97f732acc807caaa372c785c3d8814babbbd3a Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:40:19 +0200 Subject: [PATCH 29/53] Avoid unnecessary probes while opening OCI menus --- .../test_oci_removal_firewall_wording.py | 6 +- .github/workflows/test-oci-registries.yml | 6 +- oci/remote/oci_work_backup.py | 16 ++- oci/src/proxmenux_oci/management.py | 17 +-- oci/tests/test_menu_performance.py | 109 ++++++++++++++++++ 5 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 oci/tests/test_menu_performance.py diff --git a/.github/scripts/tests/test_oci_removal_firewall_wording.py b/.github/scripts/tests/test_oci_removal_firewall_wording.py index bde90937..8041f923 100644 --- a/.github/scripts/tests/test_oci_removal_firewall_wording.py +++ b/.github/scripts/tests/test_oci_removal_firewall_wording.py @@ -56,7 +56,8 @@ class RemovalWordings(unittest.TestCase): state = ModuleType('oci_installation_state') state.parse_config = lambda raw: {} scope = {'sys': SimpleNamespace(path=[]), 'source_text': lambda value: value or '', - 'translate': translated or (lambda value: value), 're': re} + 'translate': translated or (lambda value: value), 're': re, + '_selected_configuration': remover.guest_config, 'subprocess': subprocess} with patch.dict(sys.modules, {'oci_instances': instances, 'oci_remove': remover, 'oci_installation_state': state}): return extract(MENU, '_removal_summary', scope)(Path('/inert'), 101) @@ -87,7 +88,8 @@ class RemovalWordings(unittest.TestCase): with patch.dict(sys.modules, {'oci_instances': instances, 'oci_remove': remover, 'oci_installation_state': state}): scope = {'sys': SimpleNamespace(path=[]), 'source_text': lambda value: value or '', - 'translate': lambda value: value, 're': re} + 'translate': lambda value: value, 're': re, + '_selected_configuration': remover.guest_config, 'subprocess': subprocess} preview = extract(MENU, '_removal_summary', scope)(Path('/inert'), 102) self.assertIn(WHOLE, preview) diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 7625fe1c..cab053e6 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -35,4 +35,8 @@ jobs: - name: Run OCI Modify latency regressions env: PYTHONDONTWRITEBYTECODE: '1' - run: python3 -m unittest discover -s oci/tests -p test_modify_latency.py -v + run: | + python3 -m unittest discover -s oci/tests -p test_modify_latency.py -v + python3 -m unittest discover -s oci/tests -p test_menu_performance.py -v + python3 -m unittest discover -s oci/tests -p test_work_backup.py -v + python3 -m unittest discover -s oci/tests -p test_recreation.py -v diff --git a/oci/remote/oci_work_backup.py b/oci/remote/oci_work_backup.py index b37a2c26..251ffe71 100644 --- a/oci/remote/oci_work_backup.py +++ b/oci/remote/oci_work_backup.py @@ -192,21 +192,25 @@ def application(root, vmid): return primary_id, members or [vmid] -def status(root, vmid): +def status(root, vmid, only_needed=False): """What the menu needs to know before an operation: whether the backup fits on the system disk, the storage chosen earlier and the ones that - have room.""" + have room. The menu can skip unused alternatives once a destination fits; + the default retains the complete status used by the maintenance CLI.""" primary_id, vmids = application(root, vmid) record = instances.read(root, primary_id) default = instances.location(root, primary_id).parent needed, free = required(vmids), available(default) saved = (record.get('deployment') or {}).get(KEY) - path = storage_path(saved) if saved else None + fits = free >= needed + path = storage_path(saved) if saved and not (only_needed and fits) else None saved_free = available(path) if path is not None and path.is_dir() else None - return {'needed': needed, 'free': free, 'fits': free >= needed, 'saved': saved, - 'saved_fits': saved_free is not None and saved_free >= needed, + saved_fits = saved_free is not None and saved_free >= needed + candidates = [] if only_needed and (fits or saved_fits) else file_storages() + return {'needed': needed, 'free': free, 'fits': fits, 'saved': saved, + 'saved_fits': saved_fits, 'candidates': [{'storage': item['storage'], 'free': item['free']} - for item in file_storages() if item['free'] >= needed]} + for item in candidates if item['free'] >= needed]} def choose(root, vmid, storage): diff --git a/oci/src/proxmenux_oci/management.py b/oci/src/proxmenux_oci/management.py index 9b71d1b1..db75d597 100644 --- a/oci/src/proxmenux_oci/management.py +++ b/oci/src/proxmenux_oci/management.py @@ -332,7 +332,7 @@ def _work_backup_ready(project, ui, vmid): import oci_instances as instances import oci_work_backup try: - state = oci_work_backup.status(instances.ROOT, vmid) + state = oci_work_backup.status(instances.ROOT, vmid, only_needed=True) except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError): # What cannot be measured here is told by the engine when it stops. return True @@ -416,8 +416,6 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): return _toggle_watchdog(project, ui, row['vmid'], watched) if action == 'remove': return _remove(project, ui, row['vmid']) - if action != 'modify' and not _work_backup_ready(project, ui, row['vmid']): - return False record = instances.read(instances.ROOT, row['vmid']) import oci_instance_reconcile as reconcile try: @@ -462,13 +460,13 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): wizard.close() if not approved: return False - # Opening or cancelling the editor needs no disk/storage probes. - # Keep the existing backup checks after the user confirms edits. - if not _work_backup_ready(project, ui, row['vmid']): - return False elif not ui.review(translate('If the image channel has a new version, the CT is stopped, backed up and verified, and replaced with the new image to update the container. If the update fails after the container was changed, the backup is restored. An update that stops halfway is recovered from the OCI management menu.'), translate('Update OCI'), question=translate('Update now?'), default=True): return False + # Opening/cancelling a confirmation needs no disk or storage probes. + # Both accepted operations retain this check before the backend starts. + if not _work_backup_ready(project, ui, row['vmid']): + return False command = [sys.executable, str(project / 'remote/oci_update_current.py'), str(row['vmid']), *lifecycle_args] # Host directories are configured by the user, who knows they are @@ -518,7 +516,10 @@ def _removal_summary(project, vmid): record = instances.read(instances.ROOT, member) except (OSError, ValueError, KeyError): record = {} - config = oci_remove.guest_config(member) + try: + config = _selected_configuration(member) + except (OSError, RuntimeError, subprocess.SubprocessError): + config = None cfg = parse_config(config) if config else {} name = (cfg.get('hostname') or (record.get('stack_member') or {}).get('name') or record.get('deployment', {}).get('hostname') or '') diff --git a/oci/tests/test_menu_performance.py b/oci/tests/test_menu_performance.py new file mode 100644 index 00000000..94981ceb --- /dev/null +++ b/oci/tests/test_menu_performance.py @@ -0,0 +1,109 @@ +"""Menu speedups avoid unused probes, retaining confirmation and capacity checks.""" +from pathlib import Path +import sys +import tempfile +import unittest +from unittest.mock import Mock,patch +ROOT=Path(__file__).resolve().parents[1] +sys.path[:0]=[str(ROOT/'src'),str(ROOT/'remote')] +from proxmenux_oci import management as menu +import oci_instances as instances +import oci_instance_reconcile as reconcile +import oci_remove as remover +import oci_work_backup as backup + + +class UpdateEntryTests(unittest.TestCase): + def run_update(self,approved,ready): + events=[] + record={'status':'installed','vmid':110,'deployment':{},'template':{}} + row={'vmid':110,'reason':'matched','stack':False,'pending':False,'status':'installed'} + ui=Mock();ui.unattended=True + ui.review.side_effect=lambda *a,**kw:events.append('review') or approved + with patch.object(menu,'carry_records',return_value={}) as carry, \ + patch.object(menu,'check_selected',return_value=row), \ + patch.object(menu,'_selected_configuration',return_value=b'current config'), \ + patch.object(instances,'read',return_value=record),patch.object(reconcile,'propose',return_value=None), \ + patch.object(menu,'_work_backup_ready',side_effect=lambda *a:events.append('backup') or ready), \ + patch.object(menu,'_run_lifecycle',side_effect=lambda *a:events.append('lifecycle') or True): + result=menu.manage_instance(ROOT,ui,row,action='update') + carry.assert_called_once_with(ROOT,vmids=[110],verify=True) + return result,events + + def test_declining_update_does_not_probe_disks_or_storages(self): + result,events=self.run_update(False,False) + self.assertFalse(result);self.assertEqual(events,['review']) + + def test_accepted_update_checks_backup_space_before_starting(self): + result,events=self.run_update(True,True) + self.assertTrue(result);self.assertEqual(events,['review','backup','lifecycle']) + + def test_insufficient_space_still_prevents_the_update(self): + result,events=self.run_update(True,False) + self.assertFalse(result);self.assertEqual(events,['review','backup']) + + +class BackupDestinationTests(unittest.TestCase): + def status(self,local_free,saved_free=None,only_needed=True): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory);saved= root/'nas';saved.mkdir() + record={'deployment':{backup.KEY:'nas'}} + with patch.object(backup,'application',return_value=(110,[110,111])), \ + patch.object(instances,'read',return_value=record), \ + patch.object(backup,'required',return_value=100) as required, \ + patch.object(backup,'available',side_effect=lambda p:saved_free if Path(p)==saved else local_free), \ + patch.object(backup,'storage_path',return_value=saved if saved_free is not None else None) as path, \ + patch.object(backup,'file_storages',return_value=[{'storage':'enough','free':200},{'storage':'small','free':50}]) as alternatives: + result=backup.status(root,110,only_needed=only_needed) + required.assert_called_once_with([110,111]) + return result,path.call_count,alternatives.call_count + + def test_local_room_avoids_saved_storage_and_alternative_queries(self): + result,saved,alternatives=self.status(200,saved_free=1000) + self.assertTrue(result['fits']);self.assertEqual((saved,alternatives),(0,0)) + + def test_saved_destination_that_fits_avoids_alternative_queries(self): + result,saved,alternatives=self.status(50,saved_free=200) + self.assertTrue(result['saved_fits']);self.assertEqual((saved,alternatives),(1,0)) + + def test_when_neither_fits_all_available_destinations_are_checked(self): + result,saved,alternatives=self.status(50,saved_free=40) + self.assertFalse(result['fits']);self.assertFalse(result['saved_fits']) + self.assertEqual(result['candidates'],[{'storage':'enough','free':200}]) + self.assertEqual(alternatives,1) + + def test_missing_saved_storage_still_offers_alternatives(self): + result,saved,alternatives=self.status(50) + self.assertEqual(result['candidates'],[{'storage':'enough','free':200}]) + self.assertEqual(alternatives,1) + + def test_complete_status_keeps_legacy_fields_and_all_candidates(self): + result,saved,alternatives=self.status(200,saved_free=300,only_needed=False) + self.assertTrue(result['fits']);self.assertTrue(result['saved_fits']) + self.assertEqual((saved,alternatives),(1,1)) + self.assertEqual(result['candidates'],[{'storage':'enough','free':200}]) + + +class RemovalPreviewTests(unittest.TestCase): + def preview(self,reader): + record={'vmid':110,'deployment':{},'template':{'catalog_ui':{'title':'Example'}}} + with patch.object(remover,'members_of',return_value=(110,record,[110])), \ + patch.object(instances,'read',return_value=record), \ + patch.object(menu,'_selected_configuration',side_effect=reader), \ + patch.object(remover,'guest_config',side_effect=AssertionError('Duplicate pct process')), \ + patch.object(remover,'host_directories',return_value=[]), \ + patch.object(remover,'private_bridge',return_value=None): + return menu._removal_summary(ROOT,110) + + def test_preview_reuses_verified_config_and_keeps_volume_and_host_path_details(self): + config=b'hostname: example\nrootfs: local:subvol-110-disk-0,size=8G\nmp0: /mnt/media,mp=/media,backup=0\n' + text=self.preview(lambda v:config) + self.assertIn('8G',text);self.assertIn('/mnt/media',text) + self.assertIn('not targeted for removal',text) + + def test_missing_guest_can_still_be_reviewed_without_a_second_probe(self): + text=self.preview(lambda v:(_ for _ in ()).throw(RuntimeError('guest missing'))) + self.assertIn('CT 110',text);self.assertIn('rootfs -',text) + + +if __name__=='__main__':unittest.main() From 6496318452ec950b4eaff377bef7a79b957e004d Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:47:32 +0200 Subject: [PATCH 30/53] Shorten Escape key delay in OCI dialog menus --- .github/workflows/test-oci-registries.yml | 1 + oci/src/proxmenux_oci/ui.py | 4 +++ oci/tests/test_dialog_escape.py | 34 +++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 oci/tests/test_dialog_escape.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index cab053e6..2d2bf69e 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -38,5 +38,6 @@ jobs: run: | python3 -m unittest discover -s oci/tests -p test_modify_latency.py -v python3 -m unittest discover -s oci/tests -p test_menu_performance.py -v + python3 -m unittest discover -s oci/tests -p test_dialog_escape.py -v python3 -m unittest discover -s oci/tests -p test_work_backup.py -v python3 -m unittest discover -s oci/tests -p test_recreation.py -v diff --git a/oci/src/proxmenux_oci/ui.py b/oci/src/proxmenux_oci/ui.py index 8238d3c2..6ff5955f 100644 --- a/oci/src/proxmenux_oci/ui.py +++ b/oci/src/proxmenux_oci/ui.py @@ -221,6 +221,10 @@ class DialogUI: def _run(self, widget: list[str], title: str | None = None) -> subprocess.CompletedProcess[str]: environment = os.environ.copy() + # ncurses otherwise waits a full second to distinguish Escape from + # the prefix of a cursor/function key. Keep a short sequence window, + # while respecting an explicit terminal-specific setting. + environment.setdefault("ESCDELAY", "150") if environment.get("TERM", "").casefold() in {"", "dumb", "unknown"}: environment["TERM"] = "xterm-256color" # dialog draws on the terminal and writes the selection to stderr. diff --git a/oci/tests/test_dialog_escape.py b/oci/tests/test_dialog_escape.py new file mode 100644 index 00000000..f5063878 --- /dev/null +++ b/oci/tests/test_dialog_escape.py @@ -0,0 +1,34 @@ +"""Escape delay is scoped to dialog and preserves terminal-specific overrides.""" +import os +from pathlib import Path +import subprocess +import sys +import unittest +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src')) +from proxmenux_oci.ui import DialogUI, UserCancelled + + +class DialogEscapeTests(unittest.TestCase): + def test_escape_uses_short_delay_without_changing_parent_environment(self): + with patch.dict(os.environ, {'TERM': 'xterm'}, clear=True), \ + patch('proxmenux_oci.ui.subprocess.run', return_value=subprocess.CompletedProcess([], 255, stderr='')) as run: + self.assertIsNone(DialogUI().choose('Menu', [('1', 'First')])) + self.assertEqual(run.call_args.kwargs['env']['ESCDELAY'], '150') + self.assertNotIn('ESCDELAY', os.environ) + + def test_explicit_delay_is_preserved(self): + with patch.dict(os.environ, {'ESCDELAY': '900'}), \ + patch('proxmenux_oci.ui.subprocess.run', return_value=subprocess.CompletedProcess([], 0, stderr='1')) as run: + self.assertEqual(DialogUI().choose('Menu', [('1', 'First')]), '1') + self.assertEqual(run.call_args.kwargs['env']['ESCDELAY'], '900') + + def test_escape_in_search_input_keeps_cancellation_behavior(self): + with patch('proxmenux_oci.ui.subprocess.run', return_value=subprocess.CompletedProcess([], 255, stderr='')): + with self.assertRaises(UserCancelled): + DialogUI().ask('Search') + + +if __name__ == '__main__': + unittest.main() From 99adc343e8805fa2d64566808a81559988cc3f9d Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:50:42 +0200 Subject: [PATCH 31/53] Adapt isolated removal preview tests to verified configuration reader --- .github/scripts/tests/test_oci_recovery_inventory_wording.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/tests/test_oci_recovery_inventory_wording.py b/.github/scripts/tests/test_oci_recovery_inventory_wording.py index 77e7afaa..338698ae 100644 --- a/.github/scripts/tests/test_oci_recovery_inventory_wording.py +++ b/.github/scripts/tests/test_oci_recovery_inventory_wording.py @@ -5,6 +5,7 @@ import json from pathlib import Path import re import runpy +import subprocess import sys import tempfile from types import ModuleType, SimpleNamespace @@ -52,7 +53,8 @@ class InventoryMessages(unittest.TestCase): state = ModuleType('oci_installation_state') state.parse_config = lambda raw: dict(line.split(': ', 1) for line in raw.decode().splitlines()) scope = {'sys': SimpleNamespace(path=[]), 'source_text': lambda text: text or '', - 'translate': translate, 're': re} + 'translate': translate, 're': re, 'subprocess': subprocess, + '_selected_configuration': remover.guest_config} summary = extracted(MENU, '_removal_summary', scope) with patch.dict(sys.modules, {'oci_instances': instances, 'oci_remove': remover, 'oci_installation_state': state}): From ac42bea70fb06f975098235b8321092c141238c3 Mon Sep 17 00:00:00 2001 From: Jonatan <190036+jcastro@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:57:19 +0200 Subject: [PATCH 32/53] Preserve Modify fallback and detached disks after native edits --- .github/workflows/test-oci-registries.yml | 3 + oci/README.md | 17 +- oci/remote/oci_instance_transaction.py | 56 ++++- oci/remote/oci_native_modify.py | 34 ++- oci/remote/oci_update_current.py | 7 +- oci/tests/test_modify_review.py | 219 ++++++++++++++++++++ oci/tests/test_modify_review_transaction.py | 148 +++++++++++++ oci/tests/test_native_modify.py | 14 +- 8 files changed, 468 insertions(+), 30 deletions(-) create mode 100644 oci/tests/test_modify_review.py create mode 100644 oci/tests/test_modify_review_transaction.py diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index ca96aed2..fbff4ff7 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -34,6 +34,9 @@ jobs: run: | python3 -m unittest discover -s oci/tests -p test_modify_image_reuse.py -v python3 -m unittest discover -s oci/tests -p test_native_modify.py -v + python3 -m unittest discover -s oci/tests -p test_modify_review.py -v + python3 -m unittest discover -s oci/tests -p test_modify_review_transaction.py -v + python3 -m unittest discover -s oci/tests -p test_stage_release.py -v - name: Run offline registry account tests env: PYTHONDONTWRITEBYTECODE: '1' diff --git a/oci/README.md b/oci/README.md index 27ea49b3..a18414a4 100644 --- a/oci/README.md +++ b/oci/README.md @@ -142,7 +142,7 @@ An application made of several images is one entry of the catalog and one instal | Operation | What changes | What is kept | |---|---|---| | **Update** | The rootfs is replaced by the image the saved tag publishes today | Record, container disks, host directories, network and devices | -| **Modify** | Choose a setting, review it, then apply native LXC changes to the existing container; runtime settings may require a restart | Installed image, rootfs, persistent data and unrelated Proxmox settings | +| **Modify** | Edit and confirm the configuration; supported native settings apply directly, while other changes use the existing recreation flow | Native edits keep the installed image and rootfs; both paths preserve persistent data | | **Recreate** (multi-container) | Every container is rebuilt from the image it was installed with | Data, network and devices | | **Watchdog** | Whether the application is started again when it stops on its own | Everything; only the choice is saved | | **Remove** | The LXC, or every container of the application, its disks and its record | Host directories, with their content | @@ -159,20 +159,9 @@ Settings changed in Proxmox VE after the installation, such as memory, cores or A rolling tag is not an unattended update: the digest changes only through an explicit update. -For standalone applications, CPU and RAM changes apply live. Network settings, -environment overrides, directory mounts, additional managed volumes and supported -native devices use a controlled stop/start when needed. Modify does not query the -registry or download an image for these edits, even if the cached archive has -already been cleaned. Removing a managed mount detaches its disk without deleting -its data. An optional retained backup does not force recreation. +For standalone applications, CPU and RAM changes apply live. Supported network settings, environment overrides, directory mounts, additional managed volumes and native devices use a controlled stop/start when needed. Modify does not query the registry or download an image for these native edits, even if the cached archive has already been cleaned. Removing a managed mount detaches its disk without deleting its data; subsequent image updates park that unused disk and return it to the container. An optional retained backup does not force recreation. -The native path checks the saved configuration and installation identity, keeps a -private before/after journal, and restores the original settings if applying them -or checking the restarted service fails. It refuses to overwrite concurrent edits. -Application-specific installer adaptations (such as changing an NVIDIA runtime or -post-start configuration) are not silently converted into a full recreation: -unsupported same-image changes stop with an explanation before modifying the CT. -Image changes and coordinated stack operations retain their separate update flow. +The native path checks the saved configuration and installation identity, keeps a private before/after journal, and restores the original settings if applying them or checking the restarted service fails. It refuses to overwrite concurrent edits. Changes that require installer adaptations, such as NVIDIA runtime changes, GPU permission/environment setup, file mounts, completion notes or a refreshed catalog template, use the existing validated recreation flow. The route is chosen before applying any settings, so one proposal is not partially applied by both paths. The recreation flow reuses a verified cached archive when it matches the resolved image; it downloads an image when a suitable archive is unavailable. Image changes and coordinated stack operations retain their separate update flow. ## From a Compose file or a docker run command to an oci-compose diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index 25f9bf6c..bd8f09d6 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -320,6 +320,36 @@ def mounts(config): return result +def unused_volumes(config): + return {key: value for key, value in parse_config(config).items() + if re.fullmatch(r'unused[0-9]+', key)} + + +def move_retained_unused(state, to_stage): + """Unused disks are not in vzdump. Park and return them separately, using + PVE's volume reassignment API, never --delete (which frees an unused disk). + The journal's keys and both ownership markers also make retries safe.""" + keys = set(state.get('retained_unused', {})) + if not keys: + return + vmid, stage = state['vmid'], state['stage'] + guest_marker = state['record']['installation_id'] + holder_marker = 'proxmenux-transaction=' + state['id'] + source, target = (vmid, stage) if to_stage else (stage, vmid) + source_marker, target_marker = ((guest_marker, holder_marker) if to_stage + else (holder_marker, guest_marker)) + for key in sorted(keys): + present = unused_volumes(owned(source, source_marker)) + destination = unused_volumes(owned(target, target_marker)) + if key not in present: + if key in destination: + continue # Already reassigned before an interruption. + raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced')) + if key in destination or (to_stage and present.keys() - keys): + raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced')) + run('pct', 'move-volume', str(source), key, '--target-vmid', str(target), '--target-volume', key) + + def is_file_bind(mount, files=()): return mount['type'] == 'host-bind' and (mount['source'] in files or Path(mount['source']).is_file()) @@ -492,7 +522,16 @@ def preflight(record, candidate, config, coordinated=None, in_place=False, basel runtime_settings.check(config, deployment, record['vmid']) coordinated_keys = {'hookscript', *KEPT_AS_IS} if coordinated else set(KEPT_AS_IS) preserved_keys = {'nameserver', 'searchdomain', 'timezone', 'protection'} if in_place else set() - if '[' in config.decode() or keys - BASIC - runtime_keys - coordinated_keys - preserved_keys - {k for k in keys if re.fullmatch(r'(mp|dev)[0-9]+', k) or (in_place and re.fullmatch(r'unused[0-9]+', k))}: + detached = unused_volumes(config) + if baseline and detached != unused_volumes(record['observed'].get('config', '').encode()): + raise ValueError(translate('The container disks do not match the saved record')) + used_volumes = {value.split(',', 1)[0] for key, value in cfg.items() + if key == 'rootfs' or re.fullmatch(r'mp[0-9]+', key)} + if (len(set(detached.values())) != len(detached) + or any(not re.fullmatch(r'[A-Za-z0-9_.-]+:[^\s,]+', value) or value in used_volumes + for value in detached.values())): + raise ValueError(translate('The container disks do not match the saved record')) + if '[' in config.decode() or keys - BASIC - runtime_keys - coordinated_keys - preserved_keys - {k for k in keys if re.fullmatch(r'(mp|dev|unused)[0-9]+', k)}: raise ValueError(translate('The container has advanced Proxmox settings outside the supported profile')) unprivileged = deployment.get('security', {}).get('unprivileged', True) is not False if (desired.get('security', {}).get('unprivileged', True) is not False) != unprivileged \ @@ -549,7 +588,9 @@ def preflight(record, candidate, config, coordinated=None, in_place=False, basel or mount.get('ro', '0') != ('1' if old[target].get('read_only') else '0')): raise ValueError(translate('A container mount has a source, backup or permission different from the saved record')) for target in old.keys() & new.keys(): - if any(old[target].get(k) != new[target].get(k) for k in ('type', 'source', 'size_gb')): + directory_bind = old[target]['type'] == new[target]['type'] == 'host-bind' + fixed = ('type', 'size_gb') if directory_bind else ('type', 'source', 'size_gb') + if any(old[target].get(k) != new[target].get(k) for k in fixed): raise ValueError(translate('Changing the storage or size of a disk requires a migration; empty disks are not created')) protected = ('/bin', '/sbin', '/etc', '/usr', '/lib', '/lib64', '/proc', '/sys', '/dev', '/run') for target in new.keys() - old.keys(): @@ -1043,6 +1084,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i for p, m in actual.items() if p in required and not m['volume'].startswith('/')]) state = {'schema_version': 1, 'id': directory.name, 'vmid': vmid, 'operation': operation, 'record': record, 'candidate_contract': candidate, 'before_config': before.decode(), + 'retained_unused': unused_volumes(before), 'original_description': description, 'archive': str(archive), 'archive_sha256': filehash(archive), 'registry_digest': registry_digest or image['manifest_digest'], @@ -1116,6 +1158,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i else: owned(stage, 'proxmenux-transaction=' + state['id']) run('pct', 'move-volume', str(vmid), mount['key'], '--target-vmid', str(stage), '--target-volume', mount['key']) + move_retained_unused(state, to_stage=True) checkpoint(journal, state, 'data-parked') if show: msg_ok(translate('Data volumes protected')) @@ -1125,8 +1168,8 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i msg_info(translate('Installing the new image...') if update else translate('Recreating the container...')) current = owned(vmid, record['installation_id']) expected = b''.join(line for line in before.splitlines(keepends=True) - if not re.match(rb'mp[0-9]+: ', line)) - if mounts(current) or current != expected: + if not re.match(rb'(mp|unused)[0-9]+: ', line)) + if mounts(current) or unused_volumes(current) or current != expected: raise ValueError(translate('A concurrent change was detected; the container is not removed')) host_mounts.verify_sources(original_sources) gpu_devices.verify(original_gpu) @@ -1141,6 +1184,7 @@ def apply(root, vmid, archive, operation, proposal=None, registry_digest=None, i install_candidate(root, journal, state, progress) restore_description(vmid, state) restore_firewall(vmid, state) + move_retained_unused(state, to_stage=False) if coordinated: for key, value in state['preserved_stack_config'].items(): run('pct', 'set', str(vmid), '--' + key, value) @@ -1340,8 +1384,9 @@ def recover(root, journal): stop(vmid) current = owned(vmid, record['installation_id']) gpu_devices.check_recovery_entries(current, state) - if any(re.fullmatch(r'unused[0-9]+', k) for k in parse_config(current)): + if unused_volumes(current).keys() - state.get('retained_unused', {}).keys(): raise ValueError(translate('There are extra disks or bind mounts outside the journal; the rootfs is not replaced')) + move_retained_unused(state, to_stage=True) known_binds = {(m['source'], m['container_path']) for plan in (state['record']['deployment'], state['candidate_contract']['deployment']) for m in plan.get('mounts', []) if m['type'] == 'host-bind'} @@ -1369,6 +1414,7 @@ def recover(root, journal): host_mounts.verify_sources(state.get('original_host_sources', {})) run('pct', 'restore', str(vmid), backup, '--force', '1', '--storage', state['record']['deployment']['rootfs']['storage']) + move_retained_unused(state, to_stage=False) state['restore_config_sha256'] = sha(owned(vmid, state['record']['installation_id'])) checkpoint(journal, state, 'checking-recovery') if show: diff --git a/oci/remote/oci_native_modify.py b/oci/remote/oci_native_modify.py index 8538995c..8cefa086 100644 --- a/oci/remote/oci_native_modify.py +++ b/oci/remote/oci_native_modify.py @@ -86,6 +86,24 @@ def changes(record, candidate, config): if 'mounts' in changed: if any(transaction.is_file_bind(m) for plan in (before, after) for m in plan.get('mounts', [])): return None + if transaction.nested_mounts.without(config) != config: + # The installer also regenerates the recursive bind entries and + # hooks associated with these mounts. + return None + old_mounts = {m['container_path']: m for m in before.get('mounts', [])} + for mount in after.get('mounts', []): + original = old_mounts.get(mount['container_path']) + # Permission grants and installer-specific mount attributes need + # their normal installer, including when only their source changes. + if mount.get('grant_access'): + return None + if original: + fields = {k for k in original.keys() | mount.keys() if original.get(k) != mount.get(k)} + native_fields = {'read_only', 'backup', 'create_if_missing'} + if original['type'] == mount['type'] == 'host-bind': + native_fields.add('source') + if fields - native_fields: + return None return changed @@ -174,8 +192,14 @@ def plan(record, candidate, config): original = next(m for m in old['mounts'] if m['container_path'] == target) if mount == original: continue fields = dict(p.split('=', 1) for p in actual[target]['value'].split(',')[1:] if '=' in p) - fields['ro'] = '1' if mount.get('read_only') else '0' - values[actual[target]['key']] = actual[target]['volume']+','+','.join(k+'='+v for k, v in fields.items()) + source = actual[target]['volume'] + if mount['type'] == 'host-bind': + source = host_mounts.valid_path(mount['source']) + if not Path(source).is_dir() and not mount.get('create_if_missing'): + raise ValueError(translate('The host directory does not exist')) + fields.update(mp=target, backup='1' if mount['backup'] else '0', + ro='1' if mount.get('read_only') else '0') + values[actual[target]['key']] = source+','+','.join(k+'='+v for k, v in fields.items()) continue key = next('mp'+str(n) for n in range(256) if 'mp'+str(n) not in used) used.add(key) @@ -203,6 +227,12 @@ def verify_values(config, operation): if environment(config) != dict(item.split('=', 1) for item in value.split('\0')): raise ValueError(translate('Proxmox could not apply the container settings')) elif re.fullmatch(r'(mp|dev|net)[0-9]+', key): + if re.fullmatch(r'mp[0-9]+', key): + source = value.split(',', 1)[0] + actual_source = cfg.get(key, '').split(',', 1)[0] + allocating = re.fullmatch(r'[A-Za-z0-9_-]+:[0-9]+', source) + if (not allocating and actual_source != source) or (allocating and not actual_source.startswith(source.split(':')[0]+':')): + raise ValueError(translate('Proxmox could not apply the container settings')) desired = dict(p.split('=', 1) for p in value.split(',') if '=' in p) actual = dict(p.split('=', 1) for p in cfg.get(key, '').split(',') if '=' in p) defaults = {'uid': '0', 'gid': '0', 'deny-write': '0', 'ro': '0', 'backup': '0', 'mode': '0660', 'firewall': '0'} diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index ca2bb142..b57f7049 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -224,10 +224,9 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non backup_native_settings(vmid, keep_backup) return oci_native_modify.apply(instances.ROOT, record, desired, config, acknowledge_external_data=acknowledge_external_data) - if (operation == 'recreate' - and record['template']['container_contract']['image']['reference'] - == desired['template']['container_contract']['image']['reference']): - raise ValueError(translate('These settings require an application-specific adaptation. The image was not downloaded and the container was not recreated.')) + # 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. transaction.preflight(record, desired, config) msg_ok(translate('Container checked')) current = record['observed']['image'] if operation == 'update' else None diff --git a/oci/tests/test_modify_review.py b/oci/tests/test_modify_review.py new file mode 100644 index 00000000..e9d4e41d --- /dev/null +++ b/oci/tests/test_modify_review.py @@ -0,0 +1,219 @@ +"""Maintainer review: fallback, complete mount edits and retained unused disks.""" +import contextlib +import copy +from pathlib import Path +import re +import sys +import tempfile +import unittest +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'remote')) +import oci_instances as instances +import oci_instance_transaction as transaction +import oci_native_modify as native +import oci_update_current as update +import oci_operation_notice +from test_modify_image_reuse import record, CONFIG + + +def installation(): + result=record() + result['deployment'].update(rootfs={'storage':'local-zfs','size_gb':8}, onboot=True, + security={'unprivileged':True}) + result['observed']['image']['defaults']={} + config=CONFIG+b'unprivileged: 1\nonboot: 1\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,hwaddr=AA:BB:CC:DD:EE:FF\n' + result['observed'].update(config=config.decode(), config_sha256=transaction.sha(config)) + return result,config + + +class FallbackTests(unittest.TestCase): + def test_all_installer_adaptations_fall_back_before_any_native_write(self): + saved,config=installation() + def nvidia(c):c['deployment']['devices']=[{'kind':'nvidia-runtime','host_path':'/dev/nvidia0'}] + def groups(c):c['deployment']['devices']=[{'host_path':'/dev/dri/renderD128','append_host_device_gid_to_environment':'GROUPS'}] + def permissions(c): + c['deployment']['devices']=[{'host_path':'/dev/dri/renderD128'}] + c['deployment']['device_permissions']={'uid':1000} + mutations=[nvidia,groups,permissions, + lambda c:c['deployment'].update(completion_notes=['Installer note']), + lambda c:c['deployment'].update(post_start_configurations=[{'id':'adapter'}]), + lambda c:c['template']['catalog_ui'].update(title='Refreshed catalog template'), + lambda c:c['deployment'].update(mounts=[{'type':'host-bind','source':'/host/file','container_path':'/config/file','backup':False,'size_gb':None}])] + for mutation in mutations: + with self.subTest(mutation=mutation),tempfile.TemporaryDirectory() as folder: + candidate=copy.deepcopy(saved);mutation(candidate) + root=Path(folder);instances.write(instances.location(root,115),saved) + with patch.object(instances,'ROOT',root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(instances,'command',return_value=config), \ + patch.object(transaction,'external_changes',return_value={}),patch.object(transaction,'preflight'), \ + patch.object(transaction,'is_file_bind',side_effect=lambda m:m.get('source')=='/host/file'), \ + patch.object(native,'apply',side_effect=AssertionError('partial native apply')), \ + patch.object(update,'resolve_archive',return_value=(Path('/cached.tar'),'sha256:resolved')) as resolve, \ + patch.object(transaction,'apply') as recreate, \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + update.update(115,proposal={'operation':'recreate','candidate':candidate}) + recreate.assert_called_once();resolve.assert_called_once() + self.assertEqual(resolve.call_args.kwargs['local_archives'],[saved['observed']['archive_path']]) + + def test_native_validation_failure_never_falls_back_to_recreation(self): + saved,config=installation();candidate=copy.deepcopy(saved) + candidate['deployment']['environment']=[{'name':'INVALID NAME','value':'value'}] + with tempfile.TemporaryDirectory() as folder: + root=Path(folder);instances.write(instances.location(root,115),saved) + with patch.object(instances,'ROOT',root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(instances,'command',return_value=config), \ + patch.object(transaction,'preflight'),patch.object(transaction,'external_changes',return_value={}), \ + patch.object(transaction,'apply') as recreate,patch.object(update,'resolve_archive') as resolve, \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + with self.assertRaises(ValueError):update.update(115,proposal={'operation':'recreate','candidate':candidate}) + recreate.assert_not_called();resolve.assert_not_called() + + def test_native_api_failure_never_falls_back_after_starting_the_edit(self): + saved,config=installation();candidate=copy.deepcopy(saved) + candidate['deployment']['environment']=[{'name':'VALID','value':'value'}] + with tempfile.TemporaryDirectory() as folder: + root=Path(folder);instances.write(instances.location(root,115),saved) + with patch.object(instances,'ROOT',root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ + patch.object(instances,'command',return_value=config), \ + patch.object(transaction,'preflight'),patch.object(transaction,'external_changes',return_value={}), \ + patch.object(transaction,'apply') as recreate,patch.object(update,'resolve_archive') as resolve, \ + patch.object(native,'apply',side_effect=RuntimeError('native API failed')), \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + with self.assertRaisesRegex(RuntimeError,'native API failed'): + update.update(115,proposal={'operation':'recreate','candidate':candidate}) + recreate.assert_not_called();resolve.assert_not_called() + + +class MountTests(unittest.TestCase): + def setUp(self): + self.record,self.config=installation() + self.mount={'type':'host-bind','source':'/mnt/old','container_path':'/media', + 'size_gb':None,'backup':False,'read_only':False,'create_if_missing':False} + self.record['deployment']['mounts']=[self.mount] + self.config+=b'mp3: /mnt/old,mp=/media,backup=0,ro=0,replicate=0\n' + self.record['observed'].update(config=self.config.decode(),config_sha256=transaction.sha(self.config)) + self.candidate=copy.deepcopy(self.record) + + def test_existing_bind_source_and_flags_are_applied_with_other_options_preserved(self): + self.candidate['deployment']['mounts'][0].update(source='/mnt/new',read_only=True) + with patch.object(Path,'is_dir',return_value=True): + plan=native.plan(self.record,self.candidate,self.config) + self.assertEqual(plan['values'],{'mp3':'/mnt/new,mp=/media,backup=0,ro=1,replicate=0'}) + self.assertTrue(plan['restart']);self.assertEqual(plan['delete'],[]) + + def test_missing_replacement_directory_is_rejected_unless_creation_was_requested(self): + self.candidate['deployment']['mounts'][0]['source']='/mnt/new' + with patch.object(Path,'is_dir',return_value=False): + with self.assertRaisesRegex(ValueError,'directory does not exist'): + native.plan(self.record,self.candidate,self.config) + self.candidate['deployment']['mounts'][0]['create_if_missing']=True + self.assertIn('/mnt/new,',native.plan(self.record,self.candidate,self.config)['values']['mp3']) + + def test_verification_detects_a_source_that_was_not_applied(self): + operation={'values':{'mp3':'/mnt/new,mp=/media,backup=0,ro=0'},'delete':[]} + with self.assertRaisesRegex(ValueError,'could not apply'):native.verify_values(self.config,operation) + native.verify_values(self.config.replace(b'/mnt/old',b'/mnt/new'),operation) + + def test_unknown_mount_options_and_permission_grants_use_installer(self): + for fields in ({'grant_access':True},{'mount_options':['noexec']},{'type':'managed-volume','source':'local-zfs','size_gb':2,'backup':True}): + candidate=copy.deepcopy(self.candidate);candidate['deployment']['mounts'][0].update(fields) + self.assertIsNone(native.changes(self.record,candidate,self.config)) + + def test_recursive_bind_adaptations_use_installer(self): + self.candidate['deployment']['mounts'][0]['read_only']=True + derived=b'lxc.mount.entry: /mnt/old media none rbind,rslave,create=dir 0 0\n' + self.assertIsNone(native.changes(self.record,self.candidate,self.config+derived)) + + def test_storage_and_disk_size_changes_keep_existing_migration_guard(self): + self.record['deployment']['mounts'][0].update(type='managed-volume',source='local-zfs',size_gb=1,backup=True) + self.config=self.config.replace(b'/mnt/old,mp=/media,backup=0',b'local-zfs:subvol-115-disk-1,mp=/media,backup=1') + self.record['observed']['config']=self.config.decode() + for fields in ({'size_gb':2},{'source':'other-storage'}): + candidate=copy.deepcopy(self.record);candidate['deployment']['mounts'][0].update(fields) + self.assertIsNone(native.changes(self.record,candidate,self.config)) + with self.assertRaisesRegex(ValueError,'migration'):transaction.preflight(self.record,candidate,self.config) + + +class DetachedPreflightTests(unittest.TestCase): + def test_removing_a_mount_then_updating_accepts_the_recorded_unused_disk(self): + saved,config=installation() + saved['deployment']['mounts']=[{'type':'managed-volume','source':'local-zfs','size_gb':1, + 'container_path':'/extra','backup':True,'read_only':False}] + config+=b'mp4: local-zfs:subvol-115-disk-1,mp=/extra,backup=1,size=1G\n' + saved['observed']['config']=config.decode() + candidate=copy.deepcopy(saved);candidate['deployment']['mounts']=[] + self.assertEqual(native.plan(saved,candidate,config)['delete'],['mp4']) + detached=config.replace(b'mp4: local-zfs:subvol-115-disk-1,mp=/extra,backup=1,size=1G\n',b'unused0: local-zfs:subvol-115-disk-1\n') + candidate['observed'].update(config=detached.decode(),config_sha256=transaction.sha(detached)) + cfg,_,_=transaction.preflight(candidate,copy.deepcopy(candidate),detached) + self.assertIn('unused0',cfg) + + def test_unrecorded_detached_disks_are_not_adopted_silently(self): + saved,config=installation() + with self.assertRaisesRegex(ValueError,'disks do not match|discard those changes'): + transaction.preflight(saved,copy.deepcopy(saved),config+b'unused0: local-zfs:subvol-115-disk-1\n') + + def test_invalid_duplicate_or_mounted_unused_disks_are_rejected(self): + for extra in (b'unused0: /etc/passwd\n',b'unused0: local-zfs:subvol-115-disk-0\n', + b'unused0: local-zfs:subvol-115-disk-1\nunused1: local-zfs:subvol-115-disk-1\n'): + saved,config=installation();config+=extra;saved['observed']['config']=config.decode() + with self.assertRaisesRegex(ValueError,'disks do not match'):transaction.preflight(saved,copy.deepcopy(saved),config) + + +class DetachedTransferTests(unittest.TestCase): + def setUp(self): + saved,config=installation() + self.configs={115:config+b'unused0: local-zfs:subvol-115-disk-1\nunused2: local-zfs:subvol-115-disk-2\n', + 900:b'description: proxmenux-transaction=test-tx\nrootfs: local-zfs:subvol-900-disk-0\n'} + self.state={'vmid':115,'stage':900,'id':'test-tx','record':saved, + 'retained_unused':transaction.unused_volumes(self.configs[115])} + self.calls=[] + def owned(vmid,marker): + self.assertIn(marker.encode(),self.configs[vmid]);return self.configs[vmid] + def run(*args): + self.calls.append(args);self.assertEqual(args[:2],('pct','move-volume')) + source,key,target=int(args[2]),args[3],int(args[5]);self.assertEqual(key,args[7]) + line=next(line for line in self.configs[source].splitlines(keepends=True) if line.startswith((key+': ').encode())) + self.configs[source]=self.configs[source].replace(line,b'') + self.configs[target]+=line.replace(('subvol-'+str(source)+'-').encode(),('subvol-'+str(target)+'-').encode()) + for p in (patch.object(transaction,'owned',side_effect=owned),patch.object(transaction,'run',side_effect=run)): + p.start();self.addCleanup(p.stop) + + def test_unused_disks_are_parked_and_returned_without_freeing_any_volume(self): + transaction.move_retained_unused(self.state,True) + self.assertEqual(transaction.unused_volumes(self.configs[115]),{}) + self.assertEqual(set(transaction.unused_volumes(self.configs[900])),{'unused0','unused2'}) + transaction.move_retained_unused(self.state,False) + self.assertEqual(transaction.unused_volumes(self.configs[115]),self.state['retained_unused']) + self.assertEqual(transaction.unused_volumes(self.configs[900]),{}) + self.assertEqual(len(self.calls),4) + + def test_an_interrupted_transfer_can_be_retried_in_both_directions(self): + original=transaction.run.side_effect + def interrupt(*args): + original(*args);raise RuntimeError('interrupted after reassignment') + with patch.object(transaction,'run',side_effect=interrupt): + with self.assertRaises(RuntimeError):transaction.move_retained_unused(self.state,True) + transaction.move_retained_unused(self.state,True) + with patch.object(transaction,'run',side_effect=interrupt): + with self.assertRaises(RuntimeError):transaction.move_retained_unused(self.state,False) + transaction.move_retained_unused(self.state,False) + self.assertEqual(transaction.unused_volumes(self.configs[115]),self.state['retained_unused']) + + def test_unrelated_unused_disks_are_not_moved(self): + self.configs[115]+=b'unused3: local-zfs:subvol-115-disk-3\n' + with self.assertRaises(ValueError):transaction.move_retained_unused(self.state,True) + self.assertEqual(self.calls,[]) + + def test_a_missing_disk_or_target_collision_blocks_the_transfer(self): + del self.state['retained_unused']['unused2'] + self.configs[115]=self.configs[115].replace(b'unused2: local-zfs:subvol-115-disk-2\n',b'') + self.configs[900]+=b'unused0: local-zfs:subvol-900-disk-9\n' + with self.assertRaises(ValueError):transaction.move_retained_unused(self.state,True) + self.configs[115]=self.configs[115].replace(b'unused0: local-zfs:subvol-115-disk-1\n',b'') + self.configs[900]=self.configs[900].replace(b'unused0: local-zfs:subvol-900-disk-9\n',b'') + with self.assertRaises(ValueError):transaction.move_retained_unused(self.state,False) + + +if __name__=='__main__':unittest.main() diff --git a/oci/tests/test_modify_review_transaction.py b/oci/tests/test_modify_review_transaction.py new file mode 100644 index 00000000..08bf79d3 --- /dev/null +++ b/oci/tests/test_modify_review_transaction.py @@ -0,0 +1,148 @@ +"""Exercise the actual update and recovery orchestration with a simulated PVE API. + +Unused volumes must leave the old CT before destroy/restore and return before +validation/commit, including an interruption during volume reassignment. +""" +import copy +import json +from pathlib import Path +import re +import socket +import sys +import tempfile +import unittest +from unittest.mock import patch + +sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'remote')) +import oci_instances as instances +import oci_instance_transaction as transaction +from test_modify_review import installation +from test_modify_image_reuse import NEW + + +class UpdateRetainedDiskTests(unittest.TestCase): + def setUp(self): + temp=tempfile.TemporaryDirectory();self.addCleanup(temp.cleanup) + self.root=Path(temp.name)/'instances' + self.archive=Path(temp.name)/'image.tar';self.archive.write_bytes(b'verified fixture') + self.backup=Path(temp.name)/'backup.tar.zst';self.backup.write_bytes(b'verified backup') + self.saved,config=installation() + self.base_config=config + config+=b'unused0: local-zfs:subvol-115-disk-1\nunused2: local-zfs:subvol-115-disk-2\n' + self.saved['observed'].update(config=config.decode(),config_sha256=transaction.sha(config)) + self.saved['observed']['archive_path']=str(self.archive) + self.image=copy.deepcopy(self.saved['observed']['image']);self.image['manifest_digest']=NEW + self.configs={115:config};self.commands=[];self.fail_install=False;self.interrupt_return=False + instances.write(instances.location(self.root,115),self.saved) + def config_line(vmid,key,value): + before=self.configs[vmid] + result=b''.join(line for line in before.splitlines(keepends=True) if not line.startswith((key+': ').encode())) + self.configs[vmid]=result+(key+': '+value+'\n').encode() + def run(*args): + self.commands.append(args) + if args[0]=='zstd': + self.assertEqual(args[1:],('-t',str(self.backup))) + return b'' + if args[:2]==('pvesh','get'): + if args[2]=='/cluster/nextid':return b'900' + return json.dumps([{'vmid':vmid,'type':'lxc','node':socket.gethostname().split('.')[0]} + for vmid in self.configs]).encode() if args[2]=='/cluster/resources' else b'[]' + self.assertEqual(args[0],'pct',args) + action,vmid=args[1],int(args[2]) + if action=='config':return self.configs[vmid] + if action=='status':return b'status: running' + if action in ('start','shutdown','stop'):return b'' + if action=='create': + marker=args[args.index('--description')+1] + self.configs[vmid]=f'description: {marker}\nrootfs: local-zfs:subvol-{vmid}-disk-0\n'.encode() + elif action=='destroy': + self.assertFalse(transaction.unused_volumes(self.configs[vmid]),'would destroy retained data') + self.assertFalse(transaction.mounts(self.configs[vmid]),'would destroy attached data') + del self.configs[vmid] + elif action=='set': + for index in range(3,len(args),2):config_line(vmid,args[index][2:],args[index+1]) + elif action=='move-volume': + key,target,target_key=args[3],int(args[5]),args[7] + volume=transaction.unused_volumes(self.configs[vmid])[key] + line=(key+': '+volume+'\n').encode() + self.configs[vmid]=self.configs[vmid].replace(line,b'') + volume=volume.replace('subvol-'+str(vmid)+'-','subvol-'+str(target)+'-') + config_line(target,target_key,volume) + if self.interrupt_return and target==115: + self.interrupt_return=False + raise RuntimeError('interrupted return after PVE reassignment') + elif action=='restore': + # vzdump does not contain detached disks; restore skips unusedN. + self.assertFalse(transaction.unused_volumes(self.configs.get(vmid,b''))) + self.configs[vmid]=self.base_config + else:self.fail('Unexpected PVE command: '+str(args)) + return b'' + def install(root,journal,state,progress=None): + if self.fail_install:raise RuntimeError('installer failed after old rootfs was removed') + self.configs[state['vmid']]=self.base_config + def observe(vmid,identity,archive,digest,image=None): + observed=copy.deepcopy(self.saved['observed']) + observed.update(config=self.configs[vmid].decode(),config_sha256=transaction.sha(self.configs[vmid]), + image=copy.deepcopy(image or self.image),resolved_registry_digest=digest) + return observed + original_exists=Path.exists + def exists(path): + match=re.fullmatch(r'/etc/pve/lxc/([0-9]+)\.conf',str(path)) + return int(match[1]) in self.configs if match else original_exists(path) + patches=[patch.object(transaction,'run',side_effect=run),patch.object(transaction,'open_log'), + patch.object(transaction,'log'),patch.object(transaction,'stop'), + patch.object(transaction,'check_archive'),patch.object(transaction,'image_from_archive',return_value=self.image), + patch.object(transaction,'verified_backup',return_value=self.backup), + patch.object(transaction,'install_candidate',side_effect=install), + patch.object(transaction,'healthcheck',return_value='running'), + patch.object(transaction,'candidate_host_sources',return_value={}), + patch.object(transaction,'prune_backups'),patch.object(transaction.image_cache,'prune',return_value=[]), + patch.object(transaction,'cleanup_restored_format_dirs'),patch.object(transaction.runtime_settings,'restore'), + patch.object(transaction.work_backup,'locate',side_effect=lambda directory,*a:directory), + patch.object(instances,'observe',side_effect=observe),patch.object(Path,'exists',exists)] + for p in patches:p.start();self.addCleanup(p.stop) + self.addCleanup(transaction._run.update,journal=None) + + def apply(self,**kwargs):return transaction.apply(self.root,115,self.archive,'update',registry_digest=NEW,**kwargs) + + def journal(self):return next(instances.location(self.root,115).parent.glob('transactions/*/transaction.json')) + + def assert_retained(self): + self.assertEqual(transaction.unused_volumes(self.configs[115]),{ + 'unused0':'local-zfs:subvol-115-disk-1','unused2':'local-zfs:subvol-115-disk-2'}) + self.assertNotIn(900,self.configs) + self.assertFalse(any(args[:2]==('pct','set') and '--delete' in args for args in self.commands)) + + def test_update_keeps_detached_disks_and_the_next_update_preflight_accepts_them(self): + self.apply();self.assert_retained() + saved=instances.read(self.root,115) + self.assertEqual(saved['status'],'installed') + self.assertEqual(saved['observed']['image']['manifest_digest'],NEW) + transaction.preflight(saved,copy.deepcopy(saved),self.configs[115]) + self.assertEqual(json.loads(self.journal().read_text())['phase'],'committed') + + def test_failed_install_restores_both_backup_and_detached_disks(self): + self.fail_install=True + with self.assertRaisesRegex(RuntimeError,'installer failed'):self.apply() + transaction.recover(self.root,self.journal()) + self.assert_retained() + 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_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() + + def test_recovery_handles_an_interruption_while_returning_unused_disks(self): + self.interrupt_return=True + with self.assertRaisesRegex(RuntimeError,'interrupted return'):self.apply() + transaction.recover(self.root,self.journal());self.assert_retained() + + def test_failed_runtime_check_recovers_disks_already_returned_to_the_candidate(self): + with patch.object(transaction.gpu_devices,'validate_runtime',side_effect=[RuntimeError('invalid runtime'), None]): + with self.assertRaisesRegex(RuntimeError,'invalid runtime'):self.apply() + transaction.recover(self.root,self.journal()) + self.assert_retained() + + +if __name__=='__main__':unittest.main() diff --git a/oci/tests/test_native_modify.py b/oci/tests/test_native_modify.py index 62e37885..cf42493e 100644 --- a/oci/tests/test_native_modify.py +++ b/oci/tests/test_native_modify.py @@ -236,14 +236,18 @@ class NativeApplyTests(unittest.TestCase): update.update(115,proposal={'operation':'recreate','candidate':self.candidate}) self.assertEqual(instances.read(self.root,115)['deployment']['environment'],self.candidate['deployment']['environment']) - def test_unsupported_same_image_adaptation_never_silently_recreates(self): + def test_unsupported_same_image_adaptation_uses_the_existing_recreation_flow(self): self.candidate['deployment']['post_start_configurations']=[{'id':'unsupported-adapter'}] with patch.object(instances,'ROOT',self.root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ patch.object(native.transaction,'external_changes',return_value={}), \ - patch.object(update,'resolve_archive',side_effect=AssertionError('registry path')), \ - patch.object(native.transaction,'apply',side_effect=AssertionError('recreation')): - with self.assertRaisesRegex(ValueError,'application-specific adaptation'): - update.update(115,proposal={'operation':'recreate','candidate':self.candidate}) + patch.object(update,'resolve_archive',return_value=(Path('/verified.tar'),'sha256:test')) as resolve, \ + patch.object(native.transaction,'apply') as recreate, \ + patch.object(native,'apply',side_effect=AssertionError('partial native edit')), \ + patch.object(oci_operation_notice,'operation',return_value=contextlib.nullcontext()): + update.update(115,proposal={'operation':'recreate','candidate':self.candidate}) + resolve.assert_called_once() + recreate.assert_called_once() + self.assertEqual(recreate.call_args.args[3],'recreate') def test_optional_backup_does_not_force_recreation(self): with patch.object(instances,'ROOT',self.root),patch.object(instances,'locked',return_value=contextlib.nullcontext()), \ From e938a25da3da458fccdb3a91ea02818460aef9ea Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:46:16 +0200 Subject: [PATCH 33/53] fix(oci): keep Proxmox tags editable on native stack members - Accept tag changes in the native stack check before recreate or update - Keep identity and every other setting checked as before - Add a regression test for the native stack check --- oci/remote/oci_instances.py | 8 +++++--- oci/remote/oci_stack_native.py | 2 +- oci/tests/test_external_tags.py | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/oci/remote/oci_instances.py b/oci/remote/oci_instances.py index f039b956..0f4d19ce 100644 --- a/oci/remote/oci_instances.py +++ b/oci/remote/oci_instances.py @@ -230,8 +230,9 @@ def identity(config): return match.group(1) if match else None -def same_config_except_notes(record, config): - """Accept a presentation-only note edit, never a changed OCI identity or LXC setting.""" +def same_config_except_notes(record, config, ignore_tags=False): + """Accept a presentation-only note edit, never a changed OCI identity or LXC setting. + With `ignore_tags`, Proxmox tags may differ too: the operation keeps them as they are.""" previous = record['observed']['config'].encode() expected = record['installation_id'] if identity(previous) != expected or identity(config) != expected: @@ -242,7 +243,8 @@ def same_config_except_notes(record, config): notes = [line for line in lines if line.startswith(b'description: ')] if len(notes) != 1: return None - return b''.join(line for line in lines if not line.startswith(b'description: ')) + dropped = (b'description: ', b'tags: ') if ignore_tags else (b'description: ',) + return b''.join(line for line in lines if not line.startswith(dropped)) original = without_notes(previous) return original is not None and original == without_notes(config) diff --git a/oci/remote/oci_stack_native.py b/oci/remote/oci_stack_native.py index b679ee74..2b564205 100644 --- a/oci/remote/oci_stack_native.py +++ b/oci/remote/oci_stack_native.py @@ -192,7 +192,7 @@ class NativeAdapter: config = instances.command('pct', 'config', str(vmid)) if instances.identity(config) != record['installation_id']: raise ValueError(translate('The identity of a member was replaced')) - if (not self.journal.exists() or not self.state().get('replacement_intent')) and not instances.same_config_except_notes(record, config): + if (not self.journal.exists() or not self.state().get('replacement_intent')) and not instances.same_config_except_notes(record, config, ignore_tags=True): raise ValueError(translate('A member configuration changed during the preparation')) else: if Path('/etc/pve/lxc/%s.conf' % vmid).exists(): diff --git a/oci/tests/test_external_tags.py b/oci/tests/test_external_tags.py index a28dd420..0f09c5e3 100644 --- a/oci/tests/test_external_tags.py +++ b/oci/tests/test_external_tags.py @@ -166,6 +166,20 @@ die() { printf '%s\\n' "$*" >&2; exit 1; } self.assertEqual(state['preserved_stack_config']['tags'], tags or '') @unittest.skipUnless(shutil.which('bash') and shutil.which('jq'), 'bash and jq are needed') + def test_native_stack_check_accepts_tag_edits_and_nothing_else(self): + saved = record() + instances = transaction.instances + for new in ('homelab;oci;proxmenux', 'proxmenux;oci', '', None): + with self.subTest(new=new): + self.assertTrue(instances.same_config_except_notes(saved, config(new), ignore_tags=True)) + self.assertFalse(instances.same_config_except_notes(saved, config(new))) + changed = config('homelab').replace(b'cores: 2', b'cores: 4') + self.assertFalse(instances.same_config_except_notes(saved, changed, ignore_tags=True)) + other = config('homelab').replace(MARKER.encode(), b'22222222-2222-4222-8222-222222222222') + self.assertFalse(instances.same_config_except_notes(saved, other, ignore_tags=True)) + source = (ROOT / 'remote/oci_stack_native.py').read_text() + self.assertIn('same_config_except_notes(record, config, ignore_tags=True)', source) + def test_installer_still_refuses_unsupported_kept_settings(self): installer = (ROOT / 'remote/install_oci.sh').read_text() function = re.search(r'^apply_kept_settings\(\) \{\n.*?^\}\n', installer, re.M | re.S).group(0) From e38c085e5f0e80e99189bcd00924faab85ea2580 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 17 Oct 2026 20:47:24 +0200 Subject: [PATCH 34/53] accept non-HTTP answers from guessed service ports --- .github/scripts/build_translation_cache.py | 12 +++ .github/workflows/test-oci-registries.yml | 1 + oci/remote/install_oci.sh | 14 ++- oci/remote/oci_instance_transaction.py | 36 ++++++-- .../test_guessed_endpoint_healthcheck.py | 87 +++++++++++++++++++ 5 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 oci/tests/test_guessed_endpoint_healthcheck.py diff --git a/.github/scripts/build_translation_cache.py b/.github/scripts/build_translation_cache.py index 95825a29..b5797010 100644 --- a/.github/scripts/build_translation_cache.py +++ b/.github/scripts/build_translation_cache.py @@ -38,6 +38,18 @@ DEFAULT_CONTEXT = "Context: Technical message for Proxmox and IT. Translate:" # provider, then restore the exact source spelling afterwards. Keep longer # terms first so ``gasket`` cannot consume part of ``gasket-dkms``. PROTECTED_TECHNICAL_TERMS = ( + # Product names, kept whole. Left to a translator, "Proxmox Backup Server" + # became "Proxmox Sicherungsserver" and "Serveur de sauvegarde Proxmox", + # and when a provider rightly handed it back unchanged the string was + # retried and then dropped as untranslated. + "Proxmox Backup Server", + "Proxmox Datacenter Manager", + "Proxmox Mail Gateway", + "Proxmox VE", + "ProxMenux Monitor", + "ProxMenux Optimizations", + "Secure Gateway", + "Tailscale", "google/gasket-driver", "feranick/gasket-driver", "libedgetpu1-std", diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index ff0032cb..16ff314f 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -45,3 +45,4 @@ jobs: run: | python3 -m unittest discover -s oci/tests -p 'test_external_tags.py' -v python3 -m unittest discover -s oci/tests -p 'test_instance_reconcile.py' -v + python3 -m unittest discover -s oci/tests -p 'test_guessed_endpoint_healthcheck.py' -v diff --git a/oci/remote/install_oci.sh b/oci/remote/install_oci.sh index 7d6da264..5f1802ba 100755 --- a/oci/remote/install_oci.sh +++ b/oci/remote/install_oci.sh @@ -359,13 +359,18 @@ cleanup_canonical_inputs() { # A derived check accepts any HTTP answer below 500: the application is up, # even when its first page is a redirect or asks for a login. healthcheck_probe() { + local code status=0 if [[ ${HC_ANY_STATUS:-false} == true ]]; then - local code - code=$(curl "${CURL_ARGS[@]}" "$HC_URL" 2>/dev/null) || return 1 - [[ $code =~ ^[1-4][0-9][0-9]$ ]] + code=$(curl "${CURL_ARGS[@]}" "$HC_URL" 2>/dev/null) || status=$? + (( status == 0 )) && [[ $code =~ ^[1-4][0-9][0-9]$ ]] && return 0 else - curl "${CURL_ARGS[@]}" "$HC_URL" 2>/dev/null + curl "${CURL_ARGS[@]}" "$HC_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 + # in another protocol (HTTP/0.9, weird or empty reply, reset) means it is up. + [[ ${HC_ACCEPT_NON_HTTP:-false} == true ]] \ + && (( status == 1 || status == 8 || status == 52 || status == 56 )) } cleanup_failed_install() { @@ -2267,6 +2272,7 @@ if [[ $START_AFTER == 1 && $HAS_STARTUP_HEALTHCHECK == 1 ]]; then [[ -n $IP ]] || die "$(translate "The healthcheck cannot run without an IP address")" HC_URL="${HC_SCHEME}://${IP}:${HC_PORT}${HC_PATH}" 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 CURL_ARGS=(-sS --noproxy '*' -o /dev/null -w '%{http_code}' --max-time "$HC_REQUEST_TIMEOUT") else diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index cdde917b..b5cef528 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -352,6 +352,22 @@ def profile_keys(deployment): return keys +GUESSED_ENDPOINT = 'compose-first-tcp-port-fallback' +# curl exit codes for a port that accepted the connection and answered in a +# protocol other than HTTP: HTTP/0.9 refused, weird reply, empty reply, reset. +NON_HTTP_ANSWER = (1, 8, 52, 56) + + +def probe_http(args): + """Run one curl probe and return its output and exit code.""" + log('$ ' + shlex.join(str(arg) for arg in args)) + try: + result = subprocess.run(args, capture_output=True, timeout=60, close_fds=True) + except subprocess.TimeoutExpired: + return b'', -1 + return result.stdout, result.returncode + + 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.""" @@ -367,9 +383,14 @@ def effective_healthcheck(template): path = str(endpoint.get('path') or '/') if (endpoint.get('scheme') in ('http', 'https') and str(endpoint.get('port', '')).isdigit() and path.startswith('/') and not any(c.isspace() for c in path)): - return {'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 = {'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} + # 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: + check['accept_non_http'] = True + return check # Without a web address, the new image must at least keep the container running. return {'type': 'running', 'timeout_seconds': 90, 'stability_seconds': 20} @@ -733,11 +754,10 @@ def healthcheck(vmid, template): args += ['-w', '%{http_code}'] if not check.get('verify_tls', False): args.append('-k') - try: - answer = run(*args, f'{scheme}://{ip}:{port}{path}') - ok = not any_status or re.fullmatch(rb'[1-4][0-9][0-9]', answer.strip()) is not None - except RuntimeError: - pass + 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: stable_since = stable_since or time.monotonic() if time.monotonic() - stable_since >= stability: diff --git a/oci/tests/test_guessed_endpoint_healthcheck.py b/oci/tests/test_guessed_endpoint_healthcheck.py new file mode 100644 index 00000000..8ce9003f --- /dev/null +++ b/oci/tests/test_guessed_endpoint_healthcheck.py @@ -0,0 +1,87 @@ +"""A web address guessed from the compose file may belong to a service that does +not speak HTTP. Updates, Modify and recovery must accept its answer instead of +waiting for an HTTP reply that never comes.""" + +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'remote')) + +import oci_instance_transaction as transaction + +INSTALLER = ROOT / 'remote/install_oci.sh' +GUESSED = {'first_run': {'endpoints': [{'label': 'Web UI', 'scheme': 'http', 'port': 3306, 'path': '/', + 'source': 'compose-first-tcp-port-fallback'}]}} +DECLARED = {'first_run': {'endpoints': [{'label': 'Web UI', 'scheme': 'http', 'port': 8080, 'path': '/'}]}} + + +def probe_function(): + source = INSTALLER.read_text() + start = source.index('healthcheck_probe() {') + end = source.index('\n}\n', start) + 3 + return source[start:end] + + +class GuessedEndpointCheckTests(unittest.TestCase): + def test_only_a_guessed_endpoint_accepts_other_protocols(self): + self.assertTrue(transaction.effective_healthcheck(GUESSED)['accept_non_http']) + self.assertNotIn('accept_non_http', transaction.effective_healthcheck(DECLARED)) + + def run_probe(self, exit_code, code='000', accept_non_http='true', any_status='true'): + with tempfile.TemporaryDirectory() as directory: + fake = Path(directory) / 'curl' + fake.write_text(f'#!/bin/sh\nprintf %s {code}\nexit {exit_code}\n') + fake.chmod(0o755) + script = (probe_function() + '\nCURL_ARGS=()\nHC_URL=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 + + def test_installer_probe_accepts_a_non_http_answer_only_when_guessed(self): + for status in transaction.NON_HTTP_ANSWER: + with self.subTest(status=status): + self.assertEqual(self.run_probe(status), 0) + self.assertNotEqual(self.run_probe(status, accept_non_http='false'), 0) + # A closed port, a timeout or a server error still fail. + for status, code in ((7, '000'), (28, '000'), (0, '503')): + with self.subTest(status=status, code=code): + self.assertNotEqual(self.run_probe(status, code), 0) + self.assertEqual(self.run_probe(0, '200'), 0) + self.assertEqual(self.run_probe(0, any_status='false', accept_non_http='false'), 0) + + def test_recovery_check_accepts_a_database_answer(self): + check = transaction.effective_healthcheck(GUESSED) + 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) + + with patch.object(transaction, 'run', side_effect=run), \ + patch.object(transaction, 'probe_http', return_value=(b'000', 1)), \ + patch.object(transaction, 'log'): + self.assertEqual(transaction.healthcheck(113, template), 'http://192.0.2.10:3306/') + declared = transaction.effective_healthcheck(DECLARED) + declared.update(timeout_seconds=4, stability_seconds=0) + template = {'proxmox': {'installer_profile': {'startup_healthcheck': declared}}} + with patch.object(transaction, 'run', side_effect=run), \ + patch.object(transaction, 'probe_http', return_value=(b'000', 1)), \ + patch.object(transaction, 'log'), patch.object(transaction.time, 'sleep'): + with self.assertRaises(ValueError): + transaction.healthcheck(113, template) + + +if __name__ == '__main__': + unittest.main() From 152c3ddfe05daf116b19f586b1c6919d2935086f Mon Sep 17 00:00:00 2001 From: ProxMenuxBot Date: Sat, 17 Oct 2026 18:48:23 +0000 Subject: [PATCH 35/53] chore(lang): auto-rebuild translation cache Source: e38c085 Triggered by: push --- lang/sv.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/sv.json b/lang/sv.json index 57a2c152..3874f7bd 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -4883,8 +4883,10 @@ "Proxmology logo applied": "Proxmology-logotyp applicerad", "Proxmox 9 system update allready": "Proxmox 9 systemuppdatering redan", "Proxmox APT repositories configured": "Proxmox APT-förråd konfigurerade", + "Proxmox Backup Server": "Proxmox Backup Server", "Proxmox Backup Server (PBS) destinations": "Destinationer för Proxmox Backup Server (PBS)", "Proxmox Backup Server APT repositories configured": "Proxmox Backup Server APT-förråd konfigurerade", + "Proxmox Backup Server Update": "Proxmox Backup Server Uppdatering", "Proxmox Backup Server update completed.": "Proxmox Backup Server-uppdatering slutförd.", "Proxmox CIFS Storage Status:": "Proxmox CIFS-lagringsstatus:", "Proxmox NFS Storage Status:": "Proxmox NFS-lagringsstatus:", From 1bc38884cea7625cf8ad79c91891206cc3b434c5 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 17 Oct 2026 20:59:40 +0200 Subject: [PATCH 36/53] run the runtime HOME regressions in the OCI workflow --- .github/workflows/test-oci-registries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 16ff314f..f04a7677 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -46,3 +46,4 @@ jobs: python3 -m unittest discover -s oci/tests -p 'test_external_tags.py' -v python3 -m unittest discover -s oci/tests -p 'test_instance_reconcile.py' -v python3 -m unittest discover -s oci/tests -p 'test_guessed_endpoint_healthcheck.py' -v + python3 -m unittest discover -s oci/tests -p 'test_runtime_home_*.py' -v From 7d17220294323d6ace78e12aa26a74bfd5af70de Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:20:56 +0200 Subject: [PATCH 37/53] fix(lang): use directorios compartidos in the Spanish Modify menu --- lang/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/es.json b/lang/es.json index 9a7aa1db..60edefc6 100644 --- a/lang/es.json +++ b/lang/es.json @@ -5792,7 +5792,7 @@ "ShareCoveX panel": "Panel de ShareCoveX", "Shared Directory Ready:": "Directorio compartido listo:", "Shared Group": "Grupo compartido", - "Shared directories": "Carpetas compartidas", + "Shared directories": "Directorios compartidos", "Shared directory created:": "Directorio compartido creado:", "Shared directory for consume and export": "Directorio compartido para consumo y exportación", "Shared directory for copy/sync operations": "Directorio compartido para operaciones de copia/sincronización", From 2c8ebcaa9f8954549a0d6f3758b648cece0ba9c4 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:21:30 +0200 Subject: [PATCH 38/53] fix(oci): count only paths added by hand in the Modify menu - Leave the application's own volumes out of the Extra paths count - Add a regression test for the count --- oci/src/proxmenux_oci/recreation.py | 3 ++- oci/tests/test_modify_menu.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/oci/src/proxmenux_oci/recreation.py b/oci/src/proxmenux_oci/recreation.py index f0d6f691..e1f3d897 100644 --- a/oci/src/proxmenux_oci/recreation.py +++ b/oci/src/proxmenux_oci/recreation.py @@ -120,7 +120,8 @@ def modification_options(candidate): ('memory', row(translate('RAM in MiB'), resources['memory_mb'])), ('network', row(translate('Network'), f"{network.get('bridge', '-')} · {'DHCP' if address == 'dhcp' else address}")), - ('paths', row(translate('Extra paths'), len(deployment.get('mounts', [])))), + # Only the paths added by hand: the application's own volumes are not extra. + ('paths', row(translate('Extra paths'), sum(1 for m in deployment.get('mounts', []) if m.get('custom')))), ] if profile.get('shared_directories'): options.append(('shares', translate('Shared directories'))) diff --git a/oci/tests/test_modify_menu.py b/oci/tests/test_modify_menu.py index 2709da4f..601c588e 100644 --- a/oci/tests/test_modify_menu.py +++ b/oci/tests/test_modify_menu.py @@ -147,6 +147,13 @@ class ModifyMenuTests(unittest.TestCase): recreation.edit_recreation(record(), ui) self.assertNotIn('private-test-secret', json.dumps(ui.events)) + def test_extra_paths_counts_only_paths_added_by_hand(self, _language): + candidate = record() + self.assertTrue(dict(recreation.modification_options(candidate))['paths'].rstrip().endswith(' 0')) + candidate['deployment']['mounts'].append({'container_path': '/media-extra', 'type': 'host-bind', + 'source': '/mnt/media', 'custom': True}) + self.assertTrue(dict(recreation.modification_options(candidate))['paths'].rstrip().endswith(' 1')) + def test_optional_categories_follow_the_application_profile(self, _language): candidate = record() options = dict(recreation.modification_options(candidate)) From d8ae19c76e1c7e444feef496e2a84cf6ec08b4d5 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sat, 17 Oct 2026 21:49:00 +0200 Subject: [PATCH 39/53] open Manage straight to the list and simplify registry rows --- .../tests/test_oci_selection_setup_wording.py | 16 ++++++--- .github/workflows/test-oci-registries.yml | 1 + oci/src/proxmenux_oci/cli.py | 2 -- oci/src/proxmenux_oci/management.py | 19 +++++++++-- oci/src/proxmenux_oci/registry_accounts.py | 2 +- oci/tests/test_manage_sync_notice.py | 34 +++++++++++++++++++ 6 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 oci/tests/test_manage_sync_notice.py diff --git a/.github/scripts/tests/test_oci_selection_setup_wording.py b/.github/scripts/tests/test_oci_selection_setup_wording.py index 9d35d7d5..7baa2ad5 100644 --- a/.github/scripts/tests/test_oci_selection_setup_wording.py +++ b/.github/scripts/tests/test_oci_selection_setup_wording.py @@ -42,13 +42,13 @@ class SelectionSetupWording(TestCase): shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: [], _clean_orphans=lambda project: None, recover_automatically=lambda project: None, offer_recovery=lambda project, ui: None, - carry_records=lambda project, **options: {}, + carry_records=lambda project, **options: {}, _records_unread=lambda project: False, translate=lambda s: s) fn(Path('/fixture'), ui) ui.message.assert_called_once_with(EMPTY, 'OCI management') ui.choose.assert_not_called() - def _menu(self, inventories, selections): + def _menu(self, inventories, selections, unread=True): rows = iter(inventories) ui = SimpleNamespace(message=Mock(), choose=Mock(side_effect=selections)) refresh = Mock(return_value={}) @@ -57,7 +57,8 @@ class SelectionSetupWording(TestCase): shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: next(rows), _clean_orphans=lambda project: None, recover_automatically=lambda project: None, offer_recovery=lambda project, ui: None, - carry_records=refresh, manage_instance=manage, menu_size=lambda: 10, + carry_records=refresh, _records_unread=lambda project: unread, + manage_instance=manage, menu_size=lambda: 10, STATUS_LABELS={}, translate=lambda s: s) fn(Path('/fixture'), ui) return ui, refresh, manage @@ -67,7 +68,14 @@ class SelectionSetupWording(TestCase): ui, refresh, manage = self._menu([[row], [row]], ['101', None]) self.assertEqual(ui.choose.call_count, 2) manage.assert_called_once() - refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=True) + refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=True, + heading='Manage installed OCI applications') + + def test_the_list_opens_without_the_sync_notice_once_every_configuration_is_cached(self): + row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'} + _, refresh, _ = self._menu([[row]], [None], unread=False) + refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=False, + heading='Manage installed OCI applications') def test_removing_the_last_application_returns_without_a_message(self): row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'} diff --git a/.github/workflows/test-oci-registries.yml b/.github/workflows/test-oci-registries.yml index 085d9783..a3edecdf 100644 --- a/.github/workflows/test-oci-registries.yml +++ b/.github/workflows/test-oci-registries.yml @@ -50,6 +50,7 @@ jobs: PYTHONDONTWRITEBYTECODE: '1' run: | python3 -m unittest discover -s oci/tests -p test_modify_menu.py -v + python3 -m unittest discover -s oci/tests -p test_manage_sync_notice.py -v python3 -m unittest discover -s oci/tests -p test_recreation.py -v python3 -m unittest discover -s oci/tests -p test_shared_directories.py -v python3 -m unittest discover -s oci/tests -p 'test_wizard*.py' -v diff --git a/oci/src/proxmenux_oci/cli.py b/oci/src/proxmenux_oci/cli.py index 9266f283..14dda6e2 100644 --- a/oci/src/proxmenux_oci/cli.py +++ b/oci/src/proxmenux_oci/cli.py @@ -578,8 +578,6 @@ def interactive(catalog: Catalog) -> int: f"{translate('All applications')} ({len(applications)})") elif selection == "manage": from .management import interactive_management - console.show_logo() - console.msg_title(translate("Manage installed OCI applications")) interactive_management(PROJECT_ROOT, ui) elif selection == "registries": from .registry_accounts import open_registry_accounts diff --git a/oci/src/proxmenux_oci/management.py b/oci/src/proxmenux_oci/management.py index 699a279a..11007edd 100644 --- a/oci/src/proxmenux_oci/management.py +++ b/oci/src/proxmenux_oci/management.py @@ -132,7 +132,7 @@ def _run_lifecycle(command, title): return completed.returncode == 0 -def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=False): +def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=False, heading=None): """Leave inside each container the copy of its record that a restore on another host needs. It is refreshed after every operation. Returns what was found for each container. Every installed application is looked at, @@ -140,6 +140,9 @@ def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=F from . import console sys.path.insert(0, str(project / 'remote')) if shown: + if heading: + console.show_logo() + console.msg_title(heading) console.msg_info(translate('Updating the copy of the record...')) try: import oci_carried_record @@ -234,6 +237,17 @@ def interactive_management(project, ui): ui.message(translate('OCI management could not be completed. Check the backend status; no additional cleanup has been authorized.'), translate('OCI management')) +def _records_unread(project): + """Whether an installed application has no cached configuration yet; only + then does the first sync read every container and take a while.""" + sys.path.insert(0, str(project / 'remote')) + import oci_carried_record as carried + return any(not (carried.instances.location(carried.instances.ROOT, row['vmid']).parent + / carried.CONFIG_CACHE).exists() + for row in saved_inventory(project) + if row.get('status') == 'installed' and not row.get('pending')) + + def _clean_orphans(project): """Remove, silently, what is left of containers that exist on no node of the cluster.""" sys.path.insert(0, str(project / 'remote')) @@ -253,7 +267,8 @@ def _interactive_management(project, ui): _clean_orphans(project) recover_automatically(project) offer_recovery(project, ui) - carry_records(project, mount_stopped=False, shown=True) + carry_records(project, mount_stopped=False, shown=_records_unread(project), + heading=translate('Manage installed OCI applications')) # After each operation the list is shown again. Every operation leaves # the copies of the records up to date, so they are not all refreshed again. first = True diff --git a/oci/src/proxmenux_oci/registry_accounts.py b/oci/src/proxmenux_oci/registry_accounts.py index f500520c..2ad9791d 100644 --- a/oci/src/proxmenux_oci/registry_accounts.py +++ b/oci/src/proxmenux_oci/registry_accounts.py @@ -166,7 +166,7 @@ def registry_accounts_menu(ui, engine=ENGINE_ROOT, auth=None): return try: options = [('add', translate('Sign in to a registry'))] - options += [(f'account-{index}', f"{row['registry']:<28} {row['account'] or row['method']} [{translate('saved')}]") + options += [(f'account-{index}', f"{row['registry']:<28} [{translate('saved')}]") for index, row in enumerate(rows)] options += [('storage', translate('Credential storage and access'))] count = translate('{count} saved account.') if len(rows) == 1 else translate('{count} saved accounts.') diff --git a/oci/tests/test_manage_sync_notice.py b/oci/tests/test_manage_sync_notice.py new file mode 100644 index 00000000..ede909b5 --- /dev/null +++ b/oci/tests/test_manage_sync_notice.py @@ -0,0 +1,34 @@ +"""Opening Manage shows the sync notice only while some configuration is still unread.""" +from pathlib import Path +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / 'src')) +sys.path.insert(0, str(ROOT / 'remote')) + +from proxmenux_oci import management +import oci_carried_record as carried + + +class ManageSyncNoticeTests(unittest.TestCase): + def test_notice_only_while_an_installed_application_has_no_cache(self): + rows = [{'vmid': 101, 'status': 'installed', 'pending': False}, + {'vmid': 102, 'status': 'installed', 'pending': True}, + {'vmid': 103, 'status': 'failed', 'pending': False}] + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + with patch.object(carried.instances, 'ROOT', root), \ + patch.object(management, 'saved_inventory', return_value=rows): + self.assertTrue(management._records_unread(ROOT)) + cache = carried.instances.location(root, 101).parent / carried.CONFIG_CACHE + cache.parent.mkdir(parents=True) + cache.write_text('{}') + # The pending and failed applications are never cached by the sync. + self.assertFalse(management._records_unread(ROOT)) + + +if __name__ == '__main__': + unittest.main() From 32b71bdd3d236a6e2f3fce66f0b894c288772eb6 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:52:03 +0200 Subject: [PATCH 40/53] test(oci): follow the review inside the Modify menu in the latency test --- oci/tests/test_modify_latency.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/oci/tests/test_modify_latency.py b/oci/tests/test_modify_latency.py index 788215ad..f08359a5 100644 --- a/oci/tests/test_modify_latency.py +++ b/oci/tests/test_modify_latency.py @@ -53,11 +53,12 @@ class SelectedConfigTests(unittest.TestCase): class EditorLatencyTests(unittest.TestCase): - def run_editor(self,proposal=None,backup_ready=True,stale=False,adoption_error=False,approved=True): + def run_editor(self,proposal=None,backup_ready=True,stale=False,adoption_error=False,approved=True,declined=False): events=[];saved=record() row={'vmid':115,'reason':'matched','stack':False,'pending':False,'status':'installed'} def edit(*a,**kw): events.append('editor') + if declined:return None if proposal is None:raise UserCancelled() return proposal def backup(*a,**kw):events.append('backup');return backup_ready @@ -80,7 +81,9 @@ class EditorLatencyTests(unittest.TestCase): self.assertFalse(result);self.assertEqual(events,['editor']) def test_declining_the_review_has_no_backup_or_storage_probes(self): - result,events=self.run_editor({'operation':'recreate','candidate':record()},approved=False) + # The review is part of the Modify menu: declining it and leaving the + # menu returns no proposal, so no probe or operation follows. + result,events=self.run_editor(declined=True) self.assertFalse(result);self.assertEqual(events,['editor']) def test_confirmed_edit_keeps_backup_check_before_the_lifecycle(self): From e3c9162982b027a3a7274ca48679f14b8c678f6f Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 18 Oct 2026 11:40:17 +0200 Subject: [PATCH 41/53] Carry over OCI variables changed in Proxmox and keep the LXC update badge on old checks --- .../test_oci_update_preservation_wording.py | 2 + AppImage/components/virtual-machines.tsx | 9 ++- lang/es.json | 6 ++ oci/remote/oci_instance_reconcile.py | 53 +++++++++++++++- oci/remote/oci_update_current.py | 3 + oci/src/proxmenux_oci/management.py | 36 +++++++---- oci/tests/test_instance_reconcile.py | 61 +++++++++++++++++++ oci/tests/test_modify_menu.py | 21 ++++++- tests/lxc_updates/test_observed_state_ui.cjs | 6 +- 9 files changed, 171 insertions(+), 26 deletions(-) diff --git a/.github/scripts/tests/test_oci_update_preservation_wording.py b/.github/scripts/tests/test_oci_update_preservation_wording.py index 477196e9..e3fdae92 100644 --- a/.github/scripts/tests/test_oci_update_preservation_wording.py +++ b/.github/scripts/tests/test_oci_update_preservation_wording.py @@ -3,6 +3,7 @@ import ast from contextlib import nullcontext import copy import json +import os from pathlib import Path import re import runpy @@ -129,6 +130,7 @@ class UpdateWording(unittest.TestCase): reconcile = ModuleType('oci_instance_reconcile') reconcile.propose = lambda *args: None scope = {'sys': SimpleNamespace(path=[], executable='python3'), + 'os': os, 'tempfile': tempfile, 'Path': Path, 'translate': lambda text: text, 'check_selected': lambda project, row: row, '_selected_configuration': lambda vmid: instances.command('pct', 'config', str(vmid)), 'images': SimpleNamespace(offer_removal=lambda *args: None), diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index a538627a..1bf5b78c 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -11,7 +11,7 @@ import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" import { Button } from "./ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "./ui/dialog" -import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, ChevronRight, Terminal, Archive, Plus, PlusCircle, Loader2, Clock, Database, Shield, Bell, FileText, Settings2, Activity, Package, RefreshCw, EthernetPort, ArrowUpCircle, Info, CheckCircle2, EyeOff, Eye, Trash2, Check, X, AlertTriangle, AlertCircle, Search, Pin, Tag as TagIcon } from 'lucide-react' +import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, ChevronRight, Terminal, Archive, Plus, PlusCircle, Loader2, Database, Shield, Bell, FileText, Settings2, Activity, Package, RefreshCw, EthernetPort, ArrowUpCircle, Info, CheckCircle2, EyeOff, Eye, Trash2, Check, X, AlertTriangle, AlertCircle, Search, Pin, Tag as TagIcon } from 'lucide-react' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import { Checkbox } from "./ui/checkbox" import { Switch } from "./ui/switch" @@ -2863,8 +2863,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => { onClick?: () => void, ) => { if (!uc) return null - const stale = uc.stale === true || (!!uc.last_check && isLxcUpdateStale(uc)) - if (!stale && (!uc.available || !uc.count || uc.count <= 0)) return null + if (!uc.available || !uc.count || uc.count <= 0) return null const last = uc.last_check ? new Date(uc.last_check).toLocaleString() : "—" @@ -2912,8 +2911,8 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => { space — matters most on the LXC card row where uptime, IP and other chips compete for width. Count alone with the up-arrow reads unambiguously as "pending updates". */} - {stale ? : } - {stale ? t("vmLxc.updates.checkStale") : uc.count} + + {uc.count} {/* Chevron only when the badge is wired up as a clickable shortcut — its absence on the dashboard card avoids implying interactivity where there isn't any (the whole diff --git a/lang/es.json b/lang/es.json index 077ac4c6..b54e843c 100644 --- a/lang/es.json +++ b/lang/es.json @@ -2988,6 +2988,7 @@ "Inaccessible device during VM startup": "Dispositivo inaccesible durante el inicio de la VM", "Inactive": "Inactivo", "Inactive (entry in fstab, not currently mounted)": "Inactivo (entrada en fstab, no montado actualmente)", + "Include these changes in oci-compose?": "¿Incluir estos cambios en el oci-compose?", "Include these resources in oci-compose?": "¿Incorporar estos recursos al oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Incluye /etc/network (puede eliminar SSH inmediatamente)", "Includes /etc/zfs": "Incluye /etc/zfs", @@ -6781,6 +6782,7 @@ "These Proxmox resources were added outside ProxMenux:": "Se añadieron estos recursos en Proxmox fuera de ProxMenux:", "These VMIDs are not free:": "Estos VMID no están libres:", "These are the changes that will be made": "Estos son los cambios que se harán", + "These changes were made in Proxmox outside ProxMenux:": "Estos cambios se hicieron en Proxmox fuera de ProxMenux:", "These container mount paths have an extension-like suffix:": "Estas rutas de montaje del contenedor tienen un sufijo parecido a una extensión:", "These containers were restored from a backup and this host has no record of their OCI application:": "Estos contenedores se restauraron desde un backup y este host no tiene el registro de su aplicación OCI:", "These host backups run with this job and expect its storage:": "Estos backups del host se ejecutan con este trabajo y esperan su almacenamiento:", @@ -6791,6 +6793,7 @@ "These values are asked during the installation:": "Estos valores se preguntan durante la instalación:", "They are described in the image documentation:": "Están descritos en la documentación de la imagen:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Se añadirán al oci-compose antes de continuar. No se importarán recursos incompatibles o modificados.", + "They will be saved in oci-compose before continuing.": "Se guardarán en el oci-compose antes de continuar.", "This CIFS share is mounted with restrictive permissions.": "Este recurso compartido CIFS está montado con permisos restrictivos.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU se considera incompatible con el passthrough de GPU a una VM en ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este recurso compartido NFS está completamente restringido: ni siquiera la raíz del host puede escribir en él.", @@ -7644,6 +7647,7 @@ "active VF(s)": "FV activas", "active VFs": "FV activas", "active Virtual Functions. Changing its driver binding would destroy every VF.": "Funciones virtuales activas. Cambiar la vinculación del controlador destruiría todos los VF.", + "added in Proxmox": "añadida en Proxmox", "after copy; source tree is incomplete.": "después de la copia; El árbol fuente está incompleto.", "after update.": "después de la actualización.", "all guests": "todas las VM y CT", @@ -8010,6 +8014,7 @@ "remapped users": "usuarios reasignados", "remove the (now-empty) directory if possible": "elimine el directorio (ahora vacío) si es posible", "removed from Proxmox": "eliminado de Proxmox", + "removed in Proxmox": "eliminada en Proxmox", "removed successfully from Proxmox.": "eliminado correctamente de Proxmox.", "requires running the official installer, which restarts the Monitor service.": "requiere ejecutar el instalador oficial, que reinicia el servicio del Monitor.", "requires the package": "requiere el paquete", @@ -8113,6 +8118,7 @@ "using": "usando", "using direct path (not guaranteed to survive reboots).": "utilizando una ruta directa (no se garantiza que sobreviva a los reinicios).", "vCPUs:": "vCPU:", + "value changed in Proxmox": "valor cambiado en Proxmox", "vfio-pci IDs configured": "ID de vfio-pci configurados", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "ID de vfio-pci en /etc/modprobe.d/vfio.conf", "video": "vídeo", diff --git a/oci/remote/oci_instance_reconcile.py b/oci/remote/oci_instance_reconcile.py index 07291125..cd8632a7 100644 --- a/oci/remote/oci_instance_reconcile.py +++ b/oci/remote/oci_instance_reconcile.py @@ -9,6 +9,7 @@ import oci_instances as instances import oci_gpu_devices as gpu_devices import oci_host_mounts as host_mounts import oci_instance_transaction as transaction +import oci_native_modify as native_modify from oci_installation_state import parse_config, sha from oci_ui import translate @@ -88,6 +89,43 @@ def _device(key, value): return device +ENVIRONMENT_KEYS = ('env', 'lxc.environment.runtime') + + +def _environment(record, config): + """Variables changed in Proxmox since the last operation. Values are + applied but never listed: they may be passwords.""" + before = native_modify.environment(record['observed']['config'].encode()) + now = native_modify.environment(config) + defaults = {item.split('=', 1)[0] for item in record['observed']['image'].get('defaults', {}).get('Env', []) + if '=' in item} + removed = sorted(before.keys() - now.keys()) + # An image default comes back with a new container; its absence cannot be kept. + if any(name in defaults for name in removed): + raise ValueError(f"{translate('These manual changes cannot be adopted safely:')} env") + changed = sorted(name for name in now if before.get(name) != now[name]) + return {name: now[name] for name in changed}, removed + + +def _adopt_environment(deployment, values, removed): + items = deployment.setdefault('environment', []) + details = [] + for name, value in values.items(): + matches = [item for item in items if item['name'] == name] + if len(matches) > 1: + raise ValueError(translate('Duplicate variable in the contract; review it before editing')) + if matches: + matches[0]['value'] = value + details.append(f"env {name}: {translate('value changed in Proxmox')}") + else: + items.append({'name': name, 'value': value, 'sensitive': True}) + details.append(f"env {name}: {translate('added in Proxmox')}") + for name in removed: + items[:] = [item for item in items if item['name'] != name] + details.append(f"env {name}: {translate('removed in Proxmox')}") + return details + + def propose(record, config): if (record.get('status') != 'installed' or record.get('stack') or record.get('stack_member') or record.get('native_stack_intent') or record.get('deployment', {}).get('stack_managed')): @@ -101,16 +139,20 @@ def propose(record, config): # was checked above, so a presentation-only change needs no adoption. if 'description' in changed: changed.remove('description') + environment = None + if any(key in ENVIRONMENT_KEYS for key in changed): + changed = [key for key in changed if key not in ENVIRONMENT_KEYS] + environment = _environment(record, config) new_keys = [key for key in changed if key not in before and re.fullmatch(r'(mp|dev)[0-9]+', key)] unsupported = [key for key in changed if key not in new_keys and key not in transaction.ADOPTABLE and key not in transaction.KEPT_AS_IS] if unsupported: raise ValueError(f"{translate('These manual changes cannot be adopted safely:')} {', '.join(unsupported)}") - if not new_keys: + if not new_keys and environment is None: return None candidate = copy.deepcopy(record) deployment = candidate['deployment'] - details = [] + details = _adopt_environment(deployment, *environment) if environment else [] for key in new_keys: if key.startswith('mp'): mount = _mount(key, current[key], record['vmid']) @@ -122,8 +164,13 @@ def propose(record, config): deployment.setdefault('devices', []).append(device) kind = 'GPU' if gpu_devices.gpu_path(device['host_path']) else 'USB/Coral/NPU' details.append(f"{key}: {device['host_path']} ({kind})") + skipped = [*new_keys, *(ENVIRONMENT_KEYS if environment else ())] filtered = b'\n'.join(line for line in config.splitlines() - if not any(line.startswith(key.encode() + b': ') for key in new_keys)) + b'\n' + if not any(line.startswith(key.encode() + b': ') for key in skipped)) + if environment: + filtered += b''.join(b'\n' + line for line in record['observed']['config'].encode().splitlines() + if any(line.startswith(key.encode() + b': ') for key in ENVIRONMENT_KEYS)) + filtered += b'\n' for key, value in transaction.external_changes(record, filtered).items(): section, name = transaction.ADOPTABLE[key] target = deployment.setdefault(section, {}) if section else deployment diff --git a/oci/remote/oci_update_current.py b/oci/remote/oci_update_current.py index b57f7049..f4f94038 100644 --- a/oci/remote/oci_update_current.py +++ b/oci/remote/oci_update_current.py @@ -260,6 +260,9 @@ def update(vmid, acknowledge_external_data=False, proposal=None, keep_backup=Non transaction.apply(instances.ROOT, vmid, archive, operation, proposal=proposal, registry_digest=digest, acknowledge_external_data=acknowledge_external_data, keep_backup=file_storage) + marker = os.environ.get('OCI_RECREATED_MARKER') + if marker: + Path(marker).touch() def main(): diff --git a/oci/src/proxmenux_oci/management.py b/oci/src/proxmenux_oci/management.py index 27ee09ad..2744032d 100644 --- a/oci/src/proxmenux_oci/management.py +++ b/oci/src/proxmenux_oci/management.py @@ -441,11 +441,12 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): return False if adoption: summary = '\n'.join(adoption['details']) - if not ui.review( - f"{translate('These Proxmox resources were added outside ProxMenux:')}\n\n{summary}\n\n" - + translate('They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.'), + # Variables reordered in Proxmox change nothing that needs a decision. + if summary and not ui.review( + f"{translate('These changes were made in Proxmox outside ProxMenux:')}\n\n{summary}\n\n" + + translate('They will be saved in oci-compose before continuing.'), translate('Review external OCI changes'), - question=translate('Include these resources in oci-compose?'), default=False): + question=translate('Include these changes in oci-compose?'), default=False): return False try: record = reconcile.commit(instances.ROOT, row['vmid'], adoption) @@ -472,15 +473,24 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()): if '--acknowledge-external-data' not in command: command.append('--acknowledge-external-data') title = translate('Modify OCI') if proposal else translate('Update OCI') - if proposal is None: - completed = _run_lifecycle(command, title) - else: - # Saved environment/secrets must never be passed on the command line. - with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as file: - json.dump(proposal, file) - file.flush() - completed = _run_lifecycle(command + ['--proposal', file.name], title) - if completed and not getattr(ui, 'unattended', False): + # The downloaded image is only worth mentioning when a new container + # was created from it, not after a change applied to the running one. + with tempfile.TemporaryDirectory() as folder: + marker = Path(folder) / 'recreated' + os.environ['OCI_RECREATED_MARKER'] = str(marker) + try: + if proposal is None: + completed = _run_lifecycle(command, title) + else: + # Saved environment/secrets must never be passed on the command line. + with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as file: + json.dump(proposal, file) + file.flush() + completed = _run_lifecycle(command + ['--proposal', file.name], title) + finally: + os.environ.pop('OCI_RECREATED_MARKER', None) + recreated = marker.exists() + if completed and recreated and not getattr(ui, 'unattended', False): images.offer_removal(ui, [row['vmid']]) return completed action = ui.choose(translate('Interrupted operation'), [('status', translate('View status')), diff --git a/oci/tests/test_instance_reconcile.py b/oci/tests/test_instance_reconcile.py index b0fb25a1..bd9da868 100644 --- a/oci/tests/test_instance_reconcile.py +++ b/oci/tests/test_instance_reconcile.py @@ -155,5 +155,66 @@ class ProposalTests(unittest.TestCase): self.assertEqual(json.loads(history[0].read_text())['deployment']['mounts'], []) +class EnvironmentAdoptionTests(unittest.TestCase): + marker = '11111111-1111-1111-1111-111111111111' + + def record(self, env): + config = f'description: proxmenux-instance={self.marker}\nenv: {env}\n'.encode() + return { + 'vmid': 200, 'status': 'installed', 'installation_id': self.marker, + 'deployment': {'mounts': [], 'devices': [], 'environment': [ + {'name': 'APP_PASSWORD', 'value': 'first-secret', 'sensitive': True}]}, + 'observed': {'config': config.decode(), 'config_sha256': sha(config), + 'archive_path': '/unused.tar', 'resolved_registry_digest': 'sha256:test', + 'image': {'manifest_digest': 'sha256:test', 'defaults': {'Env': ['PATH=/bin']}}}, + } + + def propose(self, record, env): + config = f'description: proxmenux-instance={self.marker}\nenv: {env}\n'.encode() + with (patch.object(reconcile.instances, 'ROOT', Path('/inert')), + patch.object(reconcile.instances, 'identity', return_value=self.marker), + patch.object(reconcile.instances, 'location', return_value=Path(__file__)), + patch.object(reconcile.gpu_devices, 'capture', return_value={}), + patch.object(reconcile.host_mounts, 'capture_sources', return_value={}), + patch.object(reconcile.transaction, 'preflight')): + return reconcile.propose(record, config) + + def test_a_password_changed_in_proxmox_is_adopted_without_showing_it(self): + record = self.record('PATH=/bin\0APP_PASSWORD=first-secret') + proposal = self.propose(record, 'PATH=/bin\0APP_PASSWORD=my-own-secret\0TZ=Europe/Madrid') + environment = proposal['candidate']['deployment']['environment'] + self.assertEqual(environment, [ + {'name': 'APP_PASSWORD', 'value': 'my-own-secret', 'sensitive': True}, + {'name': 'TZ', 'value': 'Europe/Madrid', 'sensitive': True}]) + self.assertEqual(proposal['details'], ['env APP_PASSWORD: value changed in Proxmox', + 'env TZ: added in Proxmox']) + self.assertNotIn('my-own-secret', str(proposal['details'])) + self.assertEqual(record['deployment']['environment'][0]['value'], 'first-secret') + + def test_reordered_variables_need_no_decision(self): + record = self.record('PATH=/bin\0APP_PASSWORD=first-secret') + proposal = self.propose(record, 'APP_PASSWORD=first-secret\0PATH=/bin') + self.assertEqual(proposal['details'], []) + + def test_removing_an_image_default_is_refused(self): + record = self.record('PATH=/bin\0APP_PASSWORD=first-secret') + with self.assertRaisesRegex(ValueError, 'env'): + self.propose(record, 'APP_PASSWORD=first-secret') + + def test_removing_a_saved_variable_removes_it_from_the_contract(self): + record = self.record('PATH=/bin\0APP_PASSWORD=first-secret') + proposal = self.propose(record, 'PATH=/bin') + self.assertEqual(proposal['candidate']['deployment']['environment'], []) + self.assertEqual(proposal['details'], ['env APP_PASSWORD: removed in Proxmox']) + + def test_other_changes_next_to_the_variables_are_still_refused(self): + record = self.record('PATH=/bin') + config = (f'description: proxmenux-instance={self.marker}\nenv: PATH=/bin\0A=1\n' + 'net0: name=eth0,bridge=vmbr1\n').encode() + with (patch.object(reconcile.instances, 'identity', return_value=self.marker), + self.assertRaisesRegex(ValueError, 'net0')): + reconcile.propose(record, config) + + if __name__ == '__main__': unittest.main() diff --git a/oci/tests/test_modify_menu.py b/oci/tests/test_modify_menu.py index 601c588e..0f80a0a7 100644 --- a/oci/tests/test_modify_menu.py +++ b/oci/tests/test_modify_menu.py @@ -169,12 +169,13 @@ class ModifyMenuTests(unittest.TestCase): class ModifyDispatchTests(unittest.TestCase): - def call_manager(self, proposal, lifecycle): + def call_manager(self, proposal, lifecycle, ui=None, offer=None): import oci_instances import oci_instance_reconcile saved = record() row = {'vmid': 115, 'reason': 'matched', 'stack': False, 'pending': False, 'status': 'installed'} - with patch.object(management, 'carry_records', return_value={}), \ + with patch.object(management.images, 'offer_removal', offer or Mock()), \ + patch.object(management, 'carry_records', return_value={}), \ patch.object(management, 'check_selected', return_value=row), \ patch.object(management, '_work_backup_ready', return_value=True), \ patch.object(oci_instances, 'read', return_value=saved), \ @@ -182,7 +183,7 @@ class ModifyDispatchTests(unittest.TestCase): patch.object(oci_instance_reconcile, 'propose', return_value=None), \ patch.object(recreation, 'edit_recreation', return_value=proposal), \ patch.object(management, '_run_lifecycle', side_effect=lifecycle): - return management.manage_instance(ROOT, Mock(), row, action='modify') + return management.manage_instance(ROOT, ui or Mock(), row, action='modify') def test_cancel_does_not_start_a_lifecycle_transaction(self): lifecycle = Mock() @@ -203,6 +204,20 @@ class ModifyDispatchTests(unittest.TestCase): self.assertTrue(self.call_manager(proposal, lifecycle)) self.assertEqual(seen, [proposal]) + def test_the_image_question_only_follows_a_recreated_container(self): + proposal = {'operation': 'recreate', 'candidate': record(), 'base_config_sha256': 'fingerprint'} + ui = Mock(unattended=False) + for recreated in (False, True): + with self.subTest(recreated=recreated): + def lifecycle(command, title): + if recreated: + Path(management.os.environ['OCI_RECREATED_MARKER']).touch() + return True + offer = Mock() + self.assertTrue(self.call_manager(proposal, lifecycle, ui, offer)) + self.assertEqual(offer.called, recreated) + self.assertNotIn('OCI_RECREATED_MARKER', management.os.environ) + if __name__ == '__main__': unittest.main() diff --git a/tests/lxc_updates/test_observed_state_ui.cjs b/tests/lxc_updates/test_observed_state_ui.cjs index 7b659bed..4f2de690 100644 --- a/tests/lxc_updates/test_observed_state_ui.cjs +++ b/tests/lxc_updates/test_observed_state_ui.cjs @@ -162,8 +162,10 @@ assert.equal(ui.osStatus({ ...recent, available: false, count: 0 }).osUpdateStat assert.equal(ui.osStatus({ ...recent, error: "probe failed" }).osUpdateStatusKnown, false) assert.equal(ui.aggregate({ app_watches: [{ update_available: true, name: "App" }] }).last_check, null, "an app badge cannot invent an OS check timestamp") -assert.equal(ui.badge(legacy).props.children[0].type, "Clock") -assert.equal(ui.badge(legacy).props.children[1], "vmLxc.updates.checkStale") +assert.equal(ui.badge(legacy).props.children[0].type, "Arrow", + "an old check keeps the same badge as a recent one") +assert.equal(ui.badge(legacy).props.children[1], 51) +assert.equal(ui.badge({ ...legacy, available: false, count: 0 }), null) assert.equal(ui.badge({ ...recent, available: true, count: 2 }).props.children[1], 2) assert.equal(ui.badge({ ...recent, available: false, count: 0 }), null) assert.equal(requests.length, 0) From 2aa6e0ba4470ce522cdea01252599e013c5bdaee Mon Sep 17 00:00:00 2001 From: ProxMenuxBot Date: Sun, 18 Oct 2026 09:41:55 +0000 Subject: [PATCH 42/53] chore(lang): auto-rebuild translation cache Source: e3c9162 Triggered by: push --- lang/de.json | 6 ++++++ lang/fr.json | 6 ++++++ lang/it.json | 6 ++++++ lang/pt.json | 6 ++++++ lang/sk.json | 6 ++++++ lang/sv.json | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/lang/de.json b/lang/de.json index 9bc18d14..6b118ceb 100644 --- a/lang/de.json +++ b/lang/de.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "Während des VM-Starts ist kein Zugriff auf das Gerät möglich", "Inactive": "Inaktiv", "Inactive (entry in fstab, not currently mounted)": "Inaktiv (Eintrag in fstab, derzeit nicht gemountet)", + "Include these changes in oci-compose?": "Diese Änderungen in oci-compose einschließen?", "Include these resources in oci-compose?": "Diese Ressourcen in oci-compose einschließen?", "Includes /etc/network (may drop SSH immediately)": "Enthält /etc/network (kann SSH sofort löschen)", "Includes /etc/zfs": "Enthält /etc/zfs", @@ -6805,6 +6806,7 @@ "These Proxmox resources were added outside ProxMenux:": "Diese Proxmox-Ressourcen wurden außerhalb von ProxMenux hinzugefügt:", "These VMIDs are not free:": "Diese VMIDs sind nicht kostenlos:", "These are the changes that will be made": "Dies sind die Änderungen, die vorgenommen werden", + "These changes were made in Proxmox outside ProxMenux:": "Diese Änderungen wurden in Proxmox außerhalb von ProxMenux vorgenommen:", "These container mount paths have an extension-like suffix:": "Diese Einhängepfade im Container haben eine Endung, die einer Dateierweiterung ähnelt:", "These containers were restored from a backup and this host has no record of their OCI application:": "Diese Container wurden aus einem Backup wiederhergestellt und dieser Host hat keinen Datensatz ihrer OCI-Anwendung:", "These host backups run with this job and expect its storage:": "Diese Host-Backups werden mit diesem Job ausgeführt und erwarten dessen Speicherung:", @@ -6816,6 +6818,7 @@ "These values are asked during the installation:": "Diese Werte werden während der Installation abgefragt:", "They are described in the image documentation:": "Sie sind in der Dokumentation des Images beschrieben:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Sie werden zu oci-compose hinzugefügt, bevor Sie fortfahren. Nicht unterstützte oder geänderte Ressourcen werden nicht importiert.", + "They will be saved in oci-compose before continuing.": "Sie werden in oci-compose gespeichert, bevor Sie fortfahren.", "This CIFS share is mounted with restrictive permissions.": "Diese CIFS-Freigabe wird mit restriktiven Berechtigungen gemountet.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Diese GPU gilt als inkompatibel mit GPU-Passthrough zu einer VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Diese NFS-Freigabe ist vollständig eingeschränkt – selbst der Host-Root kann nicht darauf schreiben.", @@ -7670,6 +7673,7 @@ "active VF(s)": "aktive VF(s)", "active VFs": "aktive VFs", "active Virtual Functions. Changing its driver binding would destroy every VF.": "aktive virtuelle Funktionen. Eine Änderung der Treiberbindung würde jedes VF zerstören.", + "added in Proxmox": "in Proxmox hinzugefügt", "after copy; source tree is incomplete.": "nach Kopie; Der Quellbaum ist unvollständig.", "after update.": "nach dem Update.", "all guests": "alle Gäste", @@ -8037,6 +8041,7 @@ "remapped users": "Benutzer neu zugeordnet", "remove the (now-empty) directory if possible": "Entfernen Sie nach Möglichkeit das (jetzt leere) Verzeichnis", "removed from Proxmox": "aus Proxmox entfernt", + "removed in Proxmox": "in Proxmox entfernt", "removed successfully from Proxmox.": "erfolgreich aus Proxmox entfernt.", "requires running the official installer, which restarts the Monitor service.": "erfordert die Ausführung des offiziellen Installationsprogramms, das den Monitor-Dienst neu startet.", "requires the package": "erfordert das Paket", @@ -8140,6 +8145,7 @@ "using": "verwenden", "using direct path (not guaranteed to survive reboots).": "Verwenden des direkten Pfads (keine Garantie, dass Neustarts überlebt werden).", "vCPUs:": "vCPUs:", + "value changed in Proxmox": "Wert in Proxmox geändert", "vfio-pci IDs configured": "vfio-pci-IDs konfiguriert", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "vfio-pci-IDs in /etc/modprobe.d/vfio.conf", "video": "Video", diff --git a/lang/fr.json b/lang/fr.json index 6b828c4a..fd53b380 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "Périphérique inaccessible lors du démarrage de la VM", "Inactive": "Inactif", "Inactive (entry in fstab, not currently mounted)": "Inactif (entrée dans fstab, non monté actuellement)", + "Include these changes in oci-compose?": "inclure ces modifications dans oci-compose ?", "Include these resources in oci-compose?": "inclure ces ressources dans oci-compose ?", "Includes /etc/network (may drop SSH immediately)": "Inclut /etc/network (peut supprimer SSH immédiatement)", "Includes /etc/zfs": "Inclut /etc/zfs", @@ -6804,6 +6805,7 @@ "These Proxmox resources were added outside ProxMenux:": "Ces ressources Proxmox ont été ajoutées en dehors de ProxMenux :", "These VMIDs are not free:": "Ces VMID ne sont pas libres:", "These are the changes that will be made": "Ce sont les changements qui seront apportés", + "These changes were made in Proxmox outside ProxMenux:": "Ces modifications ont été apportées dans Proxmox en dehors de ProxMenux :", "These container mount paths have an extension-like suffix:": "Ces chemins de montage du conteneur ont un suffixe qui ressemble à une extension :", "These containers were restored from a backup and this host has no record of their OCI application:": "Ces conteneurs ont été restaurés à partir d'une sauvegarde et cet hôte n'a aucun enregistrement de leur application OCI :", "These host backups run with this job and expect its storage:": "Ces sauvegardes d'hôte s'exécutent avec ce travail et attendent son stockage :", @@ -6815,6 +6817,7 @@ "These values are asked during the installation:": "Ces valeurs sont demandées lors de l'installation:", "They are described in the image documentation:": "Ils sont décrits dans la documentation de l'image :", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Ils seront ajoutés à oci-compose avant de continuer. Les ressources non prises en charge ou modifiées ne sont pas importées.", + "They will be saved in oci-compose before continuing.": "Ils seront enregistrés dans oci-compose avant de continuer.", "This CIFS share is mounted with restrictive permissions.": "Ce partage CIFS est monté avec des autorisations restrictives.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Ce GPU est considéré comme incompatible avec le relais GPU vers une VM dans ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Ce partage NFS est entièrement restreint : même la racine de l'hôte ne peut pas y écrire.", @@ -7669,6 +7672,7 @@ "active VF(s)": "FV active(s)", "active VFs": "VF actives", "active Virtual Functions. Changing its driver binding would destroy every VF.": "Fonctions virtuelles actives. Changer la liaison de son pilote détruirait chaque VF.", + "added in Proxmox": "ajouté dans Proxmox", "after copy; source tree is incomplete.": "après copie ; l'arborescence des sources est incomplète.", "after update.": "après mise à jour.", "all guests": "tous les invités", @@ -8036,6 +8040,7 @@ "remapped users": "utilisateurs remappés", "remove the (now-empty) directory if possible": "supprimez le répertoire (maintenant vide) si possible", "removed from Proxmox": "supprimé de Proxmox", + "removed in Proxmox": "supprimé dans Proxmox", "removed successfully from Proxmox.": "supprimé avec succès de Proxmox.", "requires running the official installer, which restarts the Monitor service.": "nécessite l'exécution du programme d'installation officiel, qui redémarre le service Monitor.", "requires the package": "nécessite le paquet", @@ -8139,6 +8144,7 @@ "using": "en utilisant", "using direct path (not guaranteed to survive reboots).": "en utilisant un chemin direct (il n'est pas garanti de survivre aux redémarrages).", "vCPUs:": "Processeurs virtuels :", + "value changed in Proxmox": "valeur modifiée dans Proxmox", "vfio-pci IDs configured": "ID vfio-pci configurés", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "ID vfio-pci dans /etc/modprobe.d/vfio.conf", "video": "vidéo", diff --git a/lang/it.json b/lang/it.json index e589c0d1..53b3d1b6 100644 --- a/lang/it.json +++ b/lang/it.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "Dispositivo inaccessibile durante l'avvio della VM", "Inactive": "Inattivo", "Inactive (entry in fstab, not currently mounted)": "Inattivo (presente in fstab, attualmente non montato)", + "Include these changes in oci-compose?": "includere queste modifiche in oci-compose?", "Include these resources in oci-compose?": "Includere queste risorse in oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Include /etc/network (potrebbe interrompere immediatamente la connessione SSH)", "Includes /etc/zfs": "Include /etc/zfs", @@ -6805,6 +6806,7 @@ "These Proxmox resources were added outside ProxMenux:": "Queste risorse Proxmox sono state aggiunte fuori da ProxMenux:", "These VMIDs are not free:": "Questi VMID non sono disponibili:", "These are the changes that will be made": "Verranno apportate queste modifiche", + "These changes were made in Proxmox outside ProxMenux:": "queste modifiche sono state apportate in Proxmox all'esterno di ProxMenux:", "These container mount paths have an extension-like suffix:": "Questi percorsi di mount del container hanno un suffisso simile a un'estensione:", "These containers were restored from a backup and this host has no record of their OCI application:": "Questi container sono stati ripristinati da un backup e questo host non ha il record della loro applicazione OCI:", "These host backups run with this job and expect its storage:": "Questi backup dell’host vengono eseguiti con questo job e richiedono il suo storage:", @@ -6816,6 +6818,7 @@ "These values are asked during the installation:": "Questi valori verranno richiesti durante l’installazione:", "They are described in the image documentation:": "Sono descritti nella documentazione dell'immagine:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Se confermi e la verifica finale riesce, queste risorse verranno registrate in oci-compose prima di proseguire. Le risorse non supportate o modificate non vengono acquisite.", + "They will be saved in oci-compose before continuing.": "verranno salvati in oci-compose prima di continuare.", "This CIFS share is mounted with restrictive permissions.": "Questa condivisione CIFS è montata con autorizzazioni restrittive.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Questa GPU è considerata incompatibile con il passthrough GPU a una VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Questa condivisione NFS è completamente limitata: nemmeno l'utente root dell'host può scrivervi.", @@ -7670,6 +7673,7 @@ "active VF(s)": "VF attive", "active VFs": "VF attive", "active Virtual Functions. Changing its driver binding would destroy every VF.": "funzioni virtuali attive. La modifica del collegamento del driver distruggerebbe ogni VF.", + "added in Proxmox": "aggiunto in Proxmox", "after copy; source tree is incomplete.": "dopo la copia; l'albero dei sorgenti è incompleto.", "after update.": "dopo l'aggiornamento.", "all guests": "tutti i guest", @@ -8037,6 +8041,7 @@ "remapped users": "utenti rimappati", "remove the (now-empty) directory if possible": "rimuovere la directory (ora vuota) se possibile", "removed from Proxmox": "rimosso da Proxmox", + "removed in Proxmox": "rimosso in Proxmox", "removed successfully from Proxmox.": "rimosso con successo da Proxmox.", "requires running the official installer, which restarts the Monitor service.": "richiede l'esecuzione del programma di installazione ufficiale, che riavvia il servizio Monitor.", "requires the package": "richiede il pacchetto", @@ -8140,6 +8145,7 @@ "using": "utilizzando", "using direct path (not guaranteed to survive reboots).": "utilizzando il percorso diretto (non è garantito che sopravviva ai riavvii).", "vCPUs:": "vCPU:", + "value changed in Proxmox": "valore modificato in Proxmox", "vfio-pci IDs configured": "ID vfio-pci configurati", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "ID vfio-pci in /etc/modprobe.d/vfio.conf", "video": "video", diff --git a/lang/pt.json b/lang/pt.json index 0710e044..5e33e3a1 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "Dispositivo inacessível durante a inicialização da VM", "Inactive": "Inativo", "Inactive (entry in fstab, not currently mounted)": "Inativo (entrada no fstab, não montada no momento)", + "Include these changes in oci-compose?": "Incluir essas alterações no oci-compose?", "Include these resources in oci-compose?": "Incluir esses recursos no oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Inclui /etc/network (pode descartar o SSH imediatamente)", "Includes /etc/zfs": "Inclui /etc/zfs", @@ -6805,6 +6806,7 @@ "These Proxmox resources were added outside ProxMenux:": "Estes recursos Proxmox foram adicionados fora do ProxMenux:", "These VMIDs are not free:": "Estes VMIDs não são gratuitos:", "These are the changes that will be made": "Estas são as mudanças que serão feitas", + "These changes were made in Proxmox outside ProxMenux:": "Essas alterações foram feitas no Proxmox fora do ProxMenux:", "These container mount paths have an extension-like suffix:": "Estes caminhos de montagem do contêiner têm um sufixo parecido com uma extensão:", "These containers were restored from a backup and this host has no record of their OCI application:": "Esses contêineres foram restaurados a partir de um backup e este host não possui registro de seu aplicativo OCI:", "These host backups run with this job and expect its storage:": "Esses backups de host são executados com este trabalho e esperam seu armazenamento:", @@ -6816,6 +6818,7 @@ "These values are asked during the installation:": "Estes valores são questionados durante a instalação:", "They are described in the image documentation:": "Estão descritos na documentação da imagem:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Eles serão adicionados ao oci-compose antes de continuar. Recursos não suportados ou alterados não são importados.", + "They will be saved in oci-compose before continuing.": "Eles serão salvos em oci-compose antes de continuar.", "This CIFS share is mounted with restrictive permissions.": "Este compartilhamento CIFS é montado com permissões restritivas.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU é considerada incompatível com a passagem de GPU para uma VM no ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este compartilhamento NFS é totalmente restrito — até mesmo o host root não pode gravar nele.", @@ -7670,6 +7673,7 @@ "active VF(s)": "FV ativa(s)", "active VFs": "FVs ativas", "active Virtual Functions. Changing its driver binding would destroy every VF.": "Funções virtuais ativas. Alterar a ligação do driver destruiria todos os VF.", + "added in Proxmox": "adicionado em Proxmox", "after copy; source tree is incomplete.": "após cópia; a árvore de origem está incompleta.", "after update.": "após a atualização.", "all guests": "todos os convidados", @@ -8037,6 +8041,7 @@ "remapped users": "usuários remapeados", "remove the (now-empty) directory if possible": "remova o diretório (agora vazio), se possível", "removed from Proxmox": "removido do Proxmox", + "removed in Proxmox": "removido em Proxmox", "removed successfully from Proxmox.": "removido com sucesso do Proxmox.", "requires running the official installer, which restarts the Monitor service.": "requer a execução do instalador oficial, que reinicia o serviço Monitor.", "requires the package": "requer o pacote", @@ -8140,6 +8145,7 @@ "using": "usando", "using direct path (not guaranteed to survive reboots).": "usando caminho direto (não é garantido que sobreviva às reinicializações).", "vCPUs:": "vCPUs:", + "value changed in Proxmox": "valor alterado em Proxmox", "vfio-pci IDs configured": "IDs vfio-pci configurados", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "IDs vfio-pci em /etc/modprobe.d/vfio.conf", "video": "vídeo", diff --git a/lang/sk.json b/lang/sk.json index 7c3d7a7c..63a88682 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "nedostupné zariadenie počas štartu VM", "Inactive": "Neaktívne", "Inactive (entry in fstab, not currently mounted)": "Neaktívne (záznam je vo fstab, ale teraz nie je pripojený)", + "Include these changes in oci-compose?": "Zahrnúť tieto zmeny do oci-compose?", "Include these resources in oci-compose?": "Zahrnúť tieto zdroje do oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Obsahuje /etc/network (môže okamžite prerušiť SSH)", "Includes /etc/zfs": "Obsahuje /etc/zfs", @@ -6806,6 +6807,7 @@ "These Proxmox resources were added outside ProxMenux:": "Tieto zdroje Proxmox boli pridané mimo ProxMenux:", "These VMIDs are not free:": "Tieto VMID nie sú zadarmo:", "These are the changes that will be made": "Tieto zmeny sa vykonajú", + "These changes were made in Proxmox outside ProxMenux:": "Tieto zmeny boli vykonané v Proxmox mimo ProxMenux:", "These container mount paths have an extension-like suffix:": "Tieto cesty pripojenia v kontajneri majú príponu podobnú koncovke súboru:", "These containers were restored from a backup and this host has no record of their OCI application:": "Tieto kontajnery boli obnovené zo zálohy a tento hostiteľ nemá žiadny záznam o ich aplikácii OCI:", "These host backups run with this job and expect its storage:": "Tieto zálohy hostiteľa sa spúšťajú s touto úlohou a očakávajú jej uloženie:", @@ -6817,6 +6819,7 @@ "These values are asked during the installation:": "Tieto hodnoty sa požadujú počas inštalácie:", "They are described in the image documentation:": "Ich popis nájdete v dokumentácii image:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Pred pokračovaním budú pridané do oci-compose. Nepodporované alebo zmenené zdroje sa neimportujú.", + "They will be saved in oci-compose before continuing.": "Pred pokračovaním budú uložené v oci-compose.", "This CIFS share is mounted with restrictive permissions.": "Toto CIFS zdieľanie je pripojené s obmedzujúcimi oprávneniami.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Táto GPU sa v ProxMenux považuje za nekompatibilnú s GPU passthrough do VM.", "This NFS share is fully restricted — even the host root cannot write to it.": "Toto NFS zdieľanie je úplne obmedzené - ani root na hostovi doň nevie zapisovať.", @@ -7671,6 +7674,7 @@ "active VF(s)": "aktívne VF", "active VFs": "aktívnymi VF", "active Virtual Functions. Changing its driver binding would destroy every VF.": "aktívnymi Virtual Functions. Zmena naviazania ovládača by zrušila každú VF.", + "added in Proxmox": "pridané v Proxmox", "after copy; source tree is incomplete.": "po kopírovaní; zdrojový strom je neúplný.", "after update.": "po aktualizácii.", "all guests": "všetci hostia", @@ -8038,6 +8042,7 @@ "remapped users": "remapovanými používateľmi", "remove the (now-empty) directory if possible": "odstrániť teraz prázdny priečinok, ak to bude možné", "removed from Proxmox": "odstránené z Proxmoxu", + "removed in Proxmox": "odstránené v Proxmox", "removed successfully from Proxmox.": "úspešne odstránené z Proxmoxu.", "requires running the official installer, which restarts the Monitor service.": "vyžaduje spustenie oficiálneho inštalátora, ktorý reštartuje službu Monitor.", "requires the package": "vyžaduje balík", @@ -8141,6 +8146,7 @@ "using": "cez", "using direct path (not guaranteed to survive reboots).": "používa sa priama cesta (nie je zaručené, že prežije reštarty).", "vCPUs:": "vCPU:", + "value changed in Proxmox": "hodnota zmenená v Proxmox", "vfio-pci IDs configured": "vfio-pci ID nastavené", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "vfio-pci ID v /etc/modprobe.d/vfio.conf", "video": "video", diff --git a/lang/sv.json b/lang/sv.json index 8a5e64aa..c06a631e 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -2991,6 +2991,7 @@ "Inaccessible device during VM startup": "Otillgänglig enhet under VM-start", "Inactive": "Inaktiv", "Inactive (entry in fstab, not currently mounted)": "Inaktiv (post i fstab, inte för närvarande monterad)", + "Include these changes in oci-compose?": "Inkludera dessa ändringar i oci-compose?", "Include these resources in oci-compose?": "Inkludera dessa resurser i oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Inkluderar /etc/network (kan släppa SSH omedelbart)", "Includes /etc/zfs": "Inkluderar /etc/zfs", @@ -6805,6 +6806,7 @@ "These Proxmox resources were added outside ProxMenux:": "Dessa Proxmox-resurser lades till utanför ProxMenux:", "These VMIDs are not free:": "Dessa VMID är inte gratis:", "These are the changes that will be made": "Det är dessa ändringar som kommer att göras", + "These changes were made in Proxmox outside ProxMenux:": "Dessa ändringar gjordes i Proxmox utanför ProxMenux:", "These container mount paths have an extension-like suffix:": "De här monteringssökvägarna i containern har ett suffix som liknar ett filtillägg:", "These containers were restored from a backup and this host has no record of their OCI application:": "Dessa behållare återställdes från en säkerhetskopia och denna värd har ingen post för deras OCI-applikation:", "These host backups run with this job and expect its storage:": "Dessa värdsäkerhetskopior körs med det här jobbet och förväntar sig dess lagring:", @@ -6816,6 +6818,7 @@ "These values are asked during the installation:": "Dessa värden tillfrågas under installationen:", "They are described in the image documentation:": "De beskrivs i bilddokumentationen:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "De kommer att läggas till oci-compose innan du fortsätter. Resurser som inte stöds eller ändras importeras inte.", + "They will be saved in oci-compose before continuing.": "De kommer att sparas i oci-compose innan du fortsätter.", "This CIFS share is mounted with restrictive permissions.": "Denna CIFS-andel är monterad med restriktiva behörigheter.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Denna GPU anses vara inkompatibel med GPU-genomföring till en virtuell dator i ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Denna NFS-resurs är helt begränsad — även värdroten kan inte skriva till den.", @@ -7670,6 +7673,7 @@ "active VF(s)": "aktiva VF(s)", "active VFs": "aktiva VF:er", "active Virtual Functions. Changing its driver binding would destroy every VF.": "aktiva virtuella funktioner. Att ändra dess drivrutinsbindning skulle förstöra varje VF.", + "added in Proxmox": "lagt till i Proxmox", "after copy; source tree is incomplete.": "efter kopia; källträdet är ofullständigt.", "after update.": "efter uppdatering.", "all guests": "alla gäster", @@ -8037,6 +8041,7 @@ "remapped users": "ommappade användare", "remove the (now-empty) directory if possible": "ta bort den (nu tomma) katalogen om möjligt", "removed from Proxmox": "borttagen från Proxmox", + "removed in Proxmox": "borttagen i Proxmox", "removed successfully from Proxmox.": "har tagits bort från Proxmox.", "requires running the official installer, which restarts the Monitor service.": "kräver att det officiella installationsprogrammet körs, vilket startar om Monitor-tjänsten.", "requires the package": "kräver paketet", @@ -8140,6 +8145,7 @@ "using": "använder", "using direct path (not guaranteed to survive reboots).": "använder direkt sökväg (inte garanterat att överleva omstarter).", "vCPUs:": "vCPU:er:", + "value changed in Proxmox": "värde ändrat i Proxmox", "vfio-pci IDs configured": "vfio-pci ID:n konfigurerade", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "vfio-pci ID i /etc/modprobe.d/vfio.conf", "video": "video", From 9861a85ccd3c8f29df4abc22f6b9ea9efee28377 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 18 Oct 2026 11:49:48 +0200 Subject: [PATCH 43/53] Name the overlapping mount paths and fix new OCI review translations --- lang/fr.json | 8 ++++---- lang/it.json | 10 +++++----- lang/pt.json | 8 ++++---- oci/remote/oci_instance_transaction.py | 7 ++++--- oci/tests/test_instance_reconcile.py | 13 +++++++++++++ 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/lang/fr.json b/lang/fr.json index fd53b380..4a83f430 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -2991,7 +2991,7 @@ "Inaccessible device during VM startup": "Périphérique inaccessible lors du démarrage de la VM", "Inactive": "Inactif", "Inactive (entry in fstab, not currently mounted)": "Inactif (entrée dans fstab, non monté actuellement)", - "Include these changes in oci-compose?": "inclure ces modifications dans oci-compose ?", + "Include these changes in oci-compose?": "Inclure ces modifications dans oci-compose ?", "Include these resources in oci-compose?": "inclure ces ressources dans oci-compose ?", "Includes /etc/network (may drop SSH immediately)": "Inclut /etc/network (peut supprimer SSH immédiatement)", "Includes /etc/zfs": "Inclut /etc/zfs", @@ -6817,7 +6817,7 @@ "These values are asked during the installation:": "Ces valeurs sont demandées lors de l'installation:", "They are described in the image documentation:": "Ils sont décrits dans la documentation de l'image :", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Ils seront ajoutés à oci-compose avant de continuer. Les ressources non prises en charge ou modifiées ne sont pas importées.", - "They will be saved in oci-compose before continuing.": "Ils seront enregistrés dans oci-compose avant de continuer.", + "They will be saved in oci-compose before continuing.": "Elles seront enregistrées dans oci-compose avant de continuer.", "This CIFS share is mounted with restrictive permissions.": "Ce partage CIFS est monté avec des autorisations restrictives.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Ce GPU est considéré comme incompatible avec le relais GPU vers une VM dans ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Ce partage NFS est entièrement restreint : même la racine de l'hôte ne peut pas y écrire.", @@ -7672,7 +7672,7 @@ "active VF(s)": "FV active(s)", "active VFs": "VF actives", "active Virtual Functions. Changing its driver binding would destroy every VF.": "Fonctions virtuelles actives. Changer la liaison de son pilote détruirait chaque VF.", - "added in Proxmox": "ajouté dans Proxmox", + "added in Proxmox": "ajoutée dans Proxmox", "after copy; source tree is incomplete.": "après copie ; l'arborescence des sources est incomplète.", "after update.": "après mise à jour.", "all guests": "tous les invités", @@ -8040,7 +8040,7 @@ "remapped users": "utilisateurs remappés", "remove the (now-empty) directory if possible": "supprimez le répertoire (maintenant vide) si possible", "removed from Proxmox": "supprimé de Proxmox", - "removed in Proxmox": "supprimé dans Proxmox", + "removed in Proxmox": "supprimée dans Proxmox", "removed successfully from Proxmox.": "supprimé avec succès de Proxmox.", "requires running the official installer, which restarts the Monitor service.": "nécessite l'exécution du programme d'installation officiel, qui redémarre le service Monitor.", "requires the package": "nécessite le paquet", diff --git a/lang/it.json b/lang/it.json index 53b3d1b6..1828d013 100644 --- a/lang/it.json +++ b/lang/it.json @@ -2991,7 +2991,7 @@ "Inaccessible device during VM startup": "Dispositivo inaccessibile durante l'avvio della VM", "Inactive": "Inattivo", "Inactive (entry in fstab, not currently mounted)": "Inattivo (presente in fstab, attualmente non montato)", - "Include these changes in oci-compose?": "includere queste modifiche in oci-compose?", + "Include these changes in oci-compose?": "Includere queste modifiche in oci-compose?", "Include these resources in oci-compose?": "Includere queste risorse in oci-compose?", "Includes /etc/network (may drop SSH immediately)": "Include /etc/network (potrebbe interrompere immediatamente la connessione SSH)", "Includes /etc/zfs": "Include /etc/zfs", @@ -6806,7 +6806,7 @@ "These Proxmox resources were added outside ProxMenux:": "Queste risorse Proxmox sono state aggiunte fuori da ProxMenux:", "These VMIDs are not free:": "Questi VMID non sono disponibili:", "These are the changes that will be made": "Verranno apportate queste modifiche", - "These changes were made in Proxmox outside ProxMenux:": "queste modifiche sono state apportate in Proxmox all'esterno di ProxMenux:", + "These changes were made in Proxmox outside ProxMenux:": "Queste modifiche sono state apportate in Proxmox al di fuori di ProxMenux:", "These container mount paths have an extension-like suffix:": "Questi percorsi di mount del container hanno un suffisso simile a un'estensione:", "These containers were restored from a backup and this host has no record of their OCI application:": "Questi container sono stati ripristinati da un backup e questo host non ha il record della loro applicazione OCI:", "These host backups run with this job and expect its storage:": "Questi backup dell’host vengono eseguiti con questo job e richiedono il suo storage:", @@ -6818,7 +6818,7 @@ "These values are asked during the installation:": "Questi valori verranno richiesti durante l’installazione:", "They are described in the image documentation:": "Sono descritti nella documentazione dell'immagine:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Se confermi e la verifica finale riesce, queste risorse verranno registrate in oci-compose prima di proseguire. Le risorse non supportate o modificate non vengono acquisite.", - "They will be saved in oci-compose before continuing.": "verranno salvati in oci-compose prima di continuare.", + "They will be saved in oci-compose before continuing.": "Verranno salvate in oci-compose prima di continuare.", "This CIFS share is mounted with restrictive permissions.": "Questa condivisione CIFS è montata con autorizzazioni restrittive.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Questa GPU è considerata incompatibile con il passthrough GPU a una VM in ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Questa condivisione NFS è completamente limitata: nemmeno l'utente root dell'host può scrivervi.", @@ -7673,7 +7673,7 @@ "active VF(s)": "VF attive", "active VFs": "VF attive", "active Virtual Functions. Changing its driver binding would destroy every VF.": "funzioni virtuali attive. La modifica del collegamento del driver distruggerebbe ogni VF.", - "added in Proxmox": "aggiunto in Proxmox", + "added in Proxmox": "aggiunta in Proxmox", "after copy; source tree is incomplete.": "dopo la copia; l'albero dei sorgenti è incompleto.", "after update.": "dopo l'aggiornamento.", "all guests": "tutti i guest", @@ -8041,7 +8041,7 @@ "remapped users": "utenti rimappati", "remove the (now-empty) directory if possible": "rimuovere la directory (ora vuota) se possibile", "removed from Proxmox": "rimosso da Proxmox", - "removed in Proxmox": "rimosso in Proxmox", + "removed in Proxmox": "rimossa in Proxmox", "removed successfully from Proxmox.": "rimosso con successo da Proxmox.", "requires running the official installer, which restarts the Monitor service.": "richiede l'esecuzione del programma di installazione ufficiale, che riavvia il servizio Monitor.", "requires the package": "richiede il pacchetto", diff --git a/lang/pt.json b/lang/pt.json index 5e33e3a1..41fb2fd7 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -6818,7 +6818,7 @@ "These values are asked during the installation:": "Estes valores são questionados durante a instalação:", "They are described in the image documentation:": "Estão descritos na documentação da imagem:", "They will be added to oci-compose before continuing. Unsupported or changed resources are not imported.": "Eles serão adicionados ao oci-compose antes de continuar. Recursos não suportados ou alterados não são importados.", - "They will be saved in oci-compose before continuing.": "Eles serão salvos em oci-compose antes de continuar.", + "They will be saved in oci-compose before continuing.": "Elas serão salvas no oci-compose antes de continuar.", "This CIFS share is mounted with restrictive permissions.": "Este compartilhamento CIFS é montado com permissões restritivas.", "This GPU is considered incompatible with GPU passthrough to a VM in ProxMenux.": "Esta GPU é considerada incompatível com a passagem de GPU para uma VM no ProxMenux.", "This NFS share is fully restricted — even the host root cannot write to it.": "Este compartilhamento NFS é totalmente restrito — até mesmo o host root não pode gravar nele.", @@ -7673,7 +7673,7 @@ "active VF(s)": "FV ativa(s)", "active VFs": "FVs ativas", "active Virtual Functions. Changing its driver binding would destroy every VF.": "Funções virtuais ativas. Alterar a ligação do driver destruiria todos os VF.", - "added in Proxmox": "adicionado em Proxmox", + "added in Proxmox": "adicionada no Proxmox", "after copy; source tree is incomplete.": "após cópia; a árvore de origem está incompleta.", "after update.": "após a atualização.", "all guests": "todos os convidados", @@ -8041,7 +8041,7 @@ "remapped users": "usuários remapeados", "remove the (now-empty) directory if possible": "remova o diretório (agora vazio), se possível", "removed from Proxmox": "removido do Proxmox", - "removed in Proxmox": "removido em Proxmox", + "removed in Proxmox": "removida no Proxmox", "removed successfully from Proxmox.": "removido com sucesso do Proxmox.", "requires running the official installer, which restarts the Monitor service.": "requer a execução do instalador oficial, que reinicia o serviço Monitor.", "requires the package": "requer o pacote", @@ -8145,7 +8145,7 @@ "using": "usando", "using direct path (not guaranteed to survive reboots).": "usando caminho direto (não é garantido que sobreviva às reinicializações).", "vCPUs:": "vCPUs:", - "value changed in Proxmox": "valor alterado em Proxmox", + "value changed in Proxmox": "valor alterado no Proxmox", "vfio-pci IDs configured": "IDs vfio-pci configurados", "vfio-pci IDs in /etc/modprobe.d/vfio.conf": "IDs vfio-pci em /etc/modprobe.d/vfio.conf", "video": "vídeo", diff --git a/oci/remote/oci_instance_transaction.py b/oci/remote/oci_instance_transaction.py index a2d635ce..cc36b0d0 100644 --- a/oci/remote/oci_instance_transaction.py +++ b/oci/remote/oci_instance_transaction.py @@ -571,9 +571,10 @@ def preflight(record, candidate, config, coordinated=None, in_place=False, basel target = host_mounts.valid_path(mount['container_path']) if mount['type'] == 'host-bind' and mount['source'] in declared and mount.get('read_only'): continue - if (any(target == p or target.startswith(p.rstrip('/') + '/') - or p.startswith(target.rstrip('/') + '/') for p in paths)): - raise ValueError(translate('Mount paths must not overlap')) + other = next((p for p in paths if target == p or target.startswith(p.rstrip('/') + '/') + or p.startswith(target.rstrip('/') + '/')), None) + if other: + raise ValueError(f"{translate('Mount paths must not overlap')}: {target} - {other}") if mount['type'] == 'managed-volume': if mount.get('backup') is not True or not isinstance(mount.get('size_gb'), int) or mount['size_gb'] < 1: raise ValueError(translate('Managed disks must have backup enabled and a valid size')) diff --git a/oci/tests/test_instance_reconcile.py b/oci/tests/test_instance_reconcile.py index bd9da868..59cd4a3e 100644 --- a/oci/tests/test_instance_reconcile.py +++ b/oci/tests/test_instance_reconcile.py @@ -1,4 +1,5 @@ """Regression tests for explicit adoption of external OCI resources.""" +import copy import json from pathlib import Path import sys @@ -216,5 +217,17 @@ class EnvironmentAdoptionTests(unittest.TestCase): reconcile.propose(record, config) +class OverlapMessageTests(unittest.TestCase): + def test_the_refusal_names_both_paths(self): + from test_modify_review import installation + saved, config = installation() + candidate = copy.deepcopy(saved) + volume = {'type': 'managed-volume', 'source': 'local-lvm', 'size_gb': 1, 'backup': True, 'read_only': False} + candidate['deployment']['mounts'] = [dict(volume, container_path='/source'), + dict(volume, container_path='/source/archivos')] + with self.assertRaisesRegex(ValueError, '/source/archivos - /source$'): + reconcile.transaction.preflight(saved, candidate, config) + + if __name__ == '__main__': unittest.main() From 8757bbeb4fbf49b88498659c0b8af2d34570171a Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:05:11 +0200 Subject: [PATCH 44/53] Count merged-usr units once and accept guests without ss or netstat - /lib/systemd/system is skipped when it is the same directory as /usr/lib/systemd/system - A guest without ss or netstat has no listener evidence instead of an incomplete scan - Spanish messages for native discovery --- AppImage/messages/es/common.json | 10 +++++++++ AppImage/scripts/native_app_discovery.py | 5 ++++- .../tests/test_native_app_discovery.py | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index a9754ca6..02a4260b 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -1723,6 +1723,16 @@ "searchApplications": "Buscar aplicaciones", "searchingApplications": "Buscando aplicaciones…", "noApplicationsDetected": "No se han detectado aplicaciones.", + "nativeEvidencePort": "Puerto TCP a la escucha", + "nativeEvidenceFile": "Archivo instalado", + "nativeEvidenceProcess": "Ejecutable en marcha", + "nativeEvidenceDirectory": "Directorio de trabajo", + "nativeEvidenceService": "Definición del servicio", + "nativeScanFailed": "La búsqueda de aplicaciones no ha podido terminar. Lo detectado antes puede estar desactualizado; vuelve a intentarlo.", + "nativeScanPartial": "La búsqueda de aplicaciones está incompleta. Revisa lo que se ha encontrado y vuelve a intentarlo.", + "nativeVersionUnknown": "Versión instalada desconocida", + "nativeEvidenceStale": "Lo detectado puede estar desactualizado o incompleto.", + "nativeRegisterOnly": "Confirma el registro en el editor. Una versión conocida se guarda como valor manual; no se configura ningún actualizador.", "noNewApplicationsDetected": "No se han detectado aplicaciones nuevas.", "oneNewApplicationDetected": "Se ha detectado una aplicación nueva.", "newApplicationsDetected": "Se han detectado {count} aplicaciones nuevas.", diff --git a/AppImage/scripts/native_app_discovery.py b/AppImage/scripts/native_app_discovery.py index ac9daa9e..3ba83ec5 100644 --- a/AppImage/scripts/native_app_discovery.py +++ b/AppImage/scripts/native_app_discovery.py @@ -146,6 +146,8 @@ SERVICE_SCRIPT = r''' command -v awk >/dev/null && command -v head >/dev/null || exit 1 n=0 for dir in /etc/systemd/system /usr/lib/systemd/system /lib/systemd/system /etc/init.d; do + # With a merged /usr, /lib is /usr/lib: the same units must not count twice. + [ "$dir" = /lib/systemd/system ] && [ /lib/systemd/system -ef /usr/lib/systemd/system ] && continue [ ! -d "$dir" ] || [ -r "$dir" ] || exit 1 for f in "$dir"/*; do [ -f "$f" ] || continue @@ -204,7 +206,8 @@ elif command -v netstat >/dev/null; then pid=$7; sub(/\/.*$/,"",pid); if(pid ~ /^[0-9]+$/) printf "L\t%s\t%s\n",pid,port} END {print failed ? "FAILED" : (limited ? "LIMIT" : "END")}' else - exit 1 + # Without ss or netstat there is no listener evidence; a retry would not change it. + printf 'END\n' fi ''' HESTIA_PANEL_SCRIPT = r''' diff --git a/AppImage/scripts/tests/test_native_app_discovery.py b/AppImage/scripts/tests/test_native_app_discovery.py index 397bb3fc..dc24424a 100644 --- a/AppImage/scripts/tests/test_native_app_discovery.py +++ b/AppImage/scripts/tests/test_native_app_discovery.py @@ -590,6 +590,28 @@ class DiscoveryFixtures(unittest.TestCase): self.assertNotIn('SECRET', output) self.assertFalse(marker.exists()) + def test_merged_usr_units_are_counted_once(self): + usr = self.root / 'usr-units' + usr.mkdir() + (usr / 'handmade.service').write_text('[Service]\nExecStart=/opt/handmade/server\n') + lib = self.root / 'lib-units' + lib.symlink_to(usr) + check = '[ "$dir" = /lib/systemd/system ] && [ /lib/systemd/system -ef /usr/lib/systemd/system ]' + self.assertIn(check, native.SERVICE_SCRIPT) + script = (native.SERVICE_SCRIPT + .replace('/etc/systemd/system /usr/lib/systemd/system /lib/systemd/system /etc/init.d', f'{usr} {lib}') + .replace(check, f'[ "$dir" = {lib} ] && [ {lib} -ef {usr} ]')) + output = native.bounded_run(['sh', '-c', script], time.monotonic() + 3) + self.assertEqual(output.count('handmade.service'), 1) + + def test_a_guest_without_ss_or_netstat_has_no_listener_evidence(self): + bin_dir = self.root / 'no-tools' + bin_dir.mkdir() + (bin_dir / 'awk').symlink_to('/usr/bin/awk') + output = native.bounded_run(['sh', '-c', 'PATH=' + str(bin_dir) + '\n' + native.PORT_SCRIPT, 'fixture', '256'], + time.monotonic() + 3) + self.assertEqual(native.parse_records(output, 'L'), ([], False, 0)) + def test_actual_process_parser_skips_docker_and_never_reads_environ_cmdline(self): proc = self.root / 'proc' proc.mkdir() From e8b8f95c5b846358ada3ca868d4a43d2ea931534 Mon Sep 17 00:00:00 2001 From: ProxMenuxBot Date: Sun, 18 Oct 2026 10:15:05 +0000 Subject: [PATCH 45/53] chore(i18n): auto-fill missing translations in messages/{locale}/common.json Source: 1cbcdd6 Triggered by: push --- AppImage/messages/de/common.json | 12 +++++++++++- AppImage/messages/fr/common.json | 12 +++++++++++- AppImage/messages/it/common.json | 12 +++++++++++- AppImage/messages/pt/common.json | 12 +++++++++++- AppImage/messages/sv/common.json | 12 +++++++++++- 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index eb4fc2ec..6d9f2ee7 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -1737,7 +1737,17 @@ "portCategoryCustomPlaceholder": "Geben Sie eine Kategorie ein und drücken Sie Enter (Esc zum Abbrechen)", "portCustomUrlPlaceholder": "Benutzerdefinierte URL (z. B. https://vault.example.com) überschreibt IP:port", "stackMemberTitle": "Teil einer Multi-Container-Anwendung", - "stackMemberBody": "Dieser Container gehört zu einer Anwendung, deren Hauptcontainer {primary} ist. Die Anwendung, ihre Weblinks und ihre Version werden dort angezeigt." + "stackMemberBody": "Dieser Container gehört zu einer Anwendung, deren Hauptcontainer {primary} ist. Die Anwendung, ihre Weblinks und ihre Version werden dort angezeigt.", + "nativeEvidencePort": "Überwachung des TCP-Ports", + "nativeEvidenceFile": "Installierte Datei", + "nativeEvidenceProcess": "Ausführbare Datei ausführen", + "nativeEvidenceDirectory": "Arbeitsverzeichnis", + "nativeEvidenceService": "Dienstdefinition", + "nativeScanFailed": "Die Anwendungssuche konnte nicht abgeschlossen werden.Frühere Beweise könnten veraltet sein;versuchen Sie es erneut.", + "nativeScanPartial": "Die Anwendungssuche ist unvollständig.Überprüfen Sie die verfügbaren Beweise und versuchen Sie es erneut.", + "nativeVersionUnknown": "Installierte Version unbekannt", + "nativeEvidenceStale": "Die Beweise sind möglicherweise veraltet oder unvollständig.", + "nativeRegisterOnly": "Registrierung im Editor bestätigen.Eine bekannte Version ist ein manueller Wert;Es ist kein Updater konfiguriert." }, "statusFilter": { "ariaLabel": "Virtuelle Maschinen und Container filtern", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 5b30c969..475ba188 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -1737,7 +1737,17 @@ "portCategoryCustomPlaceholder": "Tapez une catégorie et appuyez sur Entrée (Esc pour annuler)", "portCustomUrlPlaceholder": "URL personnalisée (par exemple https://vault.example.com) — remplace IP:port", "stackMemberTitle": "Fait partie d'une application multi-conteneurs", - "stackMemberBody": "Ce conteneur fait partie d'une application dont le conteneur principal est {primary}. L'application, ses liens web et sa version y sont affichés." + "stackMemberBody": "Ce conteneur fait partie d'une application dont le conteneur principal est {primary}. L'application, ses liens web et sa version y sont affichés.", + "nativeEvidencePort": "Écoute du port TCP", + "nativeEvidenceFile": "Fichier installé", + "nativeEvidenceProcess": "Exécuter un exécutable", + "nativeEvidenceDirectory": "Répertoire de travail", + "nativeEvidenceService": "Définition du service", + "nativeScanFailed": "La recherche d'application n'a pas pu se terminer.Les preuves antérieures peuvent être obsolètes ;essayer à nouveau.", + "nativeScanPartial": "La recherche d’applications est incomplète.Examinez les preuves disponibles et réessayez.", + "nativeVersionUnknown": "version installée inconnue", + "nativeEvidenceStale": "Les preuves peuvent être obsolètes ou incomplètes.", + "nativeRegisterOnly": "Confirmez l'inscription dans l'éditeur.Une version connue est une valeur manuelle ;aucun programme de mise à jour n'est configuré." }, "statusFilter": { "ariaLabel": "Filtrer les machines virtuelles et les conteneurs", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index b595a4cc..542ca677 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -1737,7 +1737,17 @@ "portCategoryCustomPlaceholder": "Digita una categoria e premi Enter (Esc per annullare)", "portCustomUrlPlaceholder": "URL personalizzato (ad es. https://vault.example.com) — sostituisce IP:port", "stackMemberTitle": "Parte di un'applicazione multi-container", - "stackMemberBody": "Questo container supporta un’applicazione il cui container principale è {primary}. L’applicazione, i suoi collegamenti web e la sua versione sono mostrati lì." + "stackMemberBody": "Questo container supporta un’applicazione il cui container principale è {primary}. L’applicazione, i suoi collegamenti web e la sua versione sono mostrati lì.", + "nativeEvidencePort": "porta TCP in ascolto", + "nativeEvidenceFile": "file installato", + "nativeEvidenceProcess": "esecuzione dell'eseguibile", + "nativeEvidenceDirectory": "directory di lavoro", + "nativeEvidenceService": "definizione del servizio", + "nativeScanFailed": "impossibile completare la ricerca dell'applicazione.Le prove precedenti potrebbero essere obsolete;riprova.", + "nativeScanPartial": "la ricerca dell'applicazione è incompleta.Esaminare le prove disponibili e riprovare.", + "nativeVersionUnknown": "versione installata sconosciuta", + "nativeEvidenceStale": "le prove potrebbero essere obsolete o incomplete.", + "nativeRegisterOnly": "conferma la registrazione nell'editor.Una versione nota è un valore manuale;nessun programma di aggiornamento è configurato." }, "statusFilter": { "ariaLabel": "Filtra macchine virtuali e container", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index b64fa2d5..3a08fe72 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -1737,7 +1737,17 @@ "portCategoryCustomPlaceholder": "Digite uma categoria e pressione Enter (Esc para cancelar)", "portCustomUrlPlaceholder": "URL personalizada (por exemplo, https://vault.example.com) — substitui IP:port", "stackMemberTitle": "Parte de um aplicativo multicontêiner", - "stackMemberBody": "Este contêiner faz parte de um aplicativo cujo contêiner principal é {primary}. O aplicativo, seus links da web e sua versão são mostrados lá." + "stackMemberBody": "Este contêiner faz parte de um aplicativo cujo contêiner principal é {primary}. O aplicativo, seus links da web e sua versão são mostrados lá.", + "nativeEvidencePort": "Porta TCP de escuta", + "nativeEvidenceFile": "arquivo instalado", + "nativeEvidenceProcess": "Executando executável", + "nativeEvidenceDirectory": "diretório de trabalho", + "nativeEvidenceService": "definição de serviço", + "nativeScanFailed": "a pesquisa do aplicativo não pôde ser concluída.As evidências anteriores podem estar desatualizadas;tente novamente.", + "nativeScanPartial": "a pesquisa do aplicativo está incompleta.Revise as evidências disponíveis e tente novamente.", + "nativeVersionUnknown": "Versão instalada desconhecida", + "nativeEvidenceStale": "as evidências podem estar desatualizadas ou incompletas.", + "nativeRegisterOnly": "Confirme o cadastro no editor.Uma versão conhecida é um valor manual;nenhum atualizador está configurado." }, "statusFilter": { "ariaLabel": "Filtrar máquinas virtuais e contêineres", diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index 7b8cab64..de890ee1 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -1737,7 +1737,17 @@ "portCategoryCustomPlaceholder": "Skriv en kategori och tryck på Enter (Esc för att avbryta)", "portCustomUrlPlaceholder": "Anpassad URL (t.ex. https://vault.example.com) - åsidosätter IP:port", "stackMemberTitle": "En del av en multicontainerapplikation", - "stackMemberBody": "Den här behållaren stöder en applikation vars huvudbehållare är {primary}. Applikationen, dess webblänkar och dess version visas där." + "stackMemberBody": "Den här behållaren stöder en applikation vars huvudbehållare är {primary}. Applikationen, dess webblänkar och dess version visas där.", + "nativeEvidencePort": "Lyssnande TCP-port", + "nativeEvidenceFile": "Installerad fil", + "nativeEvidenceProcess": "Kör körbar fil", + "nativeEvidenceDirectory": "Arbetskatalog", + "nativeEvidenceService": "Servicedefinition", + "nativeScanFailed": "Programsökningen kunde inte avslutas.Tidigare bevis kan vara föråldrade;försök igen.", + "nativeScanPartial": "Sökningen är ofullständig.Granska de tillgängliga bevisen och försök igen.", + "nativeVersionUnknown": "Installerad version okänd", + "nativeEvidenceStale": "Bevisen kan vara föråldrade eller ofullständiga.", + "nativeRegisterOnly": "Bekräfta registreringen i editorn.En känd version är ett manuellt värde;ingen uppdatering är konfigurerad." }, "statusFilter": { "ariaLabel": "Filtrera virtuella maskiner och containrar", From f363ef4395d7e1e11b30256639f7713121aebe65 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 18 Oct 2026 12:38:40 +0200 Subject: [PATCH 46/53] Let dismissed remote mount warnings clear the health status --- AppImage/messages/de/common.json | 12 ++-- AppImage/messages/fr/common.json | 14 ++-- AppImage/messages/it/common.json | 20 +++--- AppImage/messages/pt/common.json | 18 ++--- AppImage/messages/sv/common.json | 12 ++-- AppImage/scripts/health_monitor.py | 4 ++ .../tests/test_remote_mount_dismissal.py | 67 +++++++++++++++++++ 7 files changed, 109 insertions(+), 38 deletions(-) create mode 100644 AppImage/scripts/tests/test_remote_mount_dismissal.py diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index 6d9f2ee7..4cc5b7e0 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -1738,16 +1738,16 @@ "portCustomUrlPlaceholder": "Benutzerdefinierte URL (z. B. https://vault.example.com) überschreibt IP:port", "stackMemberTitle": "Teil einer Multi-Container-Anwendung", "stackMemberBody": "Dieser Container gehört zu einer Anwendung, deren Hauptcontainer {primary} ist. Die Anwendung, ihre Weblinks und ihre Version werden dort angezeigt.", - "nativeEvidencePort": "Überwachung des TCP-Ports", + "nativeEvidencePort": "Lauschender TCP-Port", "nativeEvidenceFile": "Installierte Datei", - "nativeEvidenceProcess": "Ausführbare Datei ausführen", + "nativeEvidenceProcess": "Laufende ausführbare Datei", "nativeEvidenceDirectory": "Arbeitsverzeichnis", "nativeEvidenceService": "Dienstdefinition", - "nativeScanFailed": "Die Anwendungssuche konnte nicht abgeschlossen werden.Frühere Beweise könnten veraltet sein;versuchen Sie es erneut.", - "nativeScanPartial": "Die Anwendungssuche ist unvollständig.Überprüfen Sie die verfügbaren Beweise und versuchen Sie es erneut.", + "nativeScanFailed": "Die Anwendungssuche konnte nicht abgeschlossen werden. Frühere Ergebnisse sind möglicherweise veraltet; versuchen Sie es erneut.", + "nativeScanPartial": "Die Anwendungssuche ist unvollständig. Prüfen Sie die gefundenen Hinweise und versuchen Sie es erneut.", "nativeVersionUnknown": "Installierte Version unbekannt", - "nativeEvidenceStale": "Die Beweise sind möglicherweise veraltet oder unvollständig.", - "nativeRegisterOnly": "Registrierung im Editor bestätigen.Eine bekannte Version ist ein manueller Wert;Es ist kein Updater konfiguriert." + "nativeEvidenceStale": "Die Hinweise sind möglicherweise veraltet oder unvollständig.", + "nativeRegisterOnly": "Bestätigen Sie die Registrierung im Editor. Eine bekannte Version wird als manueller Wert gespeichert; es wird kein Updater eingerichtet." }, "statusFilter": { "ariaLabel": "Virtuelle Maschinen und Container filtern", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index 475ba188..7d542fe5 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -1738,16 +1738,16 @@ "portCustomUrlPlaceholder": "URL personnalisée (par exemple https://vault.example.com) — remplace IP:port", "stackMemberTitle": "Fait partie d'une application multi-conteneurs", "stackMemberBody": "Ce conteneur fait partie d'une application dont le conteneur principal est {primary}. L'application, ses liens web et sa version y sont affichés.", - "nativeEvidencePort": "Écoute du port TCP", + "nativeEvidencePort": "Port TCP en écoute", "nativeEvidenceFile": "Fichier installé", - "nativeEvidenceProcess": "Exécuter un exécutable", + "nativeEvidenceProcess": "Exécutable en cours d'exécution", "nativeEvidenceDirectory": "Répertoire de travail", "nativeEvidenceService": "Définition du service", - "nativeScanFailed": "La recherche d'application n'a pas pu se terminer.Les preuves antérieures peuvent être obsolètes ;essayer à nouveau.", - "nativeScanPartial": "La recherche d’applications est incomplète.Examinez les preuves disponibles et réessayez.", - "nativeVersionUnknown": "version installée inconnue", - "nativeEvidenceStale": "Les preuves peuvent être obsolètes ou incomplètes.", - "nativeRegisterOnly": "Confirmez l'inscription dans l'éditeur.Une version connue est une valeur manuelle ;aucun programme de mise à jour n'est configuré." + "nativeScanFailed": "La recherche d'applications n'a pas pu se terminer. Les résultats précédents peuvent être obsolètes ; réessayez.", + "nativeScanPartial": "La recherche d'applications est incomplète. Vérifiez les éléments trouvés et réessayez.", + "nativeVersionUnknown": "Version installée inconnue", + "nativeEvidenceStale": "Les éléments trouvés peuvent être obsolètes ou incomplets.", + "nativeRegisterOnly": "Confirmez l'enregistrement dans l'éditeur. Une version connue est enregistrée comme valeur manuelle ; aucun outil de mise à jour n'est configuré." }, "statusFilter": { "ariaLabel": "Filtrer les machines virtuelles et les conteneurs", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 542ca677..0846c5c0 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -1738,16 +1738,16 @@ "portCustomUrlPlaceholder": "URL personalizzato (ad es. https://vault.example.com) — sostituisce IP:port", "stackMemberTitle": "Parte di un'applicazione multi-container", "stackMemberBody": "Questo container supporta un’applicazione il cui container principale è {primary}. L’applicazione, i suoi collegamenti web e la sua versione sono mostrati lì.", - "nativeEvidencePort": "porta TCP in ascolto", - "nativeEvidenceFile": "file installato", - "nativeEvidenceProcess": "esecuzione dell'eseguibile", - "nativeEvidenceDirectory": "directory di lavoro", - "nativeEvidenceService": "definizione del servizio", - "nativeScanFailed": "impossibile completare la ricerca dell'applicazione.Le prove precedenti potrebbero essere obsolete;riprova.", - "nativeScanPartial": "la ricerca dell'applicazione è incompleta.Esaminare le prove disponibili e riprovare.", - "nativeVersionUnknown": "versione installata sconosciuta", - "nativeEvidenceStale": "le prove potrebbero essere obsolete o incomplete.", - "nativeRegisterOnly": "conferma la registrazione nell'editor.Una versione nota è un valore manuale;nessun programma di aggiornamento è configurato." + "nativeEvidencePort": "Porta TCP in ascolto", + "nativeEvidenceFile": "File installato", + "nativeEvidenceProcess": "Eseguibile in esecuzione", + "nativeEvidenceDirectory": "Directory di lavoro", + "nativeEvidenceService": "Definizione del servizio", + "nativeScanFailed": "La ricerca delle applicazioni non è stata completata. I risultati precedenti potrebbero non essere aggiornati; riprova.", + "nativeScanPartial": "La ricerca delle applicazioni è incompleta. Controlla gli elementi trovati e riprova.", + "nativeVersionUnknown": "Versione installata sconosciuta", + "nativeEvidenceStale": "Gli elementi trovati potrebbero non essere aggiornati o essere incompleti.", + "nativeRegisterOnly": "Conferma la registrazione nell'editor. Una versione nota viene salvata come valore manuale; non viene configurato alcun aggiornamento automatico." }, "statusFilter": { "ariaLabel": "Filtra macchine virtuali e container", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index 3a08fe72..c8fa95c5 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -1738,16 +1738,16 @@ "portCustomUrlPlaceholder": "URL personalizada (por exemplo, https://vault.example.com) — substitui IP:port", "stackMemberTitle": "Parte de um aplicativo multicontêiner", "stackMemberBody": "Este contêiner faz parte de um aplicativo cujo contêiner principal é {primary}. O aplicativo, seus links da web e sua versão são mostrados lá.", - "nativeEvidencePort": "Porta TCP de escuta", - "nativeEvidenceFile": "arquivo instalado", - "nativeEvidenceProcess": "Executando executável", - "nativeEvidenceDirectory": "diretório de trabalho", - "nativeEvidenceService": "definição de serviço", - "nativeScanFailed": "a pesquisa do aplicativo não pôde ser concluída.As evidências anteriores podem estar desatualizadas;tente novamente.", - "nativeScanPartial": "a pesquisa do aplicativo está incompleta.Revise as evidências disponíveis e tente novamente.", + "nativeEvidencePort": "Porta TCP em escuta", + "nativeEvidenceFile": "Arquivo instalado", + "nativeEvidenceProcess": "Executável em execução", + "nativeEvidenceDirectory": "Diretório de trabalho", + "nativeEvidenceService": "Definição do serviço", + "nativeScanFailed": "A pesquisa de aplicativos não pôde ser concluída. Os resultados anteriores podem estar desatualizados; tente novamente.", + "nativeScanPartial": "A pesquisa de aplicativos está incompleta. Revise os itens encontrados e tente novamente.", "nativeVersionUnknown": "Versão instalada desconhecida", - "nativeEvidenceStale": "as evidências podem estar desatualizadas ou incompletas.", - "nativeRegisterOnly": "Confirme o cadastro no editor.Uma versão conhecida é um valor manual;nenhum atualizador está configurado." + "nativeEvidenceStale": "Os itens encontrados podem estar desatualizados ou incompletos.", + "nativeRegisterOnly": "Confirme o registro no editor. Uma versão conhecida é salva como valor manual; nenhum atualizador é configurado." }, "statusFilter": { "ariaLabel": "Filtrar máquinas virtuais e contêineres", diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index de890ee1..c74cd1c1 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -1740,14 +1740,14 @@ "stackMemberBody": "Den här behållaren stöder en applikation vars huvudbehållare är {primary}. Applikationen, dess webblänkar och dess version visas där.", "nativeEvidencePort": "Lyssnande TCP-port", "nativeEvidenceFile": "Installerad fil", - "nativeEvidenceProcess": "Kör körbar fil", + "nativeEvidenceProcess": "Körande programfil", "nativeEvidenceDirectory": "Arbetskatalog", - "nativeEvidenceService": "Servicedefinition", - "nativeScanFailed": "Programsökningen kunde inte avslutas.Tidigare bevis kan vara föråldrade;försök igen.", - "nativeScanPartial": "Sökningen är ofullständig.Granska de tillgängliga bevisen och försök igen.", + "nativeEvidenceService": "Tjänstdefinition", + "nativeScanFailed": "Programsökningen kunde inte slutföras. Tidigare resultat kan vara inaktuella; försök igen.", + "nativeScanPartial": "Programsökningen är ofullständig. Granska det som hittades och försök igen.", "nativeVersionUnknown": "Installerad version okänd", - "nativeEvidenceStale": "Bevisen kan vara föråldrade eller ofullständiga.", - "nativeRegisterOnly": "Bekräfta registreringen i editorn.En känd version är ett manuellt värde;ingen uppdatering är konfigurerad." + "nativeEvidenceStale": "Det som hittades kan vara inaktuellt eller ofullständigt.", + "nativeRegisterOnly": "Bekräfta registreringen i redigeraren. En känd version sparas som ett manuellt värde; ingen uppdatering konfigureras." }, "statusFilter": { "ariaLabel": "Filtrera virtuella maskiner och containrar", diff --git a/AppImage/scripts/health_monitor.py b/AppImage/scripts/health_monitor.py index 8b6168a9..8f52bed0 100644 --- a/AppImage/scripts/health_monitor.py +++ b/AppImage/scripts/health_monitor.py @@ -6076,6 +6076,8 @@ class HealthMonitor: if status == 'stale': checks[check_label] = { 'status': 'CRITICAL', + 'error_key': error_key, + 'dismissable': True, 'detail': f'{fstype} mount stale: {error or "unreachable"}', 'fstype': fstype, 'source': m.get('source', ''), @@ -6105,6 +6107,8 @@ class HealthMonitor: elif status == 'readonly': checks[check_label] = { 'status': 'WARNING', + 'error_key': error_key, + 'dismissable': True, 'detail': f'{fstype} mount unexpectedly read-only', 'fstype': fstype, 'source': m.get('source', ''), diff --git a/AppImage/scripts/tests/test_remote_mount_dismissal.py b/AppImage/scripts/tests/test_remote_mount_dismissal.py new file mode 100644 index 00000000..c66ccf17 --- /dev/null +++ b/AppImage/scripts/tests/test_remote_mount_dismissal.py @@ -0,0 +1,67 @@ +"""Remote mount checks carry the key their dismissal is saved under.""" +import sys +import unittest +from pathlib import Path +from types import ModuleType, SimpleNamespace +from unittest.mock import patch + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +_health_persistence_module = ModuleType("health_persistence") +_health_persistence_module.health_persistence = SimpleNamespace(cleanup_old_errors=lambda: None) +_health_persistence_module.disk_base_name = lambda name: str(name).replace("/dev/", "") +sys.modules.setdefault("health_persistence", _health_persistence_module) +sys.modules.setdefault("psutil", ModuleType("psutil")) +sys.modules.setdefault("flask_server", SimpleNamespace( + get_proxmox_node_name=lambda: "fixture", + get_cached_pvesh_cluster_resources_vm=lambda: [], + get_cached_vm_disk=lambda _vmid: None, +)) + +import health_monitor # noqa: E402 + + +class _Persistence: + def __init__(self): + self.recorded = [] + + def record_error(self, **kwargs): + self.recorded.append(kwargs) + + def get_active_errors(self, *args, **kwargs): + return [] + + def clear_error(self, key): + pass + + +class RemoteMountDismissal(unittest.TestCase): + def check(self, host, lxc=()): + persistence = _Persistence() + mounts = SimpleNamespace(scan_remote_mounts=lambda force: list(host), + scan_lxc_mounts=lambda force: list(lxc)) + with patch.object(health_monitor, "MOUNT_MONITOR_AVAILABLE", True), \ + patch.object(health_monitor, "mount_monitor", mounts, create=True), \ + patch.object(health_monitor, "health_persistence", persistence): + result = health_monitor.HealthMonitor._check_remote_mounts(SimpleNamespace()) + return result, persistence + + def test_stale_and_read_only_mounts_carry_their_saved_key(self): + result, persistence = self.check( + [{"target": "/mnt/pve/nas", "status": "stale", "fstype": "nfs4"}, + {"target": "/mnt/share", "status": "ok", "fstype": "cifs"}], + [{"target": "/media", "status": "readonly", "fstype": "nfs4", "lxc_id": "120", "lxc_name": "app"}]) + checks = result["checks"] + self.assertEqual(checks["/mnt/pve/nas"]["error_key"], "mount_stale_/mnt/pve/nas") + self.assertEqual(checks["CT 120 (app) /media"]["error_key"], "mount_readonly_ct120:/media") + self.assertTrue(checks["/mnt/pve/nas"]["dismissable"]) + self.assertTrue(checks["CT 120 (app) /media"]["dismissable"]) + self.assertNotIn("error_key", checks["/mnt/share"]) + self.assertEqual({entry["error_key"] for entry in persistence.recorded}, + {"mount_stale_/mnt/pve/nas", "mount_readonly_ct120:/media"}) + + +if __name__ == "__main__": + unittest.main() From f8cabb4700bee2a1630b72f7e642d036342b5557 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 18 Oct 2026 12:58:25 +0200 Subject: [PATCH 47/53] Check translation contracts offline, add Swedish to the installer and enable the website languages - Offline checks for terminal and website catalogs reject translations that change commands, paths, identifiers or placeholders - Terminal and documentation texts that broke those contracts are corrected in every language - Swedish can be chosen in the stable and beta installers - The website routes, language switcher and exported pages cover the eight languages, with the shared website texts in each of them - Website documentation translations are generated only when requested - From PR #475 by @jcastro --- .github/scripts/build_web_docs_i18n.py | 83 +++++- .github/scripts/check_terminal_catalogs.py | 77 ++++++ .github/scripts/check_web_docs_catalogs.py | 75 ++++++ .../scripts/tests/test_build_web_docs_i18n.py | 118 ++++++++ .../test_documentation_review_workflows.py | 30 +++ .../scripts/tests/test_installer_languages.py | 30 +++ .../scripts/tests/test_terminal_catalogs.py | 82 ++++++ .../scripts/tests/test_web_docs_catalogs.py | 94 +++++++ .../scripts/tests/test_web_export_language.py | 41 +++ .github/workflows/build-web-docs-i18n.yml | 11 +- .github/workflows/test-i18n.yml | 10 + install_proxmenux.sh | 5 +- install_proxmenux_beta.sh | 3 +- lang/de.json | 42 +-- lang/es.json | 28 +- lang/fr.json | 50 ++-- lang/pt.json | 44 +-- lang/sk.json | 12 +- lang/sv.json | 36 +-- web/CONTRIBUTING-TRANSLATIONS.md | 22 +- web/app/layout.tsx | 9 +- web/components/language-switcher.tsx | 5 + web/i18n/routing.ts | 2 +- web/messages/de/common.json | 254 ++++++++++++++++++ web/messages/en/common.json | 7 +- web/messages/es/common.json | 7 +- .../docs/backup-restore/creating-backups.json | 2 +- .../es/docs/backup-restore/cross-kernel.json | 4 +- .../backup-restore/destinations/index.json | 2 +- .../docs/backup-restore/destinations/pbs.json | 10 +- .../es/docs/backup-restore/glossary.json | 2 +- .../es/docs/backup-restore/how-it-works.json | 2 +- .../es/docs/backup-restore/restoring.json | 2 +- .../es/docs/hardware/switch-gpu-mode.json | 2 +- web/messages/es/docs/monitor/access-auth.json | 4 +- .../es/docs/monitor/api-reference.json | 4 +- .../docs/monitor/dashboard/vms-lxcs-app.json | 2 +- .../monitor/dashboard/vms-lxcs-updates.json | 8 +- .../es/docs/monitor/integrations.json | 6 +- .../es/docs/monitor/notifications.json | 10 +- .../es/docs/oci-manager/hardware.json | 2 +- .../es/docs/oci-manager/storage-network.json | 2 +- web/messages/es/docs/post-install/system.json | 2 +- .../es/docs/storage-share/host-iscsi.json | 2 +- .../docs/storage-share/lxc-mount-points.json | 2 +- web/messages/fr/common.json | 254 ++++++++++++++++++ web/messages/it/common.json | 254 ++++++++++++++++++ web/messages/pt/common.json | 254 ++++++++++++++++++ web/messages/sk/common.json | 17 +- .../docs/monitor/dashboard/vms-lxcs-app.json | 2 +- .../monitor/dashboard/vms-lxcs-updates.json | 4 +- web/messages/sv/common.json | 254 ++++++++++++++++++ web/package.json | 4 +- web/scripts/localize-export.mjs | 32 +++ 54 files changed, 2149 insertions(+), 173 deletions(-) create mode 100644 .github/scripts/check_terminal_catalogs.py create mode 100644 .github/scripts/check_web_docs_catalogs.py create mode 100644 .github/scripts/tests/test_documentation_review_workflows.py create mode 100644 .github/scripts/tests/test_installer_languages.py create mode 100644 .github/scripts/tests/test_terminal_catalogs.py create mode 100644 .github/scripts/tests/test_web_docs_catalogs.py create mode 100644 .github/scripts/tests/test_web_export_language.py create mode 100644 web/messages/de/common.json create mode 100644 web/messages/fr/common.json create mode 100644 web/messages/it/common.json create mode 100644 web/messages/pt/common.json create mode 100644 web/messages/sv/common.json create mode 100644 web/scripts/localize-export.mjs diff --git a/.github/scripts/build_web_docs_i18n.py b/.github/scripts/build_web_docs_i18n.py index e0567c15..cf77fd5f 100644 --- a/.github/scripts/build_web_docs_i18n.py +++ b/.github/scripts/build_web_docs_i18n.py @@ -73,6 +73,24 @@ TECHNICAL_TERMS = ( "DKMS", "SSH", "API", + "Flask", + "SQLite", + "Prometheus", + "Grafana", + "Uptime Kuma", + "PWA", + "Fail2Ban", + "Tailscale", + "JavaScript", + "TypeScript", + "Next.js", + "Coral", + "rclone", + "journald", + "gasket", + "apex", + "Alpine", + "OpenVINO", ) PROTECTED_PATTERNS = ( @@ -88,9 +106,13 @@ PROTECTED_PATTERNS = ( re.compile(r"(?. + re.compile(r"(?]*>.*?", + r"<(code|codeNw|codeXs|kbd|pre)\b[^>]*>.*?", re.IGNORECASE | re.DOTALL, ) @@ -109,16 +131,23 @@ ANY_TAG_RE = re.compile(r"]*>") BARE_SENTINELS = False PLACEHOLDER_RE = re.compile(r"\{[A-Za-z_][A-Za-z0-9_.-]*\}") NON_TRANSLATABLE_KEYS = { + "accent", + "categoryslug", "command", "code", "href", "icon", + "iconbg", "id", + "menucmd", "path", "route", "slug", "src", "url", + "color", + "tone", + "variant", } SOURCE_STATE_VERSION = 1 @@ -186,12 +215,34 @@ def should_copy(source: str, path: tuple[str | int, ...]) -> bool: if not source.strip() or not re.search(r"[A-Za-z]", source): return True last = str(path[-1]).lower() if path else "" + # These fields describe the surrounding snippet; they are prose, even + # though their names end in "Code". + if last in {"aftercode", "beforecode"}: + return False + # This UI breadcrumb is prose, rather than a filesystem path. + if last == "uipath" and "" in source: + return False + if last == "flow" and source in {"loader", "dedicated"}: + return True + # These exact values drive rendering/navigation; similarly named visible + # labels remain translatable. + if last == "branch" and source in {"main", "develop", "feature/*"}: + return True + if last == "kind" and source in {"internal", "external"}: + return True + if last == "key" and source in {"linux", "nas", "windows"}: + return True if last in NON_TRANSLATABLE_KEYS or any( last.endswith(suffix) for suffix in ("url", "href", "path", "command", "code", "icon") ): return True - if re.fullmatch(r"https?://\S+", source) or re.fullmatch(r"/[A-Za-z0-9_./:@%+=-]+", source): + if re.fullmatch(r"https?://\S+", source) or re.fullmatch(r"/\S+", source): + return True + # Standalone literal blocks contain examples, not prose. Separators between + # several blocks do not make their command/configuration contents translatable. + without_literals = LITERAL_TAG_RE.sub("", source) + if without_literals != source and not re.search(r"[A-Za-z]", ANY_TAG_RE.sub("", without_literals)): return True if re.fullmatch(r"[A-Z0-9_.:/+-]{2,}", source): return True @@ -202,6 +253,15 @@ def leaf_token(path: tuple[str | int, ...]) -> str: return json.dumps(path, ensure_ascii=False, separators=(",", ":")) +def literal_values_match(source: Any, target: Any) -> bool: + """Literal commands and rendering metadata must follow the source too.""" + return all( + not should_copy(leaf.source, leaf.path) + or get_at_path(target, leaf.path) == leaf.source + for leaf in iter_leaves(source) + ) + + def source_fingerprints(source: Any) -> dict[str, str]: return { leaf_token(leaf.path): hashlib.sha256(leaf.source.encode("utf-8")).hexdigest()[:20] @@ -240,7 +300,7 @@ def protect_rich_tags(text: str) -> tuple[str, dict[str, str]]: def replace(match: re.Match[str]) -> str: original = match.group(1) - if original.lower() in {"code", "kbd", "pre"}: + if original.lower() in {"code", "codenw", "codexs", "kbd", "pre"}: return match.group(0) key = original.lower() internal = names.get(key) @@ -329,11 +389,14 @@ def protect_text(text: str) -> tuple[str, dict[str, str]]: def restore_text(text: str, mapping: dict[str, str]) -> str: - missing = [token for token in mapping if token not in text] + # A local model may append a digit to a bare sentinel. A substring match + # would silently restore the prefix and leak the extra digit into prose. + patterns = {token: re.compile(re.escape(token) + r"(?![0-9])") for token in mapping} + missing = [token for token, pattern in patterns.items() if not pattern.search(text)] if missing: raise ValueError(f"translation provider changed protected token {missing[0]}") for token, original in mapping.items(): - text = text.replace(token, original) + text = patterns[token].sub(lambda match: original, text) return text @@ -347,6 +410,11 @@ def validate_contract(source: str, target: str) -> None: raise ValueError("literal code changed") if sorted(PLACEHOLDER_RE.findall(source)) != sorted(PLACEHOLDER_RE.findall(target)): raise ValueError("placeholder contract changed") + # Bare numeric protection markers once leaked a trailing zero just + # outside an otherwise intact code span. Literal/ICU checks cannot see it. + residue = re.compile(r'(?:|)(0+)(?=\s|[;:/<().,-]|$)') + if len(residue.findall(target)) > len(residue.findall(source)): + raise ValueError("unexpected numeric residue next to literal markup") def provider_function(args: argparse.Namespace) -> Callable[[str, str], str]: @@ -822,7 +890,10 @@ def main() -> int: leaves = pending_leaves(source, target, queued_tokens) total_strings += len(iter_leaves(source)) missing_strings += len(leaves) - schema_changed = not schema_matches(source, target) + schema_changed = ( + not schema_matches(source, target) + or not literal_values_match(source, target) + ) if leaves or schema_changed: pending.append( ( diff --git a/.github/scripts/check_terminal_catalogs.py b/.github/scripts/check_terminal_catalogs.py new file mode 100644 index 00000000..ba5795da --- /dev/null +++ b/.github/scripts/check_terminal_catalogs.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Check active terminal translations offline, without generating any text. + +Validates formatting fields and literal commands/identifiers. Missing values +can retain English fallback; --require-complete makes them errors. This does +not establish the accuracy or fluency of the translated prose. +""" +from __future__ import annotations + +import argparse +from pathlib import Path +import re + +from build_translation_cache import ( + DEFAULT_LANGUAGES, STRUCTURAL_LITERAL_RE, extract_catalog_texts, + extract_python_texts, extract_translate_texts, +) +from build_web_docs_i18n import read_json, validate_contract + + +def active_sources(root: Path) -> list[str]: + # Match build-translation-cache.yml, including launchers outside scripts/. + extras = [root / name for name in ('menu', 'install_proxmenux.sh', 'install_proxmenux_beta.sh')] + texts = extract_translate_texts(root / 'scripts', extras) + texts += extract_translate_texts(root / 'oci/remote') + texts += extract_python_texts([root / 'scripts', root / 'oci/src', root / 'oci/remote']) + texts += extract_catalog_texts([root / 'oci/catalog']) + return sorted({text for text in texts if '$' not in text and '`' not in text}) + + +def validate_message(source: str, target: str, *, title_separator: bool = False) -> None: + validate_contract(source, target) + for match in STRUCTURAL_LITERAL_RE.finditer(source): + literal = match.group() + # A page title may replace its decorative separator with a dash. + # In command examples, a pipe remains a shell operator. + if title_separator and literal == '|': + continue + # DNS hostnames are case-insensitive. Paths, field names and command + # names are not, so do not case-fold those to hide a broken example. + is_hostname = re.fullmatch(r'[A-Za-z0-9.-]+\.(?:com|org|net|io)', literal) + if literal not in target and not (is_hostname and literal.casefold() in target.casefold()): + raise ValueError(f'literal command or identifier changed: {literal}') + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--root', type=Path, default=Path('.')) + parser.add_argument('--languages', default=','.join(DEFAULT_LANGUAGES)) + parser.add_argument('--require-complete', action='store_true') + args = parser.parse_args() + root = args.root.resolve() + texts = active_sources(root) + errors = 0 + for language in args.languages.split(','): + if language not in DEFAULT_LANGUAGES: + parser.error(f'Unsupported locale: {language}') + catalog = read_json(root / 'lang' / f'{language}.json') or {} + missing = 0 + for source in texts: + target = catalog.get(source) + if not isinstance(target, str) or not target.strip(): + missing += 1 + continue + try: + validate_message(source, target) + except ValueError as error: + print(f'[{language}] {source!r}: {error}') + errors += 1 + print(f'[{language}] active={len(texts)} missing={missing}') + if args.require_complete and missing: + errors += 1 + return 1 if errors else 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/.github/scripts/check_web_docs_catalogs.py b/.github/scripts/check_web_docs_catalogs.py new file mode 100644 index 00000000..422b39cb --- /dev/null +++ b/.github/scripts/check_web_docs_catalogs.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Check documentation translation structure and literal contracts offline. + +Missing locales/leaves are allowed by default because the website falls back +to English. Use --require-complete when validating a catalog completion PR. +This checks data integrity, not translation fluency or linguistic accuracy. +""" +from __future__ import annotations + +import argparse +from pathlib import Path + +from build_web_docs_i18n import ( + DEFAULT_LANGUAGES, + get_at_path, + iter_leaves, + literal_values_match, + read_json, + schema_matches, + source_files, +) +from check_terminal_catalogs import validate_message + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--messages-dir", type=Path, default=Path("web/messages")) + parser.add_argument("--languages", default=",".join(DEFAULT_LANGUAGES)) + parser.add_argument("--require-complete", action="store_true") + args = parser.parse_args() + root = args.messages_dir.resolve() + files = source_files(root / "en", ".") + errors = 0 + for language in args.languages.split(","): + language = language.strip() + if language not in DEFAULT_LANGUAGES: + parser.error(f"Unsupported locale: {language}") + missing = checked = present_files = 0 + for file in files: + relative = file.relative_to(root / "en") + source = read_json(file) + target = read_json(root / language / relative) + if target is not None: + present_files += 1 + if args.require_complete and not schema_matches(source, target): + print(f"[{language}] {relative}: source/target schema differs") + errors += 1 + for leaf in iter_leaves(source): + localized = get_at_path(target, leaf.path) + if not isinstance(localized, str) or (leaf.source.strip() and not localized.strip()): + missing += 1 + continue + try: + validate_message( + leaf.source, localized, + title_separator=bool( + leaf.path and leaf.path[0] == "meta" + and leaf.path[-1] in ("title", "ogTitle", "twitterTitle") + ), + ) + except ValueError as exc: + print(f"[{language}] {relative}:{'.'.join(map(str, leaf.path))}: {exc}") + errors += 1 + checked += 1 + if args.require_complete and not literal_values_match(source, target): + print(f"[{language}] {relative}: literal commands or rendering metadata differ") + errors += 1 + print(f"[{language}] files={present_files}/{len(files)} strings={checked} missing={missing}") + if args.require_complete and missing: + errors += 1 + return 1 if errors else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/scripts/tests/test_build_web_docs_i18n.py b/.github/scripts/tests/test_build_web_docs_i18n.py index ea2693ca..691a6639 100644 --- a/.github/scripts/tests/test_build_web_docs_i18n.py +++ b/.github/scripts/tests/test_build_web_docs_i18n.py @@ -60,6 +60,124 @@ class WebDocsI18nTests(unittest.TestCase): target = {"title": "Actualizaciones", "url": "https://example.com"} self.assertEqual(MODULE.pending_leaves(source, target), []) + def test_rendering_metadata_is_copied_but_visible_labels_are_translated(self): + for key, value in (("tone", "amber"), ("variant", "source"), + ("accent", "border-blue-300 bg-blue-50"), + ("iconBg", "bg-blue-100 text-blue-700"), + ("color", "green")): + with self.subTest(key=key): + self.assertTrue(MODULE.should_copy(value, ("steps", 0, key))) + self.assertFalse(MODULE.should_copy(value, ("label",))) + self.assertEqual(MODULE.merge_tree({key: value}, {key: "translated"}, {}), {key: value}) + self.assertFalse(MODULE.should_copy("Translation", ("type",))) + self.assertFalse(MODULE.should_copy("English", ("lang",))) + for value in ("loader", "dedicated"): + self.assertTrue(MODULE.should_copy(value, ("cards", 0, "flow"))) + self.assertFalse(MODULE.should_copy(value, ("label",))) + + def test_literal_only_examples_and_api_routes_are_not_translation_gaps(self): + for source in ("apt update", + "net0 / net1.", + "/api/task-log/<upid>", "/api/vms//config"): + with self.subTest(source=source): + self.assertTrue(MODULE.should_copy(source, ("cell",))) + self.assertFalse(MODULE.should_copy("Run apt update first.", ("body",))) + self.assertFalse(MODULE.should_copy("After running the command, check its output.", ("afterCode",))) + self.assertFalse(MODULE.should_copy("Before running the command, read this.", ("beforeCode",))) + self.assertTrue(MODULE.should_copy("curl http://localhost:8008/api/health", ("curlCode",))) + + def test_inline_identifiers_and_encoded_delimiters_round_trip(self): + source = "Set RS_SKIP_PATHS, call _rs_apply_hot and keep localStorage at <host>." + protected, mapping = MODULE.protect_text(source) + for literal in ("RS_SKIP_PATHS", "_rs_apply_hot", "localStorage", "<", ">"): + self.assertIn(literal, mapping.values()) + self.assertNotIn(literal, protected) + self.assertEqual(MODULE.restore_text(protected, mapping), source) + + def test_custom_code_spans_keep_commands_literal(self): + for tag in ("codeNw", "codeXs"): + source = f"Run <{tag}>pct config 100." + with self.subTest(tag=tag): + protected, mapping = MODULE.protect_rich_tags(source) + self.assertEqual(protected, source) + self.assertEqual(mapping, {}) + with self.assertRaisesRegex(ValueError, "literal code changed"): + MODULE.validate_contract(source, source.replace("config", "configuration")) + with self.assertRaisesRegex(ValueError, "numeric residue"): + MODULE.validate_contract(source, source.replace(f"", f"0")) + self.assertTrue(MODULE.should_copy(f"<{tag}>pct config 100", ("example",))) + + def test_navigation_identifiers_are_distinct_from_visible_labels(self): + values = { + "categorySlug": ["system", "network"], + "menuCmd": ["menu"], + "branch": ["main", "develop", "feature/*"], + "kind": ["internal", "external"], + "key": ["nas", "linux", "windows"], + } + for key, items in values.items(): + for value in items: + with self.subTest(key=key, value=value): + self.assertTrue(MODULE.should_copy(value, ("cards", 0, key))) + self.assertFalse(MODULE.should_copy(value, ("label",))) + self.assertFalse(MODULE.should_copy("Translation", ("kind",))) + self.assertFalse(MODULE.should_copy("Storage", ("branch",))) + + def test_ui_breadcrumb_is_prose_while_paths_remain_literal(self): + source = "In the UI: Datacenter → Backup → Retention:" + self.assertFalse(MODULE.should_copy(source, ("retention", "uiPath"))) + self.assertTrue(MODULE.should_copy("/etc/pve/storage.cfg", ("uiPath",))) + self.assertTrue(MODULE.should_copy("/etc/pve/storage.cfg", ("configPath",))) + + def test_bare_sentinel_with_added_digit_is_rejected(self): + with mock.patch.object(MODULE, "BARE_SENTINELS", True): + protected, mapping = MODULE.protect_text("ProxMenux collects metrics.") + token = next(iter(mapping)) + with self.assertRaisesRegex(ValueError, "changed protected token"): + MODULE.restore_text(protected.replace(token, token + "0"), mapping) + self.assertEqual(MODULE.restore_text(protected, mapping), "ProxMenux collects metrics.") + + def test_numeric_residue_outside_intact_literal_is_rejected(self): + examples = [ + ('Run pct config first.', 'Spustite pct config0 najprv.'), + ('Restart=on-failure restarts the service.', + '0Restart=on-failure reštartuje službu.'), + ] + for source, target in examples: + with self.subTest(source=source): + with self.assertRaisesRegex(ValueError, 'numeric residue'): + MODULE.validate_contract(source, target) + MODULE.validate_contract('The result is echo0.', + 'Výsledok je echo0.') + + def test_product_and_kernel_module_names_are_protected(self): + source = "Flask uses SQLite and journald; rclone works with Coral, gasket, apex and Alpine." + protected, mapping = MODULE.protect_text(source) + for name in ("Flask", "SQLite", "journald", "rclone", "Coral", "gasket", "apex", "Alpine"): + self.assertIn(name, mapping.values()) + self.assertNotIn(name, protected) + self.assertEqual(MODULE.restore_text(protected, mapping), source) + + def test_literal_only_changes_sync_without_retranslating_human_prose(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "messages/en/common.json" + target = root / "messages/de/common.json" + source.parent.mkdir(parents=True) + target.parent.mkdir(parents=True) + source.write_text(json.dumps({"title": "Updates", "code": "tool --version=2", "tone": "blue"})) + target.write_text(json.dumps({"title": "Aktualisierungen", "code": "tool --version=1", "tone": "blau"})) + provider = mock.Mock(side_effect=AssertionError("Existing prose must not be retranslated")) + self.assertEqual(self.run_generator(root, provider), 0) + self.assertEqual(json.loads(target.read_text()), { + "title": "Aktualisierungen", "code": "tool --version=2", "tone": "blue", + }) + provider.assert_not_called() + before = target.stat().st_mtime_ns + self.assertEqual(self.run_generator(root, provider), 0) + self.assertEqual(target.stat().st_mtime_ns, before) + provider.assert_not_called() + def test_changed_source_text_is_pending_even_with_existing_translation(self): source = {"title": "Updated installation guidance"} target = {"title": "Vorherige Installationsanleitung"} diff --git a/.github/scripts/tests/test_documentation_review_workflows.py b/.github/scripts/tests/test_documentation_review_workflows.py new file mode 100644 index 00000000..49d8cd93 --- /dev/null +++ b/.github/scripts/tests/test_documentation_review_workflows.py @@ -0,0 +1,30 @@ +"""Keep machine generation explicit without losing automatic validation.""" +from pathlib import Path +import unittest + +import yaml + + +ROOT = Path(__file__).resolve().parents[3] + + +class DocumentationReviewWorkflowTests(unittest.TestCase): + def workflow(self, name): + return yaml.load( + (ROOT / '.github/workflows' / name).read_text(encoding='utf-8'), + Loader=yaml.BaseLoader, + ) + + def test_generation_requires_explicit_dispatch(self): + triggers = self.workflow('build-web-docs-i18n.yml')['on'] + self.assertEqual(set(triggers), {'workflow_dispatch'}) + + def test_website_changes_keep_automatic_offline_validation(self): + triggers = self.workflow('test-i18n.yml')['on'] + for event in ('pull_request', 'push'): + with self.subTest(event=event): + self.assertIn('web/**', triggers[event]['paths']) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/scripts/tests/test_installer_languages.py b/.github/scripts/tests/test_installer_languages.py new file mode 100644 index 00000000..0aa0ccef --- /dev/null +++ b/.github/scripts/tests/test_installer_languages.py @@ -0,0 +1,30 @@ +"""Every shipped terminal language must be selectable during installation.""" +from pathlib import Path +import re +import sys +import unittest + +SCRIPTS = Path(__file__).resolve().parents[1] +ROOT = SCRIPTS.parents[1] +sys.path.insert(0, str(SCRIPTS)) +from build_translation_cache import DEFAULT_LANGUAGES + + +class InstallerLanguageTests(unittest.TestCase): + def test_both_installers_offer_every_shipped_language(self): + expected = {'en', *DEFAULT_LANGUAGES} + for name in ('install_proxmenux.sh', 'install_proxmenux_beta.sh'): + with self.subTest(installer=name): + source = (ROOT / name).read_text(encoding='utf-8') + function = re.search(r'^select_language\(\) \{(.*?)^\}', source, re.M | re.S) + self.assertIsNotNone(function, 'Missing installer language selector') + offered = re.findall(r'^\s*"([a-z]{2})"\s+"[^"]+"', function.group(1), re.M) + self.assertEqual(set(offered), expected) + self.assertEqual(len(offered), len(expected), 'Duplicate language option') + # English is the source text in the scripts, not a JSON catalog. + for language in set(offered) - {'en'}: + self.assertTrue((ROOT / 'lang' / f'{language}.json').is_file(), language) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/scripts/tests/test_terminal_catalogs.py b/.github/scripts/tests/test_terminal_catalogs.py new file mode 100644 index 00000000..f74966cb --- /dev/null +++ b/.github/scripts/tests/test_terminal_catalogs.py @@ -0,0 +1,82 @@ +"""Offline regressions for copyable commands and runtime format fields.""" +import importlib.util +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +SCRIPTS = Path(__file__).resolve().parents[1] +ROOT = SCRIPTS.parents[1] +sys.path.insert(0, str(SCRIPTS)) +SPEC = importlib.util.spec_from_file_location('check_terminal_catalogs', SCRIPTS / 'check_terminal_catalogs.py') +MODULE = importlib.util.module_from_spec(SPEC) +assert SPEC and SPEC.loader +SPEC.loader.exec_module(MODULE) + + +class TerminalCatalogsTests(unittest.TestCase): + def test_extractor_includes_launchers_remote_scripts_and_catalog_text(self): + fixtures = { + 'menu': 'translate "Main launcher"', + 'install_proxmenux.sh': 'translate "Stable installer"', + 'install_proxmenux_beta.sh': 'translate "Beta installer"', + 'scripts/check.sh': 'translate "Host shell script"', + 'scripts/check.py': 'translate("Host Python script")', + 'oci/src/check.py': 'N_("OCI Python script")', + 'oci/remote/check.py': 'translate("Remote Python script")', + 'oci/remote/check.sh': 'translate "Remote shell script"', + 'oci/catalog/app.json': json.dumps({'catalog_ui': {'tagline': {'en_US': 'Application tagline'}}}), + } + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for name, content in fixtures.items(): + file = root / name + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(content, encoding='utf-8') + self.assertEqual(set(MODULE.active_sources(root)), { + 'Main launcher', 'Stable installer', 'Beta installer', + 'Host shell script', 'Host Python script', 'OCI Python script', + 'Remote Python script', 'Remote shell script', 'Application tagline', + }) + + def test_runtime_format_field_cannot_be_translated(self): + source = 'Kimai has no default account. Enter the container with: pct enter {main_vmid}' + with self.assertRaisesRegex(ValueError, 'placeholder'): + MODULE.validate_message(source, 'Kimai nemá účet: pct enter {main vmid}') + MODULE.validate_message(source, 'Kimai nemá účet: pct enter {main_vmid}') + + def test_copyable_command_and_environment_variable_are_literal(self): + examples = [ + ('Example: pct config 110 | grep unprivileged', 'Ejemplo: configuración pct 110 | grep sin privilegios'), + ("Use 'pct restore' to recover the disks.", "Verwenden Sie 'pct restart', um die Datenträger wiederherzustellen."), + ('Invalid ALLOWED_HOSTS value', 'Valeur HÔTES INDÉSIRABLES'), + ] + for source, target in examples: + with self.subTest(source=source): + with self.assertRaisesRegex(ValueError, 'literal command or identifier'): + MODULE.validate_message(source, target) + MODULE.validate_message('Example: pct config 110 | grep unprivileged', + 'Ejemplo: pct config 110 | grep unprivileged') + + def test_dns_capitalization_does_not_relax_file_path_case(self): + MODULE.validate_message('Cannot reach download.proxmox.com.', + 'Download.proxmox.com ist nicht erreichbar.') + with self.assertRaisesRegex(ValueError, 'literal command or identifier'): + MODULE.validate_message('Check /etc/pve/storage.cfg', 'Prüfen Sie /etc/PVE/storage.cfg') + + def test_shipped_active_catalogs_keep_literal_contracts(self): + result = subprocess.run( + [sys.executable, '-B', str(SCRIPTS / 'check_terminal_catalogs.py'), + '--root', str(ROOT), '--require-complete'], + capture_output=True, text=True, timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + for locale in MODULE.DEFAULT_LANGUAGES: + self.assertIn(f'[{locale}] active=', result.stdout) + self.assertNotIn('missing=1', result.stdout) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/scripts/tests/test_web_docs_catalogs.py b/.github/scripts/tests/test_web_docs_catalogs.py new file mode 100644 index 00000000..3d851425 --- /dev/null +++ b/.github/scripts/tests/test_web_docs_catalogs.py @@ -0,0 +1,94 @@ +"""Validate complete catalogs without rejecting intentional English fallback.""" +import json +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +SCRIPT = Path(__file__).resolve().parents[1] / "check_web_docs_catalogs.py" + + +class WebDocsCatalogTests(unittest.TestCase): + def run_check(self, root, *options): + return subprocess.run( + [sys.executable, "-B", str(SCRIPT), "--messages-dir", str(root), + "--languages", "de", *options], + capture_output=True, text=True, + ) + + def write_catalog(self, root, locale, content): + file = root / locale / "common.json" + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(json.dumps(content), encoding="utf-8") + + def test_partial_catalog_keeps_fallback_but_complete_mode_reports_gap(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_catalog(root, "en", {"title": "Updates", "body": "Check the host."}) + self.write_catalog(root, "de", {"title": "Aktualisierungen"}) + partial = self.run_check(root) + self.assertEqual(partial.returncode, 0, partial.stdout + partial.stderr) + self.assertIn("missing=1", partial.stdout) + complete = self.run_check(root, "--require-complete") + self.assertEqual(complete.returncode, 1, complete.stdout + complete.stderr) + self.assertIn("schema differs", complete.stdout) + + def test_translated_code_is_rejected_in_both_modes(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_catalog(root, "en", {"body": "Run apt update."}) + self.write_catalog(root, "de", {"body": "Führe apt aktualisieren aus."}) + for options in ((), ("--require-complete",)): + with self.subTest(options=options): + result = self.run_check(root, *options) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + + def test_bare_commands_and_operators_are_not_prose(self): + examples = [ + ("Restore with pct restore.", "Mit pct restart wiederherstellen."), + ("pveversion | grep pve-manager", "pveversion grep pve-manager"), + ("cd /opt/my-app && ./update.sh", "cd /opt/meine-app && ./update.sh"), + ] + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for source, target in examples: + with self.subTest(source=source): + self.write_catalog(root, "en", {"body": source}) + self.write_catalog(root, "de", {"body": target}) + result = self.run_check(root) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn("literal command or identifier", result.stdout) + + def test_title_separator_can_change_without_relaxing_body_pipes(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_catalog(root, "en", {"meta": {"title": "Storage | ProxMenux"}}) + self.write_catalog(root, "de", {"meta": {"title": "Speicher — ProxMenux"}}) + result = self.run_check(root, "--require-complete") + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.write_catalog(root, "en", {"meta": {"description": "pveversion | grep pve-manager"}}) + self.write_catalog(root, "de", {"meta": {"description": "pveversion grep pve-manager"}}) + result = self.run_check(root) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + + def test_shipped_web_catalogs_keep_literal_contracts(self): + messages = SCRIPT.parents[2] / "web" / "messages" + result = subprocess.run( + [sys.executable, "-B", str(SCRIPT), "--messages-dir", str(messages)], + capture_output=True, text=True, timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_literal_metadata_is_required_but_labels_can_be_localized(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.write_catalog(root, "en", {"label": "Updates", "tone": "blue"}) + self.write_catalog(root, "de", {"label": "Aktualisierungen", "tone": "blau"}) + invalid = self.run_check(root, "--require-complete") + self.assertEqual(invalid.returncode, 1, invalid.stdout + invalid.stderr) + self.assertIn("rendering metadata differ", invalid.stdout) + self.write_catalog(root, "de", {"label": "Aktualisierungen", "tone": "blue"}) + valid = self.run_check(root, "--require-complete") + self.assertEqual(valid.returncode, 0, valid.stdout + valid.stderr) diff --git a/.github/scripts/tests/test_web_export_language.py b/.github/scripts/tests/test_web_export_language.py new file mode 100644 index 00000000..143b0ccb --- /dev/null +++ b/.github/scripts/tests/test_web_export_language.py @@ -0,0 +1,41 @@ +"""Exercise the static-export language repair on real HTML files.""" +from pathlib import Path +import subprocess +import tempfile +import unittest + +from test_command_descriptions import require_node + + +ROOT = Path(__file__).resolve().parents[3] +SCRIPT = ROOT / "web/scripts/localize-export.mjs" + + +class WebExportLanguageTests(unittest.TestCase): + def test_locales_nested_pages_preserved_content_and_idempotence(self): + node = require_node() + html = '

    Text

    ' + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + locales = ["en", "es", "de", "fr", "it", "pt", "sk", "sv"] + for locale in locales: + file = root / "out" / locale / "docs/monitor/index.html" + file.parent.mkdir(parents=True) + file.write_text(html) + for relative in ["out/index.html", "out/assets/index.html"]: + file = root / relative + file.parent.mkdir(parents=True, exist_ok=True) + file.write_text(html) + result = subprocess.run([node, str(SCRIPT)], cwd=root, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("8 localized pages", result.stdout) + for locale in locales: + actual = (root / "out" / locale / "docs/monitor/index.html").read_text() + self.assertEqual(actual, html.replace('&1 1>&2 2>&3) + "sk" "Slovenčina" \ + "sv" "Svenska" 3>&1 1>&2 2>&3) if [ -z "$LANGUAGE" ]; then msg_error "No language selected. Exiting." diff --git a/install_proxmenux_beta.sh b/install_proxmenux_beta.sh index 90022cd2..225b3502 100644 --- a/install_proxmenux_beta.sh +++ b/install_proxmenux_beta.sh @@ -594,7 +594,8 @@ select_language() { "de" "German" \ "it" "Italian" \ "pt" "Portuguese" \ - "sk" "Slovenčina" 3>&1 1>&2 2>&3) + "sk" "Slovenčina" \ + "sv" "Svenska" 3>&1 1>&2 2>&3) if [ -z "$LANGUAGE" ]; then msg_error "No language selected. Exiting." diff --git a/lang/de.json b/lang/de.json index 6b118ceb..1e552659 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1643,8 +1643,8 @@ "Default Proxmox logo applied": "Standardmäßiges Proxmox-Logo angewendet", "Default is none (video-only passthrough). Use SPACE to toggle selections.": "Der Standardwert ist „Keine“ (nur Video-Passthrough). Verwenden Sie die LEERTASTE, um die Auswahl umzuschalten.", "Default journald configuration restored": "Standard-Journald-Konfiguration wiederhergestellt", - "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "Der Standardspeicherort ist /mnt/. Die Freigabe wird hier auf dem Host mit offenen Berechtigungen gemountet, sodass ein unprivilegierter LXC sie binden, mounten und darauf schreiben kann. Für den LXC-Zugriff müssen Sie diesen Pfad mit dem LXC Mount Manager binden.", - "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "Der Standardspeicherort ist /mnt/. Die Freigabe wird hier auf dem Host gemountet. Verwenden Sie diesen Pfad in /etc/fstab. Für den LXC-Zugriff müssen Sie diesen Pfad mit dem LXC Mount Manager binden.", + "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "Der Standardspeicherort ist /mnt/. Die Freigabe wird hier auf dem Host mit offenen Berechtigungen eingehängt, sodass ein unprivilegierter LXC sie per Bind-Mount einbinden und darauf schreiben kann. Verwenden Sie dafür den LXC Mount Manager.", + "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "Der Standardspeicherort ist /mnt/. Die Freigabe wird hier auf dem Host eingehängt. Verwenden Sie diesen Pfad in /etc/fstab. Für den Zugriff aus einem LXC binden Sie ihn mit dem LXC Mount Manager als Bind-Mount ein.", "Default login": "Standard-Login", "Default options": "Standardoptionen", "Default options read/write": "Standardoptionen Lesen/Schreiben", @@ -2035,7 +2035,7 @@ "Enter VM ID:": "Geben Sie die VM-ID ein:", "Enter VM/CT ID:": "Geben Sie die VM/CT-ID ein:", "Enter ZFS pool name for the selected disk:": "Geben Sie den ZFS-Poolnamen für die ausgewählte Festplatte ein:", - "Enter a name for the mount point (used as /mnt/):": "Geben Sie einen Namen für den Mount-Punkt ein (verwendet als /mnt/):", + "Enter a name for the mount point (used as /mnt/):": "Geben Sie einen Namen für den Mountpoint ein (wird als /mnt/ verwendet):", "Enter a name for the new virtual machine:": "Geben Sie einen Namen für die neue virtuelle Maschine ein:", "Enter a non-empty password or access token on a single line.": "Geben Sie in einer einzelnen Zeile ein nicht leeres Passwort oder Zugriffstoken ein.", "Enter a number, or write or paste a command:": "Geben Sie eine Zahl ein oder schreiben oder fügen Sie einen Befehl ein:", @@ -2183,7 +2183,7 @@ "Example: pct stop 110": "Beispiel: pct stop 110", "Example: pct stop 114": "Beispiel: pct stop 114", "Example: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup": "Beispiel: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup", - "Example: pvesm path local-lvm:vm-114-disk-0": "Beispiel: pvesm-Pfad local-lvm:vm-114-disk-0", + "Example: pvesm path local-lvm:vm-114-disk-0": "Beispiel: pvesm path local-lvm:vm-114-disk-0", "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf": "Beispiel: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf", "Examples:": "Beispiele:", "Execute destructive rollback?": "Destruktives Rollback ausführen?", @@ -2495,7 +2495,7 @@ "For a consistent export, the VM should be stopped.": "Für einen konsistenten Export sollte die VM gestoppt werden.", "For any question, issue or bug report, please use the official repository": "Für Fragen, Probleme oder Fehlerberichte nutzen Sie bitte das offizielle Repository", "For dedicated GPUs, FLR is required by this policy to reduce VM": "Für dedizierte GPUs ist gemäß dieser Richtlinie FLR erforderlich, um VM zu reduzieren", - "For example: pct enter 101": "Beispiel: pct Geben Sie 101 ein", + "For example: pct enter 101": "Zum Beispiel: pct enter 101", "For guest shares add: --options guest": "Für Gastfreigaben hinzufügen: --options Guest", "For security reasons, the system cannot format it.": "Aus Sicherheitsgründen kann das System es nicht formatieren.", "For unprivileged containers, use instead:": "Verwenden Sie für nichtprivilegierte Container stattdessen:", @@ -2907,8 +2907,8 @@ "If notifications are enabled (Telegram/Discord/ntfy/...), you will receive a \"Host restore finished\" message when all background tasks complete.": "Wenn Benachrichtigungen aktiviert sind (Telegram/Discord/ntfy/...), erhalten Sie die Meldung „Host-Wiederherstellung abgeschlossen“, wenn alle Hintergrundaufgaben abgeschlossen sind.", "If passthrough fails on Windows: install RadeonResetBugFix.": "Wenn Passthrough unter Windows fehlschlägt: Installieren Sie RadeonResetBugFix.", "If path not accessible (ZFS/BTRFS):": "Wenn der Pfad nicht zugänglich ist (ZFS/BTRFS):", - "If pvesm path returned a DEVICE (LVM):": "Wenn der pvesm-Pfad ein GERÄT (LVM) zurückgegeben hat:", - "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Wenn der pvesm-Pfad ein VERZEICHNIS (ZFS/BTRFS) zurückgegeben hat:", + "If pvesm path returned a DEVICE (LVM):": "Wenn pvesm path ein GERÄT (LVM) zurückgegeben hat:", + "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Wenn pvesm path ein VERZEICHNIS (ZFS/BTRFS) zurückgegeben hat:", "If repositories error:": "Bei Repository-Fehler:", "If the container had already been changed, its previous native backup is restored. Shared host directories are not reverted. The disks of the failed attempt are removed when the cleanup succeeds.": "Wenn der Container bereits geändert wurde, wird sein vorheriges natives Backup wiederhergestellt. Freigegebene Hostverzeichnisse werden nicht zurückgesetzt. Die Festplatten des fehlgeschlagenen Versuchs werden entfernt, wenn die Bereinigung erfolgreich ist.", "If the host freezes, remove hostpci entries from": "Wenn der Host einfriert, entfernen Sie die Hostpci-Einträge aus", @@ -3225,7 +3225,7 @@ "Interrupted operation:": "Unterbrochene operation:", "Interrupted stack operation found": "Unterbrochener Stapel operation gefunden", "Invalid 'proxmox-ve' candidate (not 9.x or none). Please verify your repository configuration and network, then retry.": "Ungültiger „proxmox-ve“-Kandidat (nicht 9.x oder keiner). Bitte überprüfen Sie Ihre Repository-Konfiguration und Ihr Netzwerk und versuchen Sie es dann erneut.", - "Invalid ALLOWED_HOSTS value": "Wert für ungültig ALLOWED HOSTs", + "Invalid ALLOWED_HOSTS value": "Ungültiger Wert für ALLOWED_HOSTS", "Invalid Home Assistant OS check timeout": "Invalid Home Assistant OS Check Timeout", "Invalid ID": "Ungültige ID", "Invalid IPv4 address": "Ungültige IPv4-Adresse", @@ -3460,7 +3460,7 @@ "Keyfile removed.": "Schlüsseldatei entfernt.", "Keyrings method failed; trying apt-key fallback": "Keyrings-Methode fehlgeschlagen; Ich versuche einen Apt-Key-Fallback", "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. Geben Sie den Container ein mit: pct enter {main vmid}", + "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.", "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.", @@ -3531,7 +3531,7 @@ "Limiting size and optimizing journald": "Begrenzung der Größe und Optimierung des Journals", "Limiting size and optimizing journald...": "Größe begrenzen und Journal optimieren...", "Limnoria A robust, full-featured, and user/programmer-friendly Python IRC bot, with many existing plugins. Successor of the well-known Supybot.": "Limnoria Ein robuster, voll ausgestatteter und benutzerfreundlicher Python IRC-Bot mit vielen vorhandenen Plugins. Nachfolger des bekannten Supybot.", - "Limnoria joins no IRC network until its configuration file exists. Create it with the setup wizard from the Proxmox host: pct exec -- bash -c 'cd /config && limnoria-wizard'": "Limnoria schließt sich keinem IRC-Netzwerk an, bis seine Konfigurationsdatei existiert. Erstellen Sie es mit dem Setup-Assistenten aus dem Proxmox-Host: pct exec - bash -c 'cd /config & & limnoria-Wizard'", + "Limnoria joins no IRC network until its configuration file exists. Create it with the setup wizard from the Proxmox host: pct exec -- bash -c 'cd /config && limnoria-wizard'": "Limnoria verbindet sich erst mit einem IRC-Netzwerk, wenn die Konfigurationsdatei existiert. Erstellen Sie sie mit dem Einrichtungsassistenten auf dem Proxmox-Host: pct exec -- bash -c 'cd /config && limnoria-wizard'", "Line to paste (single line, including \"command=...\" prefix):": "Einzufügende Zeile (einzelne Zeile, einschließlich „command=…“-Präfix):", "Linux Installation Options": "Linux-Installationsoptionen", "Linux/Mac path:": "Linux/Mac-Pfad:", @@ -4263,7 +4263,7 @@ "No jobs": "Keine Jobs", "No language selected.": "Keine Sprache ausgewählt.", "No local disk storage configured.": "Kein lokaler Festplattenspeicher konfiguriert.", - "No main sources.list present (skipped)": "Keine Hauptquellenliste vorhanden (übersprungen)", + "No main sources.list present (skipped)": "Keine zentrale sources.list vorhanden (übersprungen)", "No matches for:": "Keine Treffer für:", "No matching PVE vzdump jobs were found for the PBS backend.": "Für das PBS-Backend wurden keine passenden PVE-vzdump-Jobs gefunden.", "No matching PVE vzdump jobs were found for the local archive backend.": "Für das lokale Archiv-Backend wurden keine passenden PVE-vzdump-Jobs gefunden.", @@ -6993,7 +6993,7 @@ "Transaction log:": "Transaktionsprotokoll:", "Translation files:": "Übersetzungsdateien:", "Transmission is designed for easy, powerful use. Transmission has the features you want from a BitTorrent client: encryption, a web interface, peer exchange, magnet links, DHT, µTP, UPnP and NAT-PMP port forwarding, webseed support, watch directories, tracker editing, global and per-torrent speed limits, and more.": "Transmission wurde für eine einfache und leistungsstarke Nutzung entwickelt. Transmission verfügt über die Funktionen, die Sie von einem BitTorrent-Client erwarten: Verschlüsselung, eine Web-Schnittstelle, Peer-Exchange, Magnet-Links, DHT, μTP, UPnP und NAT-PMP Port-Weiterleitung, Webseed-Unterstützung, Watch-Verzeichnisse, Tracker-Bearbeitung, globale und per-Torrent-Geschwindigkeitsbeschränkungen und mehr.", - "Tried pvesm path and manual detection methods": "Versuchte den Pvesm-Pfad und manuelle Erkennungsmethoden", + "Tried pvesm path and manual detection methods": "pvesm path und manuelle Erkennungsmethoden wurden versucht", "Trust this certificate and save it for scheduled backups?": "Diesem Zertifikat vertrauen und es für geplante Sicherungen speichern?", "Try Again": "Versuchen Sie es erneut", "Try a different search term.": "Versuchen Sie es mit einem anderen Suchbegriff.", @@ -7249,7 +7249,7 @@ "Usage:": "Verwendung:", "Use \"Check test progress\" to see results.": "Verwenden Sie „Testfortschritt prüfen“, um die Ergebnisse anzuzeigen.", "Use 'Export to file' to save it and inspect manually.": "Verwenden Sie „In Datei exportieren“, um es zu speichern und manuell zu überprüfen.", - "Use 'pct restore' / 'qmrestore' to recover their disks from your VM backups.": "Verwenden Sie „pct restart“ / „qmrestore“, um ihre Festplatten aus Ihren VM-Backups wiederherzustellen.", + "Use 'pct restore' / 'qmrestore' to recover their disks from your VM backups.": "Verwenden Sie 'pct restore' / 'qmrestore', um die Datenträger aus den VM-Sicherungen wiederherzustellen.", "Use Custom backup and uncheck the conflicting path from the list": "Verwenden Sie die benutzerdefinierte Sicherung und deaktivieren Sie den in Konflikt stehenden Pfad aus der Liste", "Use Default Settings?": "Standardeinstellungen verwenden?", "Use Download first if you want to save a copy of the current key. Continue?": "Verwenden Sie zuerst „Herunterladen“, wenn Sie eine Kopie des aktuellen Schlüssels speichern möchten. Weitermachen?", @@ -7994,8 +7994,8 @@ "partition(s). Partition table preserved.": "Partition(en). Partitionstabelle bleibt erhalten.", "pasted Compose file": "Einfügen der Compose-Datei", "paths for next boot (/etc/pve, guests, drivers, ...)": "Pfade für den nächsten Start (/etc/pve, Gäste, Treiber, ...)", - "pct exec authorization failed": "PCT-Exec-Autorisierung fehlgeschlagen", - "pct push failed. Check log:": "PCT-Push fehlgeschlagen. Protokoll prüfen:", + "pct exec authorization failed": "Autorisierung für pct exec fehlgeschlagen", + "pct push failed. Check log:": "pct push fehlgeschlagen. Protokoll prüfen:", "pending (reboot required to enumerate full group)": "ausstehend (Neustart erforderlich, um die vollständige Gruppe aufzulisten)", "phpMyAdmin is installed with arbitrary server connections enabled: the login page has a Server field where the address of the MySQL or MariaDB server is entered, together with its user and password.": "phpMyAdmin wird mit aktivierten beliebigen Serververbindungen installiert: Die Anmeldeseite hat ein Serverfeld, in dem die Adresse des MySQL- oder MariaDB-Servers zusammen mit seinem Benutzer und seinem Passwort eingegeben wird.", "pigz configuration completed": "Pigz-Konfiguration abgeschlossen", @@ -8018,8 +8018,8 @@ "pvesm command not found. This should not happen on Proxmox.": "pvesm-Befehl nicht gefunden. Dies sollte auf Proxmox nicht passieren.", "pvesm not available.": "pvesm nicht verfügbar.", "pvesm not found.": "pvesm nicht gefunden.", - "pvesm path failed, trying manual detection...": "pvesm-Pfad fehlgeschlagen, manuelle Erkennung versucht ...", - "pvesm status failed": "pvesm-Status fehlgeschlagen", + "pvesm path failed, trying manual detection...": "pvesm path fehlgeschlagen, manuelle Erkennung wird versucht...", + "pvesm status failed": "pvesm status fehlgeschlagen", "pyLoad is a Free and Open Source download manager written in Python and designed to be extremely lightweight, easily extensible and fully manageable via web.": "pyLoad ist ein Free- und Open Source-Download-Manager, der in Python geschrieben ist und extrem leicht, leicht erweiterbar und vollständig über das Web zu verwalten ist.", "pyLoad web interface": "pyLoad Web Interface", "qBittorrent WebUI password (user: admin)": "qBittorrent WebUI Passwort (Benutzer: admin)", @@ -8078,8 +8078,8 @@ "skipped (already exist)": "übersprungen (existiert bereits)", "smbclient command is not working properly.": "Der smbclient-Befehl funktioniert nicht ordnungsgemäß.", "smbclient command not found after installation.": "Der Befehl smbclient wurde nach der Installation nicht gefunden.", - "sources.list update skipped (no change)": "Aktualisierung der Quellenliste übersprungen (keine Änderung)", - "sources.list updated to Trixie": "Quellen. Liste auf Trixie aktualisiert", + "sources.list update skipped (no change)": "Aktualisierung von sources.list übersprungen (keine Änderung)", + "sources.list updated to Trixie": "sources.list auf Trixie aktualisiert", "ssh-keygen failed. Cannot create a new SSH key.": "ssh-keygen ist fehlgeschlagen. Es kann kein neuer SSH-Schlüssel erstellt werden.", "stale entry/entries for interfaces no longer present": "veralteter Eintrag/Einträge für Schnittstellen, die nicht mehr vorhanden sind", "standard performance": "Standardleistung", @@ -8098,7 +8098,7 @@ "switch_gpu_mode.sh was not found.": "switch_gpu_mode.sh wurde nicht gefunden.", "sysfs ROM dump failed — trying ACPI VFCT table...": "sysfs-ROM-Dump fehlgeschlagen – Versuch der ACPI-VFCT-Tabelle ...", "syslog-ng allows you to flexibly collect, parse, classify, rewrite and correlate logs from across your infrastructure and store or route them to log analysis tools.": "syslog-ng ermöglicht es Ihnen, Protokolle aus Ihrer gesamten Infrastruktur flexibel zu sammeln, zu analysieren, zu klassifizieren, neu zu schreiben und zu korrelieren und sie zu Protokollanalyse-Tools zu speichern oder zu leiten.", - "systemctl restart networking failed:": "systemctl-Neustart des Netzwerks fehlgeschlagen:", + "systemctl restart networking failed:": "systemctl restart networking fehlgeschlagen:", "systemd OnCalendar expression": "systemd OnCalendar-Ausdruck", "the API key was not generated on the first start": "Der API-Schlüssel wurde beim ersten Start nicht generiert", "the NVIDIA hook of the container is not available:": "der NVIDIA-Hook des Containers ist nicht verfügbar:", @@ -8152,7 +8152,7 @@ "video + recognition": "Video + Erkennung", "video only": "nur Video", "vzdump backup speed optimization completed": "vzdump-Backup-Geschwindigkeitsoptimierung abgeschlossen", - "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag baut seine Links aus der während der Installation angegebenen Adresse auf. Wenn es nicht mit der Adresse des Containers übereinstimmt, bearbeiten Sie lxc.environment. Laufzeit: SYMFONY ENV DOMAIN NAME in /etc/pve/lxc/.conf mit angehaltenem Container und erneut starten.", + "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag erzeugt seine Links aus der bei der Installation angegebenen Adresse. Passt diese nicht zur Container-Adresse, stoppen Sie den Container, bearbeiten Sie lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf und starten Sie ihn erneut.", "wallabag listens on port 80 of the container and stores its data in SQLite.": "wallabag hört auf Port 80 des Containers und speichert seine Daten in SQLite.", "wallabag web interface": "wallabag Web-Schnittstelle", "was": "war", diff --git a/lang/es.json b/lang/es.json index b54e843c..0638f139 100644 --- a/lang/es.json +++ b/lang/es.json @@ -320,7 +320,7 @@ "After confirming, you will be asked to choose the NVIDIA driver version to install.": "Después de confirmar, se le pedirá que elija la versión del controlador NVIDIA para instalar.", "After detailed analysis, no changes are needed.": "Después de un análisis detallado, no se necesitan cambios.", "After detailed analysis, no cleanup is needed.": "Después de un análisis detallado, no es necesaria ninguna limpieza.", - "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Después de iniciar sesión, ejecute: ip a para obtener la dirección IP.\nLuego, ingrese esa dirección IP en su navegador web de esta manera:\n http://DIRECCIÓN_IP\n\nEsto abrirá el panel de Umbral OS.", + "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Después de iniciar sesión, ejecuta ip a para obtener la dirección IP.\nIntroduce esa dirección en el navegador de esta forma:\n http://IP_ADDRESS\n\nSe abrirá el panel de Umbral OS.", "After pasting on the server, ensure the file is chmod 600 and owned by": "Después de pegarlo en el servidor, asegúrese de que el archivo sea chmod 600 y sea propiedad de", "After reboot the system will be fully accessible (SSH, web UI, login), but the following components will be reinstalled in BACKGROUND — until they finish, commands like nvidia-smi may not yet be available:": "Después de reiniciar, el sistema será completamente accesible (SSH, interfaz web e inicio de sesión), pero los siguientes componentes se reinstalarán EN SEGUNDO PLANO; hasta que finalicen, es posible que comandos como nvidia-smi aún no estén disponibles:", "After reboot, these components will reinstall in background:": "Después de reiniciar, estos componentes se reinstalarán en segundo plano:", @@ -1641,8 +1641,8 @@ "Default Proxmox logo applied": "Logotipo predeterminado de Proxmox aplicado", "Default is none (video-only passthrough). Use SPACE to toggle selections.": "Por defecto, ninguno (passthrough solo de vídeo). Use ESPACIO para marcar o desmarcar.", "Default journald configuration restored": "Configuración de diario predeterminada restaurada", - "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "La ubicación predeterminada es /mnt/. El recurso compartido se montará aquí en el host con permisos abiertos para que un LXC sin privilegios pueda vincularlo, montarlo y escribir en él. Para acceder a LXC, vincule esta ruta con LXC Mount Manager.", - "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "La ubicación predeterminada es /mnt/. El recurso compartido se montará aquí en el host. Utilice esta ruta en /etc/fstab. Para acceder a LXC, vincule esta ruta con LXC Mount Manager.", + "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "La ubicación predeterminada es /mnt/. El recurso compartido se montará aquí, en el host, con permisos abiertos para que un LXC sin privilegios pueda montarlo mediante un bind mount y escribir en él. Usa LXC Mount Manager para crear ese montaje en el LXC.", + "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "La ubicación predeterminada es /mnt/. El recurso compartido se montará aquí, en el host. Usa esta ruta en /etc/fstab. Para acceder desde un LXC, crea un bind mount de esta ruta con LXC Mount Manager.", "Default login": "Acceso predeterminado", "Default options": "Opciones predeterminadas", "Default options read/write": "Opciones predeterminadas lectura/escritura", @@ -2033,7 +2033,7 @@ "Enter VM ID:": "Introduzca el ID de la máquina virtual:", "Enter VM/CT ID:": "Introduzca el ID de VM/CT:", "Enter ZFS pool name for the selected disk:": "Ingrese el nombre del grupo ZFS para el disco seleccionado:", - "Enter a name for the mount point (used as /mnt/):": "Ingrese un nombre para el punto de montaje (usado como /mnt/):", + "Enter a name for the mount point (used as /mnt/):": "Introduce un nombre para el punto de montaje (se usará como /mnt/):", "Enter a name for the new virtual machine:": "Ingrese un nombre para la nueva máquina virtual:", "Enter a non-empty password or access token on a single line.": "Escribe una contraseña o un token de acceso, en una sola línea.", "Enter a number, or write or paste a command:": "Ingrese un número, o escriba o pegue un comando:", @@ -2176,11 +2176,11 @@ "Example: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak": "Ejemplo: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak", "Example: daily or Mon..Fri 03:00": "Ejemplo: diario o lunes a viernes 03:00", "Example: mkdir -p /tmp/lxc_convert_114": "Ejemplo: mkdir -p /tmp/lxc_convert_114", - "Example: pct config 110 | grep unprivileged": "Ejemplo: configuración pct 110 | grep sin privilegios", - "Example: pct stop 110": "Ejemplo: parada pct 110", - "Example: pct stop 114": "Ejemplo: parada pct 114", + "Example: pct config 110 | grep unprivileged": "Ejemplo: pct config 110 | grep unprivileged", + "Example: pct stop 110": "Ejemplo: pct stop 110", + "Example: pct stop 114": "Ejemplo: pct stop 114", "Example: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup": "Ejemplo: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup", - "Example: pvesm path local-lvm:vm-114-disk-0": "Ejemplo: ruta pvesm local-lvm:vm-114-disk-0", + "Example: pvesm path local-lvm:vm-114-disk-0": "Ejemplo: pvesm path local-lvm:vm-114-disk-0", "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf": "Ejemplo: sed -i '/^sin privilegios: 1/d' /etc/pve/lxc/110.conf", "Examples:": "Ejemplos:", "Execute destructive rollback?": "¿Ejecutar el rollback destructivo?", @@ -2492,7 +2492,7 @@ "For a consistent export, the VM should be stopped.": "Para una exportación consistente, se debe detener la VM.", "For any question, issue or bug report, please use the official repository": "Para cualquier pregunta, problema o informe de error, utilice el repositorio oficial", "For dedicated GPUs, FLR is required by this policy to reduce VM": "Para GPU dedicadas, esta política requiere FLR para reducir la VM", - "For example: pct enter 101": "Por ejemplo: pct introduzca 101", + "For example: pct enter 101": "Por ejemplo: pct enter 101", "For guest shares add: --options guest": "Para recursos compartidos de invitado añada: --options guest", "For security reasons, the system cannot format it.": "Por razones de seguridad, el sistema no puede formatearlo.", "For unprivileged containers, use instead:": "Para contenedores sin privilegios, utilice en su lugar:", @@ -5234,7 +5234,7 @@ "Replace": "Reemplazar", "Replace with the actual ID.": "Reemplace con el ID real.", "Replace with your actual container ID": "Reemplace con su ID de contenedor real", - "Replace with the value from step 4": "Reemplace con el valor del paso 4", + "Replace with the value from step 4": "Sustituye por el valor del paso 4", "Replace invalid port(s)": "Reemplazar puerto(s) no válido(s)", "Replace it?": "¿Reemplazarlo?", "Replace paths with your actual values from step 5": "Reemplace las rutas con sus valores reales del paso 5", @@ -7967,7 +7967,7 @@ "partition(s). Partition table preserved.": "partición(es). Se conserva la tabla de particiones.", "pasted Compose file": "archivo Compose pegado", "paths for next boot (/etc/pve, guests, drivers, ...)": "rutas para el próximo arranque (/etc/pve, VM y CT, controladores, ...)", - "pct exec authorization failed": "error en la autorización ejecutiva de PCT", + "pct exec authorization failed": "Ha fallado la autorización de pct exec", "pct push failed. Check log:": "pct push falló. Revise el log:", "pending (reboot required to enumerate full group)": "pendiente (es necesario reiniciar para enumerar el grupo completo)", "phpMyAdmin is installed with arbitrary server connections enabled: the login page has a Server field where the address of the MySQL or MariaDB server is entered, together with its user and password.": "phpMyAdmin se instala con conexiones de servidor arbitrarias habilitadas: la página de inicio de sesión tiene un campo Server donde se introduce la dirección del servidor MySQL o MariaDB, junto con su usuario y contraseña.", @@ -7991,8 +7991,8 @@ "pvesm command not found. This should not happen on Proxmox.": "Comando pvesm no encontrado. Esto no debería suceder en Proxmox.", "pvesm not available.": "pvesm no disponible.", "pvesm not found.": "pvesm no encontrado.", - "pvesm path failed, trying manual detection...": "La ruta pvesm falló, intentando la detección manual...", - "pvesm status failed": "el estado de pvesm falló", + "pvesm path failed, trying manual detection...": "Ha fallado pvesm path; intentando la detección manual...", + "pvesm status failed": "Ha fallado pvesm status", "pyLoad is a Free and Open Source download manager written in Python and designed to be extremely lightweight, easily extensible and fully manageable via web.": "pyLoad es un gestor de descargas libre y de código abierto escrito en Python, diseñado para ser muy ligero, fácil de ampliar y totalmente gestionable por web.", "pyLoad web interface": "Interfaz web de pyLoad", "qBittorrent WebUI password (user: admin)": "contraseña de qBittorrent WebUI (usuario: admin)", @@ -8052,7 +8052,7 @@ "smbclient command is not working properly.": "El comando smbclient no funciona correctamente.", "smbclient command not found after installation.": "El comando smbclient no se encuentra después de la instalación.", "sources.list update skipped (no change)": "Actualización de sources.list omitida (sin cambios)", - "sources.list updated to Trixie": "fuentes.lista actualizada a Trixie", + "sources.list updated to Trixie": "sources.list actualizado a Trixie", "ssh-keygen failed. Cannot create a new SSH key.": "ssh-keygen falló. No se puede crear una nueva clave SSH.", "stale entry/entries for interfaces no longer present": "entrada(s) obsoleta(s) de interfaces que ya no existen", "standard performance": "rendimiento estándar", diff --git a/lang/fr.json b/lang/fr.json index 4a83f430..d9509728 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -301,7 +301,7 @@ "Address of the database server (mysql and pgsql only)": "Adresse du serveur de base de données (mysql et pgsql uniquement)", "Address of the oci-compose file": "Adresse du fichier oci-compose", "Address to reach Pydio Cells (https://domain or https://IP:8080)": "Adresse pour atteindre Pydio Cells (https://domaine ou https://IP:8080)", - "Address used to reach wallabag (http://IP or https://wallabag.example.com)": "Adresse utilisée pour atteindre wallabag (http://IP ou https://wallabag.exemple.com)", + "Address used to reach wallabag (http://IP or https://wallabag.example.com)": "Adresse d’accès à wallabag (http://IP ou https://wallabag.example.com)", "Addresses to update: ipv4, ipv6 or both (uses an external service)": "Adresses à mettre à jour: ipv4, ipv6 ou les deux (utilise un service externe)", "Adguardhome Sync web interface": "Interface web Adguardhome Sync", "Adguardhome-sync is a tool to synchronize AdGuardHome config to replica instances.": "Adguardhome-sync est un outil pour synchroniser la configuration AdGuardHome pour reproduire des instances.", @@ -322,7 +322,7 @@ "After confirming, you will be asked to choose the NVIDIA driver version to install.": "Après confirmation, il vous sera demandé de choisir la version du pilote NVIDIA à installer.", "After detailed analysis, no changes are needed.": "Après une analyse détaillée, aucune modification n’est nécessaire.", "After detailed analysis, no cleanup is needed.": "Après une analyse détaillée, aucun nettoyage n’est nécessaire.", - "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Après vous être connecté, exécutez : ip a pour obtenir l'adresse IP.\nEnsuite, saisissez cette adresse IP dans votre navigateur Web comme ceci :\n http://ADRESSE_IP\n\nCela ouvrira le tableau de bord Umbral OS.", + "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Après la connexion, exécutez ip a pour obtenir l’adresse IP.\nSaisissez ensuite cette adresse dans votre navigateur sous cette forme :\n http://IP_ADDRESS\n\nLe tableau de bord Umbral OS s’ouvrira.", "After pasting on the server, ensure the file is chmod 600 and owned by": "Après avoir collé sur le serveur, assurez-vous que le fichier est chmod 600 et appartient à", "After reboot the system will be fully accessible (SSH, web UI, login), but the following components will be reinstalled in BACKGROUND — until they finish, commands like nvidia-smi may not yet be available:": "Après le redémarrage, le système sera entièrement accessible (SSH, interface utilisateur Web, connexion), mais les composants suivants seront réinstallés en ARRIÈRE-PLAN — jusqu'à ce qu'ils soient terminés, des commandes comme nvidia-smi peuvent ne pas encore être disponibles :", "After reboot, these components will reinstall in background:": "Après le redémarrage, ces composants seront réinstallés en arrière-plan :", @@ -1643,8 +1643,8 @@ "Default Proxmox logo applied": "Logo Proxmox par défaut appliqué", "Default is none (video-only passthrough). Use SPACE to toggle selections.": "La valeur par défaut est aucune (passthrough vidéo uniquement). Utilisez ESPACE pour basculer les sélections.", "Default journald configuration restored": "Configuration journald par défaut restaurée", - "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "L'emplacement par défaut est /mnt/. Le partage sera monté ici sur l'hôte avec des autorisations ouvertes afin qu'un LXC non privilégié puisse y effectuer un montage en liaison et y écrire. Pour l'accès LXC, montez en liaison ce chemin avec le gestionnaire de montage LXC.", - "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "L'emplacement par défaut est /mnt/. Le partage sera monté ici sur l'hôte. Utilisez ce chemin dans /etc/fstab. Pour l'accès LXC, montez en liaison ce chemin avec le gestionnaire de montage LXC.", + "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "L’emplacement par défaut est /mnt/. Le partage sera monté ici sur l’hôte avec des permissions ouvertes, afin qu’un LXC non privilégié puisse le monter par liaison et y écrire. Utilisez LXC Mount Manager pour créer ce montage dans le LXC.", + "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "L’emplacement par défaut est /mnt/. Le partage sera monté ici sur l’hôte. Utilisez ce chemin dans /etc/fstab. Pour y accéder depuis un LXC, créez un montage lié de ce chemin avec LXC Mount Manager.", "Default login": "Connexion par défaut", "Default options": "Options par défaut", "Default options read/write": "Options par défaut lecture/écriture", @@ -1920,7 +1920,7 @@ "Dry run completed; the container and the mounts were not changed.": "Le parcours à sec est terminé; le conteneur et les supports n'ont pas été changés.", "DuckDNS subdomain without .duckdns.org (comma separated for several)": "DuckDNS sous-domaine sans .duckdns.org (comma séparé pour plusieurs)", "DuckDNS token from your account at duckdns.org": "Jeton DuckDNS de votre compte sur duckdns.org", - "DuckDNS updates the subdomain every 5 minutes. Without UPDATE_IP, DuckDNS itself detects the public IPv4 address of the request.": "DuckDNS met à jour le sous-domaine toutes les 5 minutes. Sans mise à jour, DuckDNS détecte lui-même l'adresse IPv4 publique de la requête.", + "DuckDNS updates the subdomain every 5 minutes. Without UPDATE_IP, DuckDNS itself detects the public IPv4 address of the request.": "DuckDNS met à jour le sous-domaine toutes les 5 minutes. Sans UPDATE_IP, DuckDNS détecte lui-même l’adresse IPv4 publique de la requête.", "DuckStation is a PS1 Emulator aiming for the best accuracy and game support.": "DuckStation est un émulateur PS1 visant le meilleur accuracy et le meilleur support de jeu.", "Duckdns is a free service which will point a DNS (sub domains of duckdns.org) to an IP of your choice. The service is completely free, and doesn't require reactivation or forum posts to maintain its existence.": "Duckdns est un service gratuit qui dirigera un DNS (sous-domaines de duckdns.org) vers une IP de votre choix. Le service est entièrement gratuit, et ne require réactivation ou forum posts pour maintenir son existence.", "Dumping AMD GPU ROM BIOS via sysfs...": "Vidage du BIOS ROM GPU AMD via sysfs...", @@ -2179,11 +2179,11 @@ "Example: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak": "Exemple : cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak", "Example: daily or Mon..Fri 03:00": "Exemple : tous les jours ou du lundi au vendredi à 03h00", "Example: mkdir -p /tmp/lxc_convert_114": "Exemple : mkdir -p /tmp/lxc_convert_114", - "Example: pct config 110 | grep unprivileged": "Exemple : configuration PCT 110 | grep sans privilège", - "Example: pct stop 110": "Exemple : PCT stop 110", - "Example: pct stop 114": "Exemple : PCT arrêt 114", + "Example: pct config 110 | grep unprivileged": "Exemple : pct config 110 | grep unprivileged", + "Example: pct stop 110": "Exemple : pct stop 110", + "Example: pct stop 114": "Exemple : pct stop 114", "Example: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup": "Exemple : pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup", - "Example: pvesm path local-lvm:vm-114-disk-0": "Exemple : chemin pvesm local-lvm:vm-114-disk-0", + "Example: pvesm path local-lvm:vm-114-disk-0": "Exemple : pvesm path local-lvm:vm-114-disk-0", "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf": "Exemple : sed -i '/^unprivileged : 1/d' /etc/pve/lxc/110.conf", "Examples:": "Exemples :", "Execute destructive rollback?": "Exécuter une restauration destructrice ?", @@ -2495,7 +2495,7 @@ "For a consistent export, the VM should be stopped.": "Pour une exportation cohérente, la VM doit être arrêtée.", "For any question, issue or bug report, please use the official repository": "Pour toute question, problème ou rapport de bug, veuillez utiliser le référentiel officiel", "For dedicated GPUs, FLR is required by this policy to reduce VM": "Pour les GPU dédiés, FLR est requis par cette stratégie pour réduire les VM", - "For example: pct enter 101": "Par exemple : PCT, entrez 101", + "For example: pct enter 101": "Par exemple : pct enter 101", "For guest shares add: --options guest": "Pour les partages invités, ajoutez : --options invité", "For security reasons, the system cannot format it.": "Pour des raisons de sécurité, le système ne peut pas le formater.", "For unprivileged containers, use instead:": "Pour les conteneurs non privilégiés, utilisez plutôt :", @@ -2907,8 +2907,8 @@ "If notifications are enabled (Telegram/Discord/ntfy/...), you will receive a \"Host restore finished\" message when all background tasks complete.": "Si les notifications sont activées (Telegram/Discord/ntfy/...), vous recevrez un message « Restauration de l'hôte terminée » lorsque toutes les tâches en arrière-plan seront terminées.", "If passthrough fails on Windows: install RadeonResetBugFix.": "Si le relais échoue sous Windows : installez RadeonResetBugFix.", "If path not accessible (ZFS/BTRFS):": "Si chemin non accessible (ZFS/BTRFS) :", - "If pvesm path returned a DEVICE (LVM):": "Si le chemin pvesm a renvoyé un DEVICE (LVM) :", - "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Si le chemin pvesm renvoie un RÉPERTOIRE (ZFS/BTRFS) :", + "If pvesm path returned a DEVICE (LVM):": "Si pvesm path a renvoyé un PÉRIPHÉRIQUE (LVM) :", + "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Si pvesm path a renvoyé un RÉPERTOIRE (ZFS/BTRFS) :", "If repositories error:": "Si erreur de référentiels :", "If the container had already been changed, its previous native backup is restored. Shared host directories are not reverted. The disks of the failed attempt are removed when the cleanup succeeds.": "Si le conteneur a déjà été modifié, sa sauvegarde native précédente est restaurée. Les répertoires hôtes partagés ne sont pas rétablis. Les disques de la tentative ayant échoué sont supprimés une fois le nettoyage réussi.", "If the host freezes, remove hostpci entries from": "Si l'hôte se bloque, supprimez les entrées hostpci de", @@ -3048,7 +3048,7 @@ "Initiator IQN is authorised on the target": "L'IQN initiateur est autorisé sur la cible", "Initiator IQN:": "IQN initiateur :", "Inkscape is professional quality vector graphics software which runs on Linux, Mac OS X and Windows desktop computers.": "Inkscape est un logiciel graphique vectoriel de qualité professionnelle qui fonctionne sur les ordinateurs de bureau Linux, Mac OS X et Windows.", - "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "À l'intérieur du LXC, créez l'administrateur: console kimai:utilisateur:créez VOTRE USERNAME VOTRE ROLE_SUPER_ADMIN", + "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Dans le LXC, créez l’administrateur : console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN", "Inspect disks before any action": "Inspecter les disques avant toute action", "Inspect host device nodes": "Inspecter les nœuds du périphérique hôte", "Inspect passthrough/kernel events": "Inspecter les événements de relais/noyau", @@ -3225,7 +3225,7 @@ "Interrupted operation:": "Interruption du opera:", "Interrupted stack operation found": "Pile interrompue operation trouvée", "Invalid 'proxmox-ve' candidate (not 9.x or none). Please verify your repository configuration and network, then retry.": "Candidat 'proxmox-ve' invalide (pas 9.x ou aucun). Veuillez vérifier la configuration et le réseau de votre référentiel, puis réessayez.", - "Invalid ALLOWED_HOSTS value": "Valeur HÔTES INDÉSIRABLES", + "Invalid ALLOWED_HOSTS value": "Valeur ALLOWED_HOSTS invalide", "Invalid Home Assistant OS check timeout": "Non valide Home Assistant OS check timeout", "Invalid ID": "Pièce d'identité invalide", "Invalid IPv4 address": "Adresse IPv4 non valide", @@ -3460,7 +3460,7 @@ "Keyfile removed.": "fichier clé supprimé.", "Keyrings method failed; trying apt-key fallback": "La méthode des porte-clés a échoué ; essayer la solution de secours apt-key", "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. Saisissez le conteneur avec : pct entre {main vmid}", + "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.", "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.", @@ -3512,7 +3512,7 @@ "Leave empty to keep every backup (not recommended).": "Laissez vide pour conserver chaque sauvegarde (non recommandé).", "Leave empty to use the retention of the storage.": "Laisser vide pour utiliser la rétention du stockage.", "Legacy PVE 8 .list files commented or not present": "Fichiers .list hérités PVE 8 commentés ou non présents", - "Legacy ceph.list commented or not present": "Ceph.list hérité commenté ou non présent", + "Legacy ceph.list commented or not present": "Ancien fichier ceph.list commenté ou absent", "Legacy gasket-dkms cleanup could not be verified as complete.": "Impossible de confirmer que le nettoyage de l'ancien paquet gasket-dkms est terminé.", "Legacy gasket-dkms detected": "Ancien paquet gasket-dkms détecté", "Legacy network tools (e.g., ifconfig)": "Outils réseau hérités (par exemple, ifconfig)", @@ -4263,7 +4263,7 @@ "No jobs": "Aucun emploi", "No language selected.": "Aucune langue sélectionnée.", "No local disk storage configured.": "Aucun stockage sur disque local configuré.", - "No main sources.list present (skipped)": "Aucune liste de sources principale présente (ignorée)", + "No main sources.list present (skipped)": "Aucun fichier sources.list principal présent (ignoré)", "No matches for:": "Aucune correspondance pour :", "No matching PVE vzdump jobs were found for the PBS backend.": "Aucune tâche vzdump PVE correspondante n'a été trouvée pour le backend PBS.", "No matching PVE vzdump jobs were found for the local archive backend.": "Aucune tâche vzdump PVE correspondante n'a été trouvée pour le backend d'archive local.", @@ -6992,7 +6992,7 @@ "Transaction log:": "Registre des transactions & #160;:", "Translation files:": "Fichiers de traduction :", "Transmission is designed for easy, powerful use. Transmission has the features you want from a BitTorrent client: encryption, a web interface, peer exchange, magnet links, DHT, µTP, UPnP and NAT-PMP port forwarding, webseed support, watch directories, tracker editing, global and per-torrent speed limits, and more.": "Transmission est conçu pour une utilisation facile et puissante. Transmission possède les fonctionnalités que vous souhaitez d'un client BitTorrent : cryptage, interface web, échange entre pairs, liens magnétiques, DHT, μTP, UPnP et le transfert de port NAT-PMP, support de webseed, répertoires de veille, édition de tracker, limites de vitesse globales et per-torrent, et plus encore.", - "Tried pvesm path and manual detection methods": "Chemin pvesm essayé et méthodes de détection manuelle", + "Tried pvesm path and manual detection methods": "pvesm path et les méthodes de détection manuelle ont été essayés", "Trust this certificate and save it for scheduled backups?": "Faire confiance à ce certificat et l'enregistrer pour les sauvegardes planifiées ?", "Try Again": "Essayer à nouveau", "Try a different search term.": "Essayez un autre terme de recherche.", @@ -7248,7 +7248,7 @@ "Usage:": "Utilisation :", "Use \"Check test progress\" to see results.": "Utilisez « Vérifier la progression du test » pour voir les résultats.", "Use 'Export to file' to save it and inspect manually.": "Utilisez « Exporter vers un fichier » pour l'enregistrer et l'inspecter manuellement.", - "Use 'pct restore' / 'qmrestore' to recover their disks from your VM backups.": "Utilisez « PCT Restore » / « qmrestore » pour récupérer leurs disques à partir des sauvegardes de votre VM.", + "Use 'pct restore' / 'qmrestore' to recover their disks from your VM backups.": "Utilisez 'pct restore' / 'qmrestore' pour récupérer leurs disques depuis les sauvegardes des VM.", "Use Custom backup and uncheck the conflicting path from the list": "Utilisez la sauvegarde personnalisée et décochez le chemin en conflit dans la liste", "Use Default Settings?": "Utiliser les paramètres par défaut ?", "Use Download first if you want to save a copy of the current key. Continue?": "utilisez d'abord Télécharger si vous souhaitez enregistrer une copie de la clé actuelle. Continuer?", @@ -7993,8 +7993,8 @@ "partition(s). Partition table preserved.": "partition(s). Table de partition conservée.", "pasted Compose file": "fichier Compose collé", "paths for next boot (/etc/pve, guests, drivers, ...)": "chemins pour le prochain démarrage (/etc/pve, invités, pilotes, ...)", - "pct exec authorization failed": "échec de l'autorisation d'exécution PCT", - "pct push failed. Check log:": "La poussée PCT a échoué. Journal de vérification :", + "pct exec authorization failed": "Échec de l’autorisation pour pct exec", + "pct push failed. Check log:": "Échec de pct push. Consultez le journal :", "pending (reboot required to enumerate full group)": "en attente (redémarrage requis pour énumérer le groupe complet)", "phpMyAdmin is installed with arbitrary server connections enabled: the login page has a Server field where the address of the MySQL or MariaDB server is entered, together with its user and password.": "phpMyAdmin est installé avec des connexions de serveur arbitraires activées : la page de connexion a un champ Serveur où l'adresse du serveur MySQL ou MariaDB est saisie, ainsi que son utilisateur et son mot de passe.", "pigz configuration completed": "configuration pigz terminée", @@ -8017,8 +8017,8 @@ "pvesm command not found. This should not happen on Proxmox.": "Commande pvesm introuvable. Cela ne devrait pas arriver sur Proxmox.", "pvesm not available.": "pvesm non disponible.", "pvesm not found.": "pvesm introuvable.", - "pvesm path failed, trying manual detection...": "Échec du chemin pvesm, tentative de détection manuelle...", - "pvesm status failed": "le statut pvesm a échoué", + "pvesm path failed, trying manual detection...": "Échec de pvesm path, tentative de détection manuelle...", + "pvesm status failed": "Échec de pvesm status", "pyLoad is a Free and Open Source download manager written in Python and designed to be extremely lightweight, easily extensible and fully manageable via web.": "pyLoad est un gestionnaire de téléchargement gratuit et Open Source écrit en Python et conçu pour être extrêmement léger, facilement extensible et entièrement gérable via le web.", "pyLoad web interface": "PyLoad interface web", "qBittorrent WebUI password (user: admin)": "qBittorrent Mot de passe WebUI (utilisateur: admin)", @@ -8097,7 +8097,7 @@ "switch_gpu_mode.sh was not found.": "switch_gpu_mode.sh n'a pas été trouvé.", "sysfs ROM dump failed — trying ACPI VFCT table...": "Échec du vidage de la ROM sysfs - tentative de la table ACPI VFCT...", "syslog-ng allows you to flexibly collect, parse, classify, rewrite and correlate logs from across your infrastructure and store or route them to log analysis tools.": "syslog-ng vous permet de collecter, d'analyser, de classer, de réécrire et de corréler les logs de toute votre infrastructure et de les stocker ou de les acheminer vers des outils d'analyse de log.", - "systemctl restart networking failed:": "échec du redémarrage du réseau systemctl :", + "systemctl restart networking failed:": "Échec de systemctl restart networking :", "systemd OnCalendar expression": "expression systemd OnCalendar", "the API key was not generated on the first start": "la clé API n'a pas été générée au premier démarrage", "the NVIDIA hook of the container is not available:": "le hook NVIDIA du conteneur n'est pas disponible :", @@ -8151,7 +8151,7 @@ "video + recognition": "vidéo + reconnaissance", "video only": "vidéo uniquement", "vzdump backup speed optimization completed": "Optimisation de la vitesse de sauvegarde de vzdump terminée", - "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag construit ses liens à partir de l'adresse donnée pendant l'installation. S'il ne correspond pas à l'adresse du conteneur, modifier lxc.environnement. temps d'exécution: SYMFONY ENV DOMAIN NAME dans /etc/pve/lxc/.conf avec le conteneur arrêté, et recommencer.", + "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag construit ses liens avec l’adresse indiquée à l’installation. Si elle ne correspond pas à celle du conteneur, arrêtez-le, modifiez lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME dans /etc/pve/lxc/.conf, puis redémarrez-le.", "wallabag listens on port 80 of the container and stores its data in SQLite.": "wallabag écoute sur le port 80 du conteneur et stocke ses données dans SQLite.", "wallabag web interface": "Interface web wallabag", "was": "était", diff --git a/lang/pt.json b/lang/pt.json index 41fb2fd7..ade30ee9 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -138,7 +138,7 @@ "ALL DATA ON": "TODOS OS DADOS EM", "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!": "TODOS OS DADOS NESTE DISCO SERÃO PERMANENTEMENTE PERDIDOS!", "ALL Utilities": "TODOS os utilitários", - "ALLOWED_HOSTS cannot contain line breaks": "Allowed hosts não pode conter quebras de linha", + "ALLOWED_HOSTS cannot contain line breaks": "ALLOWED_HOSTS não pode conter quebras de linha", "AList initial login": "Login inicial do AList", "AMD (ROCm)": "AMD (ROCm)", "AMD (VA-API and ROCm detection)": "AMD (detecção VA-API e ROCm)", @@ -1643,8 +1643,8 @@ "Default Proxmox logo applied": "Logotipo padrão do Proxmox aplicado", "Default is none (video-only passthrough). Use SPACE to toggle selections.": "O padrão é nenhum (passagem somente de vídeo). Use ESPAÇO para alternar as seleções.", "Default journald configuration restored": "Configuração padrão do diário restaurada", - "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "O local padrão é /mnt/. O compartilhamento será montado aqui no host com permissões abertas para que um LXC sem privilégios possa montar e gravar nele. Para acesso LXC, monte este caminho com o LXC Mount Manager.", - "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "O local padrão é /mnt/. O compartilhamento será montado aqui no host. Use este caminho em /etc/fstab. Para acesso LXC, monte este caminho com o LXC Mount Manager.", + "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "A localização predefinida é /mnt/. A partilha será montada aqui no host com permissões abertas, para que um LXC sem privilégios a possa montar por bind mount e nela escrever. Utilize o LXC Mount Manager para criar essa montagem no LXC.", + "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "A localização predefinida é /mnt/. A partilha será montada aqui no host. Utilize este caminho em /etc/fstab. Para aceder a partir de um LXC, crie um bind mount deste caminho com o LXC Mount Manager.", "Default login": "Utilizador por omissão", "Default options": "Opções padrão", "Default options read/write": "Opções padrão leitura/gravação", @@ -2035,7 +2035,7 @@ "Enter VM ID:": "Insira o ID da VM:", "Enter VM/CT ID:": "Insira o ID da VM/CT:", "Enter ZFS pool name for the selected disk:": "Insira o nome do pool ZFS para o disco selecionado:", - "Enter a name for the mount point (used as /mnt/):": "Insira um nome para o ponto de montagem (usado como /mnt/):", + "Enter a name for the mount point (used as /mnt/):": "Introduza um nome para o ponto de montagem (será usado como /mnt/):", "Enter a name for the new virtual machine:": "Insira um nome para a nova máquina virtual:", "Enter a non-empty password or access token on a single line.": "Insira uma senha ou token de acesso não vazio em uma única linha.", "Enter a number, or write or paste a command:": "Digite um número ou escreva ou cole um comando:", @@ -2179,11 +2179,11 @@ "Example: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak": "Exemplo: cp /etc/pve/lxc/114.conf /etc/pve/lxc/114.conf.bak", "Example: daily or Mon..Fri 03:00": "Exemplo: diariamente ou Seg..Sex 03:00", "Example: mkdir -p /tmp/lxc_convert_114": "Exemplo: mkdir -p /tmp/lxc_convert_114", - "Example: pct config 110 | grep unprivileged": "Exemplo: configuração pct 110 | grep sem privilégios", + "Example: pct config 110 | grep unprivileged": "Exemplo: pct config 110 | grep unprivileged", "Example: pct stop 110": "Exemplo: pct stop 110", - "Example: pct stop 114": "Exemplo: parada pct 114", + "Example: pct stop 114": "Exemplo: pct stop 114", "Example: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup": "Exemplo: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup", - "Example: pvesm path local-lvm:vm-114-disk-0": "Exemplo: caminho pvesm local-lvm:vm-114-disk-0", + "Example: pvesm path local-lvm:vm-114-disk-0": "Exemplo: pvesm path local-lvm:vm-114-disk-0", "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf": "Exemplo: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf", "Examples:": "Exemplos:", "Execute destructive rollback?": "Executar reversão destrutiva?", @@ -2495,7 +2495,7 @@ "For a consistent export, the VM should be stopped.": "Para uma exportação consistente, a VM deve ser interrompida.", "For any question, issue or bug report, please use the official repository": "Para qualquer dúvida, problema ou relatório de bug, use o repositório oficial", "For dedicated GPUs, FLR is required by this policy to reduce VM": "Para GPUs dedicadas, o FLR é exigido por esta política para reduzir VM", - "For example: pct enter 101": "Por exemplo: pct digite 101", + "For example: pct enter 101": "Por exemplo: pct enter 101", "For guest shares add: --options guest": "Para compartilhamentos de convidados, adicione: --options guest", "For security reasons, the system cannot format it.": "Por motivos de segurança, o sistema não pode formatá-lo.", "For unprivileged containers, use instead:": "Para contêineres sem privilégios, use:", @@ -2907,8 +2907,8 @@ "If notifications are enabled (Telegram/Discord/ntfy/...), you will receive a \"Host restore finished\" message when all background tasks complete.": "Se as notificações estiverem habilitadas (Telegram/Discord/ntfy/...), você receberá uma mensagem \"Restauração do host concluída\" quando todas as tarefas em segundo plano forem concluídas.", "If passthrough fails on Windows: install RadeonResetBugFix.": "Se a passagem falhar no Windows: instale RadeonResetBugFix.", "If path not accessible (ZFS/BTRFS):": "Se o caminho não estiver acessível (ZFS/BTRFS):", - "If pvesm path returned a DEVICE (LVM):": "Se o caminho pvesm retornou um DEVICE (LVM):", - "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Se o caminho pvesm retornou um DIRETÓRIO (ZFS/BTRFS):", + "If pvesm path returned a DEVICE (LVM):": "Se pvesm path devolveu um DISPOSITIVO (LVM):", + "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Se pvesm path devolveu um DIRETÓRIO (ZFS/BTRFS):", "If repositories error:": "Se houver erro nos repositórios:", "If the container had already been changed, its previous native backup is restored. Shared host directories are not reverted. The disks of the failed attempt are removed when the cleanup succeeds.": "Se o contêiner já tiver sido alterado, seu backup nativo anterior será restaurado. Os diretórios de host compartilhados não são revertidos. Os discos da tentativa fracassada serão removidos quando a limpeza for bem-sucedida.", "If the host freezes, remove hostpci entries from": "Se o host congelar, remova as entradas hostpci de", @@ -3048,7 +3048,7 @@ "Initiator IQN is authorised on the target": "O iniciador IQN está autorizado no alvo", "Initiator IQN:": "IQN do iniciador:", "Inkscape is professional quality vector graphics software which runs on Linux, Mac OS X and Windows desktop computers.": "Inkscape é um software gráfico vetorial de qualidade profissional que funciona em computadores desktop Linux, Mac OS X e Windows.", - "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Dentro do LXC, crie o administrador: console kimai:user:create Your USERNAME Your EMAIL ROLE_SUPER_ADMIN", + "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Dentro do LXC, crie o administrador: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN", "Inspect disks before any action": "Inspecione os discos antes de qualquer ação", "Inspect host device nodes": "Inspecione os nós do dispositivo host", "Inspect passthrough/kernel events": "Inspecionar eventos de passagem/kernel", @@ -3225,7 +3225,7 @@ "Interrupted operation:": "operation interrompida:", "Interrupted stack operation found": "Pilha interrompida operation encontrada", "Invalid 'proxmox-ve' candidate (not 9.x or none). Please verify your repository configuration and network, then retry.": "Candidato 'proxmox-ve' inválido (não 9.x ou nenhum). Verifique a configuração e a rede do seu repositório e tente novamente.", - "Invalid ALLOWED_HOSTS value": "Valor Allowed hosts inválido", + "Invalid ALLOWED_HOSTS value": "Valor de ALLOWED_HOSTS inválido", "Invalid Home Assistant OS check timeout": "Tempo de verificação do sistema operacional Home Assistant inválido", "Invalid ID": "ID inválido", "Invalid IPv4 address": "Endereço IPv4 inválido", @@ -3460,7 +3460,7 @@ "Keyfile removed.": "arquivo-chave removido.", "Keyrings method failed; trying apt-key fallback": "O método dos chaveiros falhou; tentando substituto do apt-key", "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}": "Kimai não tem conta padrão. Digite o recipiente com: pct enter {main vmid}", + "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.", "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.", @@ -3512,7 +3512,7 @@ "Leave empty to keep every backup (not recommended).": "Deixe em branco para manter todos os backups (não recomendado).", "Leave empty to use the retention of the storage.": "Deixe em branco para usar a retenção do armazenamento.", "Legacy PVE 8 .list files commented or not present": "Arquivos .list PVE 8 legados comentados ou não presentes", - "Legacy ceph.list commented or not present": "Ceph.list legado comentado ou não presente", + "Legacy ceph.list commented or not present": "ceph.list antigo comentado ou inexistente", "Legacy gasket-dkms cleanup could not be verified as complete.": "Não foi possível confirmar a conclusão da limpeza do pacote gasket-dkms legado.", "Legacy gasket-dkms detected": "gasket-dkms legado detetado", "Legacy network tools (e.g., ifconfig)": "Ferramentas de rede legadas (por exemplo, ifconfig)", @@ -4263,7 +4263,7 @@ "No jobs": "Sem empregos", "No language selected.": "Nenhum idioma selecionado.", "No local disk storage configured.": "Nenhum armazenamento em disco local configurado.", - "No main sources.list present (skipped)": "Nenhuma fonte principal.list presente (ignorada)", + "No main sources.list present (skipped)": "Não existe um sources.list principal (ignorado)", "No matches for:": "Nenhuma correspondência para:", "No matching PVE vzdump jobs were found for the PBS backend.": "Nenhum trabalho PVE vzdump correspondente foi encontrado para o backend do PBS.", "No matching PVE vzdump jobs were found for the local archive backend.": "Nenhum trabalho PVE vzdump correspondente foi encontrado para o back-end do arquivo local.", @@ -6993,7 +6993,7 @@ "Transaction log:": "Registo de transacções:", "Translation files:": "Arquivos de tradução:", "Transmission is designed for easy, powerful use. Transmission has the features you want from a BitTorrent client: encryption, a web interface, peer exchange, magnet links, DHT, µTP, UPnP and NAT-PMP port forwarding, webseed support, watch directories, tracker editing, global and per-torrent speed limits, and more.": "Transmission é projetado para uso fácil e poderoso. Transmission tem os recursos que você deseja de um cliente BitTorrent: criptografia, uma interface web, troca de pares, links magnéticos, DHT, μTP, encaminhamento de portas UPnP e NAT-PMP, suporte a webseed, diretórios de relógio, edição de rastreadores, limites de velocidade globais e per-torrent, e muito mais.", - "Tried pvesm path and manual detection methods": "Tentei caminho pvesm e métodos de detecção manual", + "Tried pvesm path and manual detection methods": "Foram tentados pvesm path e os métodos de deteção manual", "Trust this certificate and save it for scheduled backups?": "Confiar neste certificado e salvá-lo para backups agendados?", "Try Again": "Tente novamente", "Try a different search term.": "Experimente um termo de pesquisa diferente.", @@ -7213,7 +7213,7 @@ "Updates file is empty or unreadable.": "O arquivo de atualizações está vazio ou ilegível.", "Updating": "Actualização", "Updating APT package lists...": "Atualizando listas de pacotes APT...", - "Updating Debian Bookworm → Trixie in sources.list...": "Atualizando Debian Bookworm → Trixie em fontes.list...", + "Updating Debian Bookworm → Trixie in sources.list...": "A atualizar Debian Bookworm → Trixie em sources.list...", "Updating Figurine binary...": "Atualizando o binário da estatueta...", "Updating Lynis to the latest version...": "Atualizando Lynis para a versão mais recente...", "Updating PVE application manager...": "Atualizando o gerenciador de aplicativos PVE...", @@ -8018,8 +8018,8 @@ "pvesm command not found. This should not happen on Proxmox.": "Comando pvesm não encontrado. Isso não deveria acontecer no Proxmox.", "pvesm not available.": "pvesm não disponível.", "pvesm not found.": "pvesm não encontrado.", - "pvesm path failed, trying manual detection...": "caminho pvesm falhou, tentando detecção manual...", - "pvesm status failed": "status pvesm falhou", + "pvesm path failed, trying manual detection...": "O pvesm path falhou; a tentar a deteção manual...", + "pvesm status failed": "O pvesm status falhou", "pyLoad is a Free and Open Source download manager written in Python and designed to be extremely lightweight, easily extensible and fully manageable via web.": "pyLoad é um gerenciador de download livre e Open Source escrito em Python e projetado para ser extremamente leve, facilmente extensível e totalmente gerenciável via web.", "pyLoad web interface": "interface web pyLoad", "qBittorrent WebUI password (user: admin)": "Senha do WebUI do qBittorrent (utilizador: administrador)", @@ -8079,7 +8079,7 @@ "smbclient command is not working properly.": "O comando smbclient não está funcionando corretamente.", "smbclient command not found after installation.": "Comando smbclient não encontrado após a instalação.", "sources.list update skipped (no change)": "Atualização de sources.list ignorada (sem alteração)", - "sources.list updated to Trixie": "fontes.list atualizado para Trixie", + "sources.list updated to Trixie": "sources.list atualizado para Trixie", "ssh-keygen failed. Cannot create a new SSH key.": "ssh-keygen falhou. Não é possível criar uma nova chave SSH.", "stale entry/entries for interfaces no longer present": "entradas/entradas obsoletas para interfaces não estão mais presentes", "standard performance": "desempenho padrão", @@ -8098,7 +8098,7 @@ "switch_gpu_mode.sh was not found.": "switch_gpu_mode.sh não foi encontrado.", "sysfs ROM dump failed — trying ACPI VFCT table...": "Falha no despejo de ROM do sysfs - tentando tabela ACPI VFCT...", "syslog-ng allows you to flexibly collect, parse, classify, rewrite and correlate logs from across your infrastructure and store or route them to log analysis tools.": "o syslog-ng permite coletar, analisar, classificar, reescrever e correlacionar logs de toda sua infraestrutura e armazená-los ou roteá-los para ferramentas de análise de log.", - "systemctl restart networking failed:": "falha na reinicialização da rede do systemctl:", + "systemctl restart networking failed:": "O systemctl restart networking falhou:", "systemd OnCalendar expression": "Expressão Systemd OnCalendar", "the API key was not generated on the first start": "a tecla API não foi gerada no primeiro início", "the NVIDIA hook of the container is not available:": "o hook NVIDIA do contêiner não está disponível:", @@ -8152,7 +8152,7 @@ "video + recognition": "vídeo + reconhecimento", "video only": "apenas vídeo", "vzdump backup speed optimization completed": "otimização da velocidade de backup do vzdump concluída", - "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag constrói seus links a partir do endereço fornecido durante a instalação. Se não corresponder ao endereço do recipiente, edite lxc.environment. tempo de execução: SYMFONY ENV DOMAIN NAME em /etc/pve/lxc/.conf com o recipiente parado, e inicie-o novamente.", + "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "O wallabag gera as ligações com o endereço indicado na instalação. Se não corresponder ao do contentor, pare-o, edite lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME em /etc/pve/lxc/.conf e volte a iniciá-lo.", "wallabag listens on port 80 of the container and stores its data in SQLite.": "wallabag escuta na porta 80 do recipiente e armazena seus dados em SQLite.", "wallabag web interface": "Interface Web wallabag", "was": "era", diff --git a/lang/sk.json b/lang/sk.json index 63a88682..becda058 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -138,7 +138,7 @@ "ALL DATA ON": "VŠETKY DÁTA NA", "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!": "VŠETKY DÁTA NA TOMTO DISKU BUDÚ NATRVALO STRATENÉ!", "ALL Utilities": "VŠETKY nástroje", - "ALLOWED_HOSTS cannot contain line breaks": "UDELENÉ HOSTS nemôžu obsahovať prestávky na čiare", + "ALLOWED_HOSTS cannot contain line breaks": "ALLOWED_HOSTS nesmie obsahovať zalomenia riadkov", "AList initial login": "AList začiatočné prihlásenie", "AMD (ROCm)": "AMD (ROCm)", "AMD (VA-API and ROCm detection)": "AMD (detekcia VA-API a ROCm)", @@ -322,7 +322,7 @@ "After confirming, you will be asked to choose the NVIDIA driver version to install.": "Po potvrdení si vyberiete verziu ovládača NVIDIA, ktorú chcete nainštalovať.", "After detailed analysis, no changes are needed.": "Po podrobnej analýze nie sú potrebné žiadne zmeny.", "After detailed analysis, no cleanup is needed.": "Po podrobnej analýze nie je potrebné žiadne čistenie.", - "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Po prihlásení spustite príkaz: ip a, aby ste zistili IP adresu.\nPotom túto IP adresu zadajte do prehliadača takto:\n http://IP_ADRESA\n\nOtvorí sa tým webové rozhranie Umbral OS.", + "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Po prihlásení zistite IP adresu príkazom ip a.\nPotom ju zadajte do prehliadača v tomto tvare:\n http://IP_ADDRESS\n\nOtvorí sa panel Umbral OS.", "After pasting on the server, ensure the file is chmod 600 and owned by": "Po vložení na serveri overte, že súbor má chmod 600 a vlastní ho", "After reboot the system will be fully accessible (SSH, web UI, login), but the following components will be reinstalled in BACKGROUND — until they finish, commands like nvidia-smi may not yet be available:": "Po reštarte bude systém plne dostupný (SSH, webové rozhranie, prihlásenie), ale tieto súčasti sa budú doinštalovávať NA POZADÍ - kým neskončia, príkazy ako nvidia-smi ešte nemusia byť dostupné:", "After reboot, these components will reinstall in background:": "Po reštarte sa tieto súčasti doinštalujú na pozadí:", @@ -3048,7 +3048,7 @@ "Initiator IQN is authorised on the target": "IQN iniciátora je na targete povolené", "Initiator IQN:": "IQN iniciátora:", "Inkscape is professional quality vector graphics software which runs on Linux, Mac OS X and Windows desktop computers.": "Inkscape je profesionálne kvalitný vektorový grafický softvér, ktorý beží na počítačoch Linux, Mac OS X a Windows.", - "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Vnútri LXC, vytvoriť administrátor: konzola kimai:používateľ:vytvorte VAŠE VYUŽITE VAŠE EMAIL ROLE_SUPER_ADMIN", + "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "V LXC vytvorte správcu príkazom: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN", "Inspect disks before any action": "Skontrolovať disky pred akoukoľvek akciou", "Inspect host device nodes": "Skontrolovať device nodes na hostovi", "Inspect passthrough/kernel events": "Skontrolovať passthrough/kernel udalosti", @@ -3225,7 +3225,7 @@ "Interrupted operation:": "Prerušená operation:", "Interrupted stack operation found": "Prerušený zásobník operation nájdený", "Invalid 'proxmox-ve' candidate (not 9.x or none). Please verify your repository configuration and network, then retry.": "Neplatný kandidát 'proxmox-ve' (nie je 9.x alebo chýba). Skontrolujte nastavenie repozitárov a sieť, potom skúste znova.", - "Invalid ALLOWED_HOSTS value": "Neplatná hodnota UDELENÁ HOSTS", + "Invalid ALLOWED_HOSTS value": "Neplatná hodnota ALLOWED_HOSTS", "Invalid Home Assistant OS check timeout": "Neplatný čas odhlásenia Home Assistant OS", "Invalid ID": "Neplatné ID", "Invalid IPv4 address": "Neplatná adresa IPv4", @@ -3460,7 +3460,7 @@ "Keyfile removed.": "Keyfile odstránený.", "Keyrings method failed; trying apt-key fallback": "Metóda keyrings zlyhala; skúšam náhradný apt-key postup", "KiCad - A Cross Platform and Open Source Electronics Design Automation Suite.": "KiCad - Cross Platform a Open Source Electronics Design Automation Suite.", - "Kimai has no default account. Enter the container with: pct enter {main_vmid}": "Kimai nemá predvolený účet. Zadajte kontajner s: pct zadajte {main vmid}", + "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 časová aplikácia, zadarmo a open-source.", "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 je výkonný nástroj navrhnutý tak, aby vám plnú kontrolu nad vaše mediálne knižnice. S Kometa, môžete vziať svoje prispôsobenie na ďalšiu úroveň, s granulovanou kontrolou nad metaúdaje, zbierky, prekrývanie, a oveľa viac.", "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 svoju konfiguráciu z /config/config.yml a kontajner iba lode /config/config.yml.templát. Skopírujte šablónu pre config.yml, vyplňte required Plex a TMDb pripojenia, potom reštartujte kontajner.", @@ -8153,7 +8153,7 @@ "video + recognition": "video + rozpoznávanie", "video only": "iba video", "vzdump backup speed optimization completed": "Optimalizácia rýchlosti záloh vzdump je dokončená", - "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag buduje svoje odkazy z adresy uvedenej počas inštalácie. Ak nezodpovedá adrese kontajnera, upravte prostredie lxc. čas: SYMFONY ENV DOMAIN NAME in /etc/pve/lxc/ .conf s kontajnerom zastavil, a začať znova.", + "wallabag builds its links from the address given during the installation. If it does not match the address of the container, edit lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME in /etc/pve/lxc/.conf with the container stopped, and start it again.": "wallabag vytvára odkazy z adresy zadanej pri inštalácii. Ak sa nezhoduje s adresou kontajnera, zastavte ho, upravte lxc.environment.runtime: SYMFONY__ENV__DOMAIN_NAME v /etc/pve/lxc/.conf a znova ho spustite.", "wallabag listens on port 80 of the container and stores its data in SQLite.": "wallabag počúva port 80 kontajnera a ukladá svoje údaje v SQLite.", "wallabag web interface": "Webové rozhranie wallabag", "was": "bolo", diff --git a/lang/sv.json b/lang/sv.json index c06a631e..663e5e28 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -322,7 +322,7 @@ "After confirming, you will be asked to choose the NVIDIA driver version to install.": "Efter bekräftelse kommer du att bli ombedd att välja NVIDIA-drivrutinsversionen att installera.", "After detailed analysis, no changes are needed.": "Efter detaljerad analys behövs inga ändringar.", "After detailed analysis, no cleanup is needed.": "Efter detaljerad analys behövs ingen rengöring.", - "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "När du har loggat in, kör: ip a för att få IP-adressen.\nAnge sedan den IP-adressen i din webbläsare så här:\n http://IP_ADRESS\n\nDetta öppnar Umbral OS-instrumentpanelen.", + "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.": "Kör ip a efter inloggningen för att ta reda på IP-adressen.\nAnge sedan adressen i webbläsaren så här:\n http://IP_ADDRESS\n\nDå öppnas instrumentpanelen för Umbral OS.", "After pasting on the server, ensure the file is chmod 600 and owned by": "Efter att ha klistrat in på servern, se till att filen är chmod 600 och ägs av", "After reboot the system will be fully accessible (SSH, web UI, login), but the following components will be reinstalled in BACKGROUND — until they finish, commands like nvidia-smi may not yet be available:": "Efter omstart kommer systemet att vara fullt tillgängligt (SSH, webbgränssnitt, inloggning), men följande komponenter kommer att installeras om i BAKGRUND - tills de är klara kanske kommandon som nvidia-smi inte är tillgängliga ännu:", "After reboot, these components will reinstall in background:": "Efter omstart kommer dessa komponenter att installeras om i bakgrunden:", @@ -1643,8 +1643,8 @@ "Default Proxmox logo applied": "Standard Proxmox-logotyp tillämpas", "Default is none (video-only passthrough). Use SPACE to toggle selections.": "Standard är ingen (endast videogenomströmning). Använd MELLANSLAG för att växla mellan val.", "Default journald configuration restored": "Standard journalkonfiguration återställd", - "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "Standardplatsen är /mnt/. Delingen kommer att monteras här på värden med öppna behörigheter så att en oprivilegierad LXC kan bind-mounta och skriva till den. För LXC-åtkomst, bind-montera den här sökvägen med LXC Mount Manager.", - "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "Standardplatsen är /mnt/. Delen kommer att monteras här på värden. Använd denna sökväg i /etc/fstab. För LXC-åtkomst, bind-montera den här sökvägen med LXC Mount Manager.", + "Default location is /mnt/. The share will be mounted here on the host with open permissions so an unprivileged LXC can bind-mount and write to it. For LXC access, bind-mount this path with the LXC Mount Manager.": "Standardplatsen är /mnt/. Den delade resursen monteras här på värden med öppna behörigheter, så att en oprivilegierad LXC-container kan bind-montera den och skriva till den. Använd LXC Mount Manager för att skapa monteringen i LXC-containern.", + "Default location is /mnt/. The share will be mounted here on the host. Use this path in /etc/fstab. For LXC access, bind-mount this path with the LXC Mount Manager.": "Standardplatsen är /mnt/. Den delade resursen monteras här på värden. Använd sökvägen i /etc/fstab. För åtkomst från en LXC-container, skapa en bind-montering av sökvägen med LXC Mount Manager.", "Default login": "Standard inloggning", "Default options": "Standardalternativ", "Default options read/write": "Standardalternativ läs/skriv", @@ -2035,7 +2035,7 @@ "Enter VM ID:": "Ange VM ID:", "Enter VM/CT ID:": "Ange VM/CT ID:", "Enter ZFS pool name for the selected disk:": "Ange ZFS-poolnamn för den valda disken:", - "Enter a name for the mount point (used as /mnt/):": "Ange ett namn för monteringspunkten (används som /mnt/):", + "Enter a name for the mount point (used as /mnt/):": "Ange ett namn för monteringspunkten (används som /mnt/):", "Enter a name for the new virtual machine:": "Ange ett namn för den nya virtuella maskinen:", "Enter a non-empty password or access token on a single line.": "Ange ett icke-tomt lösenord eller åtkomsttoken på en enda rad.", "Enter a number, or write or paste a command:": "Ange ett nummer, eller skriv eller klistra in ett kommando:", @@ -2183,7 +2183,7 @@ "Example: pct stop 110": "Exempel: pct stop 110", "Example: pct stop 114": "Exempel: pct stop 114", "Example: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,backup": "Exempel: pvesm add nfs nfs-nas --server 192.168.1.50 --export /volume1/proxmox --content import,säkerhetskopia", - "Example: pvesm path local-lvm:vm-114-disk-0": "Exempel: pvesm sökväg local-lvm:vm-114-disk-0", + "Example: pvesm path local-lvm:vm-114-disk-0": "Exempel: pvesm path local-lvm:vm-114-disk-0", "Example: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf": "Exempel: sed -i '/^unprivileged: 1/d' /etc/pve/lxc/110.conf", "Examples:": "Exempel:", "Execute destructive rollback?": "Utföra destruktiv återställning?", @@ -2495,7 +2495,7 @@ "For a consistent export, the VM should be stopped.": "För en konsekvent export bör den virtuella datorn stoppas.", "For any question, issue or bug report, please use the official repository": "För alla frågor, problem eller felrapporter, använd det officiella arkivet", "For dedicated GPUs, FLR is required by this policy to reduce VM": "För dedikerade GPU:er krävs FLR enligt denna policy för att minska virtuella datorer", - "For example: pct enter 101": "Till exempel: pct ange 101", + "For example: pct enter 101": "Till exempel: pct enter 101", "For guest shares add: --options guest": "För gästandelar lägg till: --options gäst", "For security reasons, the system cannot format it.": "Av säkerhetsskäl kan systemet inte formatera det.", "For unprivileged containers, use instead:": "För oprivilegierade behållare, använd istället:", @@ -2773,7 +2773,7 @@ "Host backup attached to PVE job": "Värdsäkerhetskopia kopplad till PVE-jobb", "Host data is not restored by the backup; confirm it with --acknowledge-external-data": "Värddata återställs inte av säkerhetskopieringen; bekräfta den med --acknowledge-external-data", "Host device for /dev/kvm": "Värd enhet för /dev/kvm", - "Host device for /dev/net/tun": "Värd enhet för /dev/nett/tun", + "Host device for /dev/net/tun": "Enhet på värden för /dev/net/tun", "Host device for /dev/ttyUSB0": "Värd enhet för /dev/ttyUSB0", "Host device for /dev/video10": "Värd enhet för /dev/video10", "Host device for /dev/video11": "Värd enhet för /dev/video11", @@ -2907,8 +2907,8 @@ "If notifications are enabled (Telegram/Discord/ntfy/...), you will receive a \"Host restore finished\" message when all background tasks complete.": "Om aviseringar är aktiverade (Telegram/Discord/ntfy/...), kommer du att få meddelandet \"Värdåterställning avslutad\" när alla bakgrundsuppgifter är klara.", "If passthrough fails on Windows: install RadeonResetBugFix.": "Om passthrough misslyckas på Windows: installera RadeonResetBugFix.", "If path not accessible (ZFS/BTRFS):": "Om sökväg inte är tillgänglig (ZFS/BTRFS):", - "If pvesm path returned a DEVICE (LVM):": "Om pvesm-sökvägen returnerade en DEVICE (LVM):", - "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Om pvesm-sökvägen returnerade en DIRECTORY (ZFS/BTRFS):", + "If pvesm path returned a DEVICE (LVM):": "Om pvesm path returnerade en ENHET (LVM):", + "If pvesm path returned a DIRECTORY (ZFS/BTRFS):": "Om pvesm path returnerade en KATALOG (ZFS/BTRFS):", "If repositories error:": "Om förrådsfel:", "If the container had already been changed, its previous native backup is restored. Shared host directories are not reverted. The disks of the failed attempt are removed when the cleanup succeeds.": "Om behållaren redan hade ändrats, återställs dess tidigare inbyggda säkerhetskopia. Delade värdkataloger återställs inte. Diskarna för det misslyckade försöket tas bort när rensningen lyckas.", "If the host freezes, remove hostpci entries from": "Om värden fryser, ta bort hostpci-poster från", @@ -3048,7 +3048,7 @@ "Initiator IQN is authorised on the target": "Initiator IQN är auktoriserad på målet", "Initiator IQN:": "Initiativtagare IQN:", "Inkscape is professional quality vector graphics software which runs on Linux, Mac OS X and Windows desktop computers.": "Inkscape är professionell kvalitet vektor grafik programvara som körs på Linux, Mac OS X och Windows stationära datorer.", - "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Inuti LXC, skapa administratören: konsol kimai:user: skapa ditt USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN", + "Inside the LXC, create the administrator: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN": "Skapa administratören inne i LXC-containern: console kimai:user:create YOUR_USERNAME YOUR_EMAIL ROLE_SUPER_ADMIN", "Inspect disks before any action": "Inspektera diskar innan någon åtgärd", "Inspect host device nodes": "Inspektera värdenhetsnoder", "Inspect passthrough/kernel events": "Inspektera passthrough/kärnhändelser", @@ -3460,7 +3460,7 @@ "Keyfile removed.": "Nyckelfilen har tagits bort.", "Keyrings method failed; trying apt-key fallback": "Nyckelringsmetoden misslyckades; försöker apt-key fallback", "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. Ange behållaren med: pct enter {main vmid}", + "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.", "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.", @@ -3531,7 +3531,7 @@ "Limiting size and optimizing journald": "Begränsning av storlek och optimering av journal", "Limiting size and optimizing journald...": "Begränsar storlek och optimerar journal...", "Limnoria A robust, full-featured, and user/programmer-friendly Python IRC bot, with many existing plugins. Successor of the well-known Supybot.": "Limnoria En robust, fullfjädrad och användarvänlig Python IRC bot, med många befintliga plugins. Framgång för den välkända Supybot.", - "Limnoria joins no IRC network until its configuration file exists. Create it with the setup wizard from the Proxmox host: pct exec -- bash -c 'cd /config && limnoria-wizard'": "Limnoria går med i inget IRC-nätverk tills dess konfigurationsfil finns. Skapa den med installationsguiden från Proxmox-värden: pct exec - bash -c \"cd /config & & & & limnoria-wizard\"", + "Limnoria joins no IRC network until its configuration file exists. Create it with the setup wizard from the Proxmox host: pct exec -- bash -c 'cd /config && limnoria-wizard'": "Limnoria ansluter inte till något IRC-nätverk förrän konfigurationsfilen finns. Skapa den med installationsguiden från Proxmox-värden: pct exec -- bash -c 'cd /config && limnoria-wizard'", "Line to paste (single line, including \"command=...\" prefix):": "Rad att klistra in (en rad, inklusive \"command=...\" prefix):", "Linux Installation Options": "Linux installationsalternativ", "Linux/Mac path:": "Linux/Mac sökväg:", @@ -4600,7 +4600,7 @@ "Partition created:": "Partition skapad:", "Partition table wiped": "Skiljebord torkat", "Pass /dev/kvm to the LXC": "Pass /dev/kvm till LXC", - "Pass /dev/net/tun to the LXC": "Pass /dev/netto/tun till LXC", + "Pass /dev/net/tun to the LXC": "Ge LXC-containern åtkomst till /dev/net/tun", "Pass /dev/ttyUSB0 to the LXC": "Pass /dev/ttyUSB0 till LXC", "Pass /dev/video10 to the LXC": "Pass /dev/video10 till LXC", "Pass /dev/video11 to the LXC": "Pass /dev/video11 till LXC", @@ -5250,7 +5250,7 @@ "Replace": "Ersätta", "Replace with the actual ID.": "Ersätt med det faktiska ID:t.", "Replace with your actual container ID": "Ersätt med ditt faktiska container-ID", - "Replace with the value from step 4": "Ersätt med värdet från steg 4", + "Replace with the value from step 4": "Ersätt med värdet från steg 4", "Replace invalid port(s)": "Ersätt ogiltiga port(ar)", "Replace it?": "Byt ut den?", "Replace paths with your actual values from step 5": "Ersätt sökvägar med dina faktiska värden från steg 5", @@ -6993,7 +6993,7 @@ "Transaction log:": "Transaktionslogg:", "Translation files:": "Översättningsfiler:", "Transmission is designed for easy, powerful use. Transmission has the features you want from a BitTorrent client: encryption, a web interface, peer exchange, magnet links, DHT, µTP, UPnP and NAT-PMP port forwarding, webseed support, watch directories, tracker editing, global and per-torrent speed limits, and more.": "Transmission är utformad för enkel, kraftfull användning. Transmission har de funktioner du vill ha från en BitTorrent-klient: kryptering, ett webbgränssnitt, peer-utbyte, magnetlänkar, DHT, μTP, UPnP och NAT-PMP-port vidarebefordran, webseed support, watch kataloger, tracker redigering, globala och per-torrent hastighet gränser, och mer.", - "Tried pvesm path and manual detection methods": "Testade pvesm-väg och manuella detekteringsmetoder", + "Tried pvesm path and manual detection methods": "Har provat pvesm path och metoder för manuell identifiering", "Trust this certificate and save it for scheduled backups?": "Lita på det här certifikatet och spara det för schemalagda säkerhetskopieringar?", "Try Again": "Försök igen", "Try a different search term.": "Prova en annan sökterm.", @@ -8018,8 +8018,8 @@ "pvesm command not found. This should not happen on Proxmox.": "pvesm-kommandot hittades inte. Detta bör inte hända på Proxmox.", "pvesm not available.": "pvesm inte tillgänglig.", "pvesm not found.": "pvesm hittades inte.", - "pvesm path failed, trying manual detection...": "pvesm sökväg misslyckades, försöker manuell upptäckt...", - "pvesm status failed": "pvesm-status misslyckades", + "pvesm path failed, trying manual detection...": "pvesm path misslyckades, försöker identifiera manuellt...", + "pvesm status failed": "pvesm status misslyckades", "pyLoad is a Free and Open Source download manager written in Python and designed to be extremely lightweight, easily extensible and fully manageable via web.": "pyLoad är en gratis och öppen källkod nedladdningshanterare skriven i Python och utformad för att vara extremt lätt, lättföränderlig och fullt hanterbar via webben.", "pyLoad web interface": "PyLoad webbgränssnitt", "qBittorrent WebUI password (user: admin)": "qBittorrent WebUI lösenord (användare: admin)", @@ -8098,7 +8098,7 @@ "switch_gpu_mode.sh was not found.": "switch_gpu_mode.sh hittades inte.", "sysfs ROM dump failed — trying ACPI VFCT table...": "sysfs ROM-dump misslyckades — försöker ACPI VFCT-tabell...", "syslog-ng allows you to flexibly collect, parse, classify, rewrite and correlate logs from across your infrastructure and store or route them to log analysis tools.": "syslog-ng låter dig flexibelt samla in, analysera, klassificera, skriva och korrelera loggar från hela din infrastruktur och lagra eller dirigera dem för att logga analysverktyg.", - "systemctl restart networking failed:": "systemctl omstart av nätverk misslyckades:", + "systemctl restart networking failed:": "systemctl restart networking misslyckades:", "systemd OnCalendar expression": "systemd OnCalendar-uttryck", "the API key was not generated on the first start": "API-nyckeln genererades inte i första början", "the NVIDIA hook of the container is not available:": "NVIDIA-hooken för behållaren är inte tillgänglig:", diff --git a/web/CONTRIBUTING-TRANSLATIONS.md b/web/CONTRIBUTING-TRANSLATIONS.md index ce9250cb..bd668edc 100644 --- a/web/CONTRIBUTING-TRANSLATIONS.md +++ b/web/CONTRIBUTING-TRANSLATIONS.md @@ -5,7 +5,12 @@ serves every published page under locale-prefixed URLs: - `/en/` — English, the source of truth - `/es/` — Spanish +- `/de/` — German +- `/fr/` — French +- `/it/` — Italian +- `/pt/` — Portuguese - `/sk/` — Slovak, with English fallback where needed +- `/sv/` — Swedish We use [`next-intl`](https://next-intl.dev) for the i18n plumbing. Anyone can translate the docs without writing TypeScript: most of the work is @@ -148,6 +153,18 @@ Example: ### 4. Test locally +From the repository root, check the available catalogs without using a +translation provider: + +```bash +python3 .github/scripts/check_web_docs_catalogs.py +``` + +For a full locale-completion change, add `--require-complete` to also require +every English file and leaf. This validates schema, placeholders, rich-text +tags, literal code and rendering metadata; it does not replace linguistic +review. Partial translations can continue to use the English fallback. + ```bash cd web npm run dev @@ -311,7 +328,10 @@ registers a renderer for them. If you want to add a language that isn't in the project yet: 1. Create `messages//common.json` and the page catalogs. The automated - builder can provide the initial baseline. + builder can provide the initial baseline when explicitly dispatched. It + does not run on merge: changing English and its translations together must + not trigger machine retranslation of the reviewed text. Offline validation + still runs automatically for website changes. 2. Review the shared navigation and a representative set of documentation pages with a native speaker. 3. Add the locale code to `routing.ts` only when the locale is ready to be diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 22dcf74a..05cf6b82 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -8,13 +8,14 @@ const inter = Inter({ subsets: ["latin"] }) * Minimal root layout — the localized layout under [locale]/ does the * real work (provider, navbar, etc.) but cannot own / tags * because Next.js requires those at the root level. Lang is set to the - * default locale here and refined client-side from inside the locale - * layout so the page still renders correctly with JS disabled (defaults - * to English) and switches to the active locale once hydrated. + * default locale here; scripts/localize-export.mjs sets the exported + * document's language before search indexing, and LocaleHtmlSync keeps + * it in sync during client navigation. The root-only hydration suppression + * allows that intentional lang attribute change in the static export. */ export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {children} diff --git a/web/components/language-switcher.tsx b/web/components/language-switcher.tsx index aa084783..cede72da 100644 --- a/web/components/language-switcher.tsx +++ b/web/components/language-switcher.tsx @@ -29,7 +29,12 @@ export function LanguageSwitcher() { const labels: Record = { en: t("en"), es: t("es"), + de: t("de"), + fr: t("fr"), + it: t("it"), + pt: t("pt"), sk: t("sk"), + sv: t("sv"), } return ( diff --git a/web/i18n/routing.ts b/web/i18n/routing.ts index 41c0cfdc..ffc11c7f 100644 --- a/web/i18n/routing.ts +++ b/web/i18n/routing.ts @@ -19,7 +19,7 @@ import { defineRouting } from "next-intl/routing" * `CONTRIBUTING-TRANSLATIONS.md`. */ export const routing = defineRouting({ - locales: ["en", "es", "sk"], + locales: ["en", "es", "de", "fr", "it", "pt", "sk", "sv"], defaultLocale: "en", localePrefix: "always", }) diff --git a/web/messages/de/common.json b/web/messages/de/common.json new file mode 100644 index 00000000..c3aefb73 --- /dev/null +++ b/web/messages/de/common.json @@ -0,0 +1,254 @@ +{ + "site": { + "name": "ProxMenux", + "tagline": "Interaktives Menü und Web-Dashboard für Proxmox VE" + }, + "nav": { + "home": "Startseite", + "docs": "Dokumentation", + "documentation": "Dokumentation", + "guides": "Anleitungen", + "changelog": "Änderungsprotokoll", + "github": "GitHub", + "rss": "RSS", + "rssTitle": "RSS-Feed", + "menuOpen": "Menü öffnen" + }, + "footer": { + "sponsorHeading": "Projekt unterstützen", + "sponsorBody": "Wenn Sie das Projekt unterstützen möchten.", + "sponsorAlt": "Auf Ko-fi unterstützen", + "connectHeading": "Community", + "connectBody": "Beteiligen Sie sich an den Community-Diskussionen auf GitHub, um Hilfe zu erhalten, Ideen auszutauschen und zum Projekt beizutragen. Jede Idee ist willkommen!", + "joinDiscussion": "An der Diskussion teilnehmen", + "copyrightPrefix": "ProxMenux, ein gemeinschaftliches Open-Source-Projekt von", + "copyrightSuffix": "." + }, + "language": { + "switcher": "Sprache", + "en": "English", + "es": "Español", + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" + }, + "resources": { + "documentation": { + "title": "Dokumentation", + "description": "Systembeschreibung und Benutzeranleitungen" + }, + "changelog": { + "title": "Änderungsprotokoll", + "description": "Informationen zu den neuesten Aktualisierungen" + }, + "guides": { + "title": "Anleitungen", + "description": "Schrittweise Anleitungen für häufige Aufgaben" + }, + "github": { + "title": "GitHub-Repository", + "description": "Sehen Sie sich den Quellcode an." + } + }, + "docSidebar": { + "documentation": "Dokumentation", + "items": { + "introduction": "Einführung", + "installation": "Installation", + "proxmenuxMonitor": "ProxMenux Monitor", + "monitorOverview": "Übersicht", + "architecture": "Architektur", + "accessAuth": "Zugriff und Authentifizierung", + "dashboard": "Dashboard", + "dashboardSystemOverview": "Registerkarte Systemübersicht", + "dashboardApps": "Registerkarte Apps", + "dashboardStorage": "Registerkarte Speicher", + "dashboardNetwork": "Registerkarte Netzwerk", + "dashboardVmsLxcs": "Registerkarte VMs und LXCs", + "dashboardVmsLxcsApp": "App", + "dashboardVmsLxcsUpdates": "Aktualisierungen", + "dashboardHardware": "Registerkarte Hardware", + "dashboardSystemLogs": "Registerkarte Systemprotokolle", + "dashboardTerminal": "Registerkarte Terminal", + "dashboardSecurity": "Registerkarte Sicherheit", + "dashboardSettings": "Registerkarte Einstellungen", + "healthMonitor": "Zustandsüberwachung", + "auditReport": "Prüfung und Berichte", + "auditReportOverview": "Übersicht", + "auditReportAssessment": "Bewertung und Inventar", + "auditReportPolicy": "Richtlinie", + "auditReportChanges": "Änderungen", + "auditReportReports": "Berichte und Vergleiche", + "auditReportScope": "Umfang und Garantien", + "notifications": "Benachrichtigungen", + "aiAssistant": "KI-Assistent", + "apiReference": "API-Referenz", + "integrations": "Integrationen", + "ociManager": "OCI manager Apps", + "ociOverview": "Übersicht", + "ociArchitecture": "So wird ein OCI-Image umgesetzt", + "ociCustomImage": "Image außerhalb des Katalogs", + "ociStorageNetwork": "Daten, Pfade und Netzwerk", + "ociHardware": "Geräte und Beschleunigung", + "ociStacks": "Anwendungen mit mehreren Containern", + "ociLifecycle": "Installieren, aktualisieren und ändern", + "ociMonitor": "In ProxMenux Monitor", + "proxmenuxScripts": "ProxMenux Scripts", + "postInstallScript": "Skript zur Nachinstallation", + "postInstallOverview": "Übersicht", + "postInstallAutomated": "Automatisch", + "postInstallCustomizable": "Anpassbar", + "postInstallBasicSettings": "Grundeinstellungen", + "postInstallSystem": "System", + "postInstallVirtualization": "Virtualisierung", + "postInstallNetwork": "Netzwerk", + "postInstallStorage": "Speicher", + "postInstallSecurity": "Sicherheit", + "postInstallCustomization": "Anpassung", + "postInstallMonitoring": "Überwachung", + "postInstallPerformance": "Leistung", + "postInstallOptional": "Optional", + "postInstallUpdates": "Verfügbare Aktualisierungen installieren", + "postInstallUninstall": "Optimierungen entfernen", + "gpusCoralTpu": "GPUs und Coral TPU", + "nvidiaHost": "NVIDIA-Treiber auf dem Host installieren", + "coralHost": "Coral TPU auf dem Host installieren", + "addGpuLxc": "GPU für einen LXC freigeben", + "addCoralLxc": "Coral TPU für einen LXC freigeben", + "addGpuVm": "GPU einer VM zuweisen (Passthrough)", + "switchGpuMode": "GPU-Modus wechseln (VM ↔ LXC)", + "createVm": "VM erstellen", + "createVmOverview": "Übersicht", + "createVmSystemNas": "NAS-Systeme", + "createVmSynology": "Synology-VM", + "createVmNasOthers": "Weitere NAS-Systeme", + "createVmSystemWindows": "Windows-Systeme", + "createVmSystemLinux": "Linux-Systeme", + "diskManager": "Datenträgerverwaltung", + "diskManagerOverview": "Übersicht", + "diskImportVm": "Datenträger in eine VM importieren", + "diskImportImageVm": "Datenträgerabbild in eine VM importieren", + "diskAddController": "Controller oder NVMe-Gerät zu einer VM hinzufügen", + "diskImportLxc": "Datenträger in einen LXC importieren", + "diskFormat": "Physischen Datenträger formatieren oder löschen", + "diskSmart": "SMART-Zustand und Datenträgertests", + "storageShareManager": "Speicher und Freigaben verwalten", + "storageShareOverview": "Übersicht", + "hostStorage": "Speicher in den Host einbinden", + "hostNfs": "NFS-Freigabe als Proxmox-Speicher hinzufügen", + "hostSamba": "Samba-Freigabe als Proxmox-Speicher hinzufügen", + "hostIscsi": "iSCSI-Ziel als Proxmox-Speicher hinzufügen", + "hostLocalDisk": "Lokalen Datenträger als Proxmox-Speicher hinzufügen", + "hostLocalShared": "Freigegebenes Verzeichnis auf dem Host anlegen", + "lxcMountPoints": "LXC-Mountpoints (Host ↔ CT)", + "lxcNetworkSharing": "Netzwerkfreigaben in LXC", + "lxcNfsClient": "NFS-Client in LXC", + "lxcSambaClient": "Samba-Client in LXC", + "lxcNfsServer": "NFS-Server in LXC", + "lxcSambaServer": "Samba-Server in LXC", + "network": "Netzwerk", + "networkOverview": "Übersicht", + "networkDiagnostics": "Diagnose", + "networkMonitoring": "Werkzeuge zur Echtzeitüberwachung", + "networkBridge": "Netzwerk-Bridges prüfen und reparieren", + "networkConfig": "Konfiguration prüfen und bereinigen", + "networkPersistent": "Dauerhafte Schnittstellennamen", + "networkBackup": "Schnittstellen sichern und neu starten", + "security": "Sicherheit", + "securityOverview": "Übersicht", + "securityFail2ban": "Fail2Ban", + "securityLynis": "Lynis", + "utilities": "Dienstprogramme", + "utilsOverview": "Übersicht", + "utilsUupDump": "ISO-Erstellung mit UUP Dump", + "utilsSystemUtils": "Systemwerkzeuge installieren", + "utilsSystemUpdate": "Proxmox-System aktualisieren", + "utilsUpgradePve": "Von PVE 8 auf PVE 9 aktualisieren", + "utilsExportVm": "VM als OVA/OVF exportieren", + "utilsImportVm": "VM aus OVA/OVF importieren", + "settingsProxmenux": "ProxMenux-Einstellungen", + "settingsOverview": "Übersicht", + "settingsMonitor": "ProxMenux Monitor", + "settingsBeta": "Release-Kanal wechseln", + "settingsVersion": "Versionsinformationen anzeigen", + "settingsUninstall": "ProxMenux deinstallieren", + "commandsReference": "Befehlsreferenz", + "commandsOverview": "Übersicht", + "commandsSystem": "Nützliche Systembefehle", + "commandsVmCt": "VMs und CTs verwalten", + "commandsStorage": "Speicher und Datenträger", + "commandsNetwork": "Netzwerkbefehle", + "commandsUpdates": "Aktualisierungen und Pakete", + "commandsGpu": "GPU-Passthrough", + "commandsZfs": "ZFS verwalten", + "commandsBackup": "Sichern und Wiederherstellen", + "commandsTools": "Systemwerkzeuge für die Befehlszeile", + "glossary": "Glossar", + "about": "Über das Projekt", + "aboutOverview": "Übersicht", + "aboutFaq": "FAQ", + "aboutContributors": "Mitwirkende", + "aboutContributing": "Mitwirken", + "aboutCodeOfConduct": "Verhaltenskodex", + "externalRepositories": "Externe Repositorys", + "backupRestore": "Sichern und Wiederherstellen", + "backupRestoreOverview": "Übersicht", + "backupRestoreHowItWorks": "Funktionsweise", + "backupRestoreDestinations": "Ziele", + "backupRestoreDestOverview": "Übersicht", + "backupRestoreDestLocal": "Lokales Archiv", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Sicherungen erstellen", + "backupRestoreJobs": "Geplante Aufträge", + "backupRestoreRestoring": "Wiederherstellen", + "backupRestoreCrossKernel": "Wiederherstellung mit anderer Kernel-Version", + "backupRestoreGlossary": "Glossar" + } + }, + "hero": { + "title": "ProxMenux", + "tagline": "Ein interaktives Menü und Web-Dashboard für Proxmox VE", + "description": "ProxMenux verbindet ein interaktives Menü mit einem Web-Dashboard für Proxmox VE. Führen Sie Befehle, Skripte und geführte Assistenten über das Terminalmenü aus oder verfolgen Sie Host-Zustand, Messwerte und Benachrichtigungen im Browser.", + "installButton": "Jetzt installieren", + "whatIsButton": "Was ist ProxMenux?", + "logoAlt": "ProxMenux-Logo" + }, + "docNav": { + "previous": "Zurück", + "next": "Weiter" + }, + "tocPanel": { + "onThisPage": "Auf dieser Seite" + }, + "supportProject": { + "heading": "Unterstützen Sie das Projekt!", + "body": "Wenn Sie ProxMenux hilfreich finden, geben Sie dem Projekt eine ⭐ auf GitHub, damit auch andere darauf aufmerksam werden.", + "button": "Stern auf GitHub vergeben" + }, + "rssLink": { + "heading": "Bleiben Sie auf dem Laufenden!", + "body": "Abonnieren Sie unseren RSS-Feed, um über neue Änderungen informiert zu werden.", + "copy": "Kopieren", + "copied": "Kopiert!", + "copyTitle": "RSS-URL kopieren", + "openFeed": "RSS-Feed öffnen", + "openTitle": "RSS-Feed öffnen" + }, + "changelog": { + "pageTitle": "Änderungsprotokoll", + "meta": { + "title": "ProxMenux-Änderungsprotokoll", + "titleSuffix": "Versionshinweise", + "latest": "Aktuell", + "mostRecent": "Neueste Version", + "description": "Versionshinweise und Aktualisierungsverlauf für ProxMenux, das menügesteuerte Skript zur Verwaltung von Proxmox VE.", + "descriptionTail": "Neue Funktionen, Fehlerbehebungen, Sicherheitsupdates, Verbesserungen an ProxMenux Monitor, Kompatibilität mit KI-Anbietern, zusätzliche Absicherung beim GPU-Passthrough und mehr.", + "ogDescription": "Versionshinweise für ProxMenux.", + "ogTail": "Abonnieren Sie den RSS-Feed oder besuchen Sie diese Seite erneut, um neue Versionen zu finden." + } + } +} diff --git a/web/messages/en/common.json b/web/messages/en/common.json index 8e5f794b..2c37c948 100644 --- a/web/messages/en/common.json +++ b/web/messages/en/common.json @@ -28,7 +28,12 @@ "switcher": "Language", "en": "English", "es": "Español", - "sk": "Slovenčina" + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" }, "resources": { "documentation": { diff --git a/web/messages/es/common.json b/web/messages/es/common.json index 3ba17487..477d303b 100644 --- a/web/messages/es/common.json +++ b/web/messages/es/common.json @@ -28,7 +28,12 @@ "switcher": "Idioma", "en": "English", "es": "Español", - "sk": "Slovenčina" + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" }, "resources": { "documentation": { diff --git a/web/messages/es/docs/backup-restore/creating-backups.json b/web/messages/es/docs/backup-restore/creating-backups.json index f4b2188f..8bca0e53 100644 --- a/web/messages/es/docs/backup-restore/creating-backups.json +++ b/web/messages/es/docs/backup-restore/creating-backups.json @@ -157,7 +157,7 @@ "Filesystems runtime del kernel y sistema — /proc, /sys, /dev y /run son pseudo-filesystems que produce el kernel y udev; no se persisten en ningún sitio.", "Binarios de paquetes bajo /usr/bin, /usr/lib, /lib, /sbin — los reinstala el APT del destino a partir de packages.manual.list.", "/var/log/, /var/tmp/, /var/cache/ — estado runtime por host, no se restaura.", - "/home/USUARIO — 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." + "/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 la copia." diff --git a/web/messages/es/docs/backup-restore/cross-kernel.json b/web/messages/es/docs/backup-restore/cross-kernel.json index 407a7f53..ced544ff 100644 --- a/web/messages/es/docs/backup-restore/cross-kernel.json +++ b/web/messages/es/docs/backup-restore/cross-kernel.json @@ -14,7 +14,7 @@ }, "intro": { "title": "Cada salto de kernel se maneja de forma distinta", - "body": "Cuando la restauración detecta que la copia y el destino tienen versiones mayores distintas de kernel, el flujo se ramifica en función de la dirección del salto — si la copia es más antigua o más reciente que el destino — porque los dos casos tienen modos de fallo opuestos. Las copias más recientes que el destino se restauran limpiamente tal cual (verificado empíricamente en múltiples pruebas de banco). Las copias más antiguas que el destino necesitan un filtro para evitar romper el arranque del destino con configuración escrita para un kernel que desde entonces ha cambiado." + "body": "El kernel de un host Proxmox se identifica mediante una versión como 6.17.13-2-pve. Los dos primeros números (6.17 en este ejemplo) definen la versión principal: cuando cambian, algunos archivos de configuración del kernel anterior podrían no ser válidos para el nuevo. Si la restauración detecta versiones principales distintas en la copia y en el destino, el proceso toma un camino u otro según la dirección del salto: si el kernel de la copia es anterior o posterior al del destino. Los dos casos presentan riesgos opuestos. Las copias con un kernel más reciente que el del destino se restauran tal cual, sin problemas según las pruebas realizadas con varios saltos de kernel. Las copias con un kernel anterior al del destino necesitan un filtro para evitar que una configuración del kernel antiguo impida arrancar el sistema de destino." }, "directionCheck": { "heading": "La comprobación de dirección", @@ -78,7 +78,7 @@ "intro": "Para desarrolladores que quieran trazar o extender el comportamiento cross-kernel:", "rows": [ { "component": "Detección de dirección", "location": "hb_compat_check en lib_host_backup_common.sh. Fija HB_COMPAT_KERNEL_DIRECTION." }, - { "component": "Lista de rutas del subconjunto seguro", "location": "hb_unsafe_paths_cross_version en lib_host_backup_common.sh. Emite líneas path\\tmotivo usadas tanto por el filtro CLI como por el endpoint Web /api/host-backups/restore/prepare." }, + { "component": "Lista de rutas del subconjunto seguro", "location": "hb_unsafe_paths_cross_version en lib_host_backup_common.sh. Emite líneas path\\treason usadas tanto por el filtro CLI como por el endpoint Web /api/host-backups/restore/prepare." }, { "component": "Fases de hidratación", "location": "_rs_hyd_grub, _rs_hyd_kernel_cmdline, _rs_hyd_modules, _rs_hyd_files en backup_host.sh. Orquestados por _rs_apply_bk_older_hydration." }, { "component": "Forzado de reflow post-arranque", "location": "apply_pending_restore.sh lee HB_HYDRATION_APPLIED de plan.env y fuerza NEEDS_INITRAMFS=1/NEEDS_GRUB=1." }, { "component": "Preview Web", "location": "/api/host-backups/restore/prepare en flask_server.py. Fuentea la librería helper, ejecuta _rs_apply_bk_older_hydration en modo plan, devuelve las acciones al modal Web." } diff --git a/web/messages/es/docs/backup-restore/destinations/index.json b/web/messages/es/docs/backup-restore/destinations/index.json index 1965649f..efb1e349 100644 --- a/web/messages/es/docs/backup-restore/destinations/index.json +++ b/web/messages/es/docs/backup-restore/destinations/index.json @@ -76,7 +76,7 @@ }, "sameArchive": { "heading": "El contenido interno de la copia es el mismo en cualquier destino", - "body": "Con independencia de dónde se guarde la copia, dentro siempre están los mismos tres bloques descritos en Cómo funciona: el sistema de ficheros (rootfs/), los metadatos (metadata/) y el manifiesto (manifest.json). Al extraer una copia local .tar.zst, restaurar un archivo .pxar desde PBS o descomprimir un archivo Borg, el árbol de directorios resultante es idéntico en los tres casos. Esto significa que el proceso de restauración lee siempre los mismos ficheros y no le importa desde qué destino provenga la copia." + "body": "Los tres destinos contienen la misma estructura rootfs/ + metadata/ + manifest.json descrita en Cómo funciona. Extraer un .tar.zst de un archivo local, restaurar un .pxar desde PBS o extraer un archivo de Borg con borg extract produce el mismo árbol de directorios. El proceso de restauración (_rs_check_layout, _rs_apply, _rs_prepare_pending_restore) lee los mismos tres componentes sin necesitar saber de qué destino proceden." }, "extractStandalone": { "heading": "Extraer una copia fuera de ProxMenux", diff --git a/web/messages/es/docs/backup-restore/destinations/pbs.json b/web/messages/es/docs/backup-restore/destinations/pbs.json index 917cc76d..354ad987 100644 --- a/web/messages/es/docs/backup-restore/destinations/pbs.json +++ b/web/messages/es/docs/backup-restore/destinations/pbs.json @@ -23,7 +23,7 @@ }, "repoSelection": { "heading": "Selección del repositorio", - "intro": "ProxMenux descubre repositorios PBS desde dos fuentes en cada copia. El usuario elige uno desde un menú unificado; esa elección determina, para esa ejecución concreta, contra qué servidor y datastore se envía la copia, con qué contraseña se autentica y con qué huella (fingerprint) valida el certificado.", + "intro": "ProxMenux descubre los repositorios de PBS a partir de dos fuentes en cada copia de seguridad. El usuario elige uno en un menú unificado; esa elección determina los valores de HB_PBS_REPOSITORY, HB_PBS_SECRET y HB_PBS_FINGERPRINT para esa ejecución.", "sourceRows": [ { "source": "storage.cfg de Proxmox (auto-descubierto)", @@ -46,7 +46,7 @@ "backupIdTitle": "Nombrado del backup ID", "backupIdBody": "El backup ID por defecto es hostcfg-HOSTNAME. Se pide al usuario que lo confirme o edite antes de la subida; cualquier carácter fuera de [A-Za-z0-9_-] se elimina y los guiones finales se recortan. Reutilizar el mismo ID entre ejecuciones es intencional — PBS trata el ID como un grupo, y cada copia posterior aparece como un nuevo backup dentro de ese grupo, compartiendo dedup con las ejecuciones previas.", "pxarTitle": "Qué se incluye dentro del archivo .pxar", - "pxarBody": "Al construir el archivo .pxar se empaqueta el directorio de trabajo completo: el sistema de ficheros del host (rootfs/), los metadatos (metadata/) y el manifiesto (manifest.json). Al restaurar, ProxMenux compara la información del manifiesto con la del host destino para detectar si son equivalentes, si cambia el hardware o si se trata de un equipo distinto, y ajustar el proceso en consecuencia. Las copias antiguas —hechas con versiones anteriores de ProxMenux que empaquetaban solo el sistema de ficheros— siguen restaurándose sin problema: el flujo de restauración detecta ese formato heredado y lo reorganiza automáticamente." + "pxarBody": "El origen del .pxar es todo el directorio staging_root, con rootfs/, metadata/ y manifest.json juntos. Las versiones anteriores utilizaban $staging_root/rootfs como origen; esto dejaba metadata/ fuera del archivo y la comprobación de compatibilidad de la restauración no tenía datos que leer, por lo que mostraba advertencias de restauración entre hosts incluso al restaurar en el mismo host. Las copias antiguas creadas solo a partir de rootfs siguen restaurándose correctamente mediante la rama del caso 3 de _rs_check_layout, que vuelve a colocar el árbol plano etc/var/root/usr dentro de una estructura rootfs/." }, "encryption": { "heading": "Cifrado del lado del cliente", @@ -61,9 +61,9 @@ "modesSharedTitle": "Clave compartida (importar la misma en cada host)", "modesSharedBody": "Se genera una única clave y se importa en todos los hosts mediante la opción Usar una clave existente. La gestión es más sencilla: hay un solo secreto que proteger, y cualquier host puede leer las copias de cualquier otro (útil para verificación centralizada, ejercicios de restauración cruzada o consolidación). A cambio, si la clave compartida se filtra queda expuesto todo el conjunto de hosts a la vez. Recomendado para laboratorios personales y entornos donde todos los hosts pertenecen al mismo responsable y comparten el mismo nivel de confianza.", "recoveryTitle": "¿Subir la clave a PBS? — el usuario decide", - "recoveryBody": "Justo después de elegir la clave, ProxMenux pregunta si se quiere subir una copia de esa clave a PBS como red de seguridad, con dos opciones. No (por defecto) mantiene la clave solo en el host; ProxMenux no toca PBS con nada relacionado con la recuperación y el usuario gestiona por su cuenta la copia externa (desde el Monitor, la acción Descargar en la fila del destino PBS la exporta en el acto). Sí pide una passphrase de recuperación —dos veces, para comprobar que coincidan—; con esa passphrase, la clave se envuelve localmente con AES-256-CBC y PBKDF2 (600 000 iteraciones, sal aleatoria) en un fichero pbs-key.recovery.enc. A partir de ese momento, cada copia cifrada sube también ese fichero envuelto a PBS. Ni la passphrase ni la clave en claro salen del host. La respuesta se puede cambiar después desde el Monitor en cualquier momento; el interruptor está inline en la fila del destino PBS y el botón que aparece se adapta al estado (empezar a subir, dejar de subir, o cambiar la passphrase).", + "recoveryBody": "Justo después de elegir la clave, ProxMenux hace una única pregunta explícita — ¿Subir clave a PBS? — con dos respuestas. No se sube nada a PBS hasta que el usuario responde Sí. La opción por defecto es No, mantener solo local: la clave se queda solo en su ruta local, ProxMenux no toca PBS con nada relacionado con la recuperación y el usuario gestiona por su cuenta la copia externa (desde el Monitor, el botón Descargar archivo clave de la fila del destino PBS la exporta en el acto). Al elegir Sí, subir se pide una passphrase de recuperación —dos veces, para comprobar que coincidan— y ProxMenux envuelve la clave con esa passphrase usando AES-256-CBC y PBKDF2 (600 000 iteraciones, sal aleatoria) en un fichero pbs-key.recovery.enc. A partir de ese momento, cada backup cifrado sube también ese fichero envuelto a PBS como un grupo de backup asociado. Ni la passphrase ni la clave en claro salen del host. La respuesta se puede cambiar después desde el Monitor en cualquier momento: el interruptor de la fila del destino PBS cambia entre Sí y No y se aplica al momento (empezar a subir, dejar de subir o cambiar la passphrase, según el estado).", "blobUploadTitle": "Grupo emparejado en PBS", - "blobUploadBody1": "Cuando el usuario ha respondido Sí, ProxMenux sube el sobre de recuperación a PBS después de cada copia cifrada como un segundo grupo emparejado, con el mismo nombre que el del host pero terminado en -keyrecovery. Los dos grupos aparecen juntos en el listado del datastore: uno con las copias del host, otro con los sobres. El sobre nunca sale del host en claro: se cifra en el host con AES-256-CBC y PBKDF2 antes de enviar un solo byte (openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt). PBS solo recibe el sobre ya cifrado.", + "blobUploadBody1": "Cuando la custodia está configurada como Sí, subir, ProxMenux sube el archivo de recuperación protegido a PBS después de cada copia cifrada. Lo guarda en un segundo grupo de copia asociado, con el mismo nombre que el grupo del host y el sufijo -keyrecovery. Los dos grupos aparecen juntos en la lista del almacén de datos: uno para las copias del host y otro para los archivos de recuperación. El archivo de recuperación nunca sale del host en texto plano: se cifra en el propio host con AES-256-CBC y PBKDF2 antes de enviar ningún dato (openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt). PBS solo recibe el archivo ya cifrado.", "envelopeSecurityTitle": "¿No es un riesgo subir el archivo keyrecovery a PBS?", "envelopeSecurityBody": "El sobre viaja y se guarda cifrado en todo momento. La passphrase de recuperación no sale nunca del host: el cifrado ocurre en local antes de la subida y PBS solo recibe el resultado ya cifrado. Un administrador de PBS —o cualquiera con acceso al datastore— puede descargar el sobre, pero sin la passphrase no puede leer la clave que contiene: son solo bytes cifrados. Para reconstruir la clave hacen falta las dos cosas a la vez, el sobre y la passphrase, y solo el usuario tiene ambas.", "blobUploadConstraintTitle": "Por qué dos grupos y no uno solo cifrado", @@ -73,7 +73,7 @@ "recoverTitle": "Recuperación en un equipo recién instalado", "recoverBody": "Cuando la clave no está en su sitio —tras reinstalar el host desde cero o después de eliminarla a propósito— el flujo de restauración prueba tres fuentes en orden y se queda con la primera que produce una clave usable. Primero, si el repositorio PBS elegido tiene una clave gestionada por PVE en /etc/pve/priv/storage/<NAME>.enc, se copia sola a la ruta canónica. Segundo, si en PBS hay un grupo -keyrecovery, se descarga la copia más reciente y se pide la passphrase de recuperación para abrirla. Tercero, si ninguna de las dos anteriores funciona o el usuario las descarta, el diálogo pide la ruta absoluta donde ha dejado la clave en este host, y se copia a la ruta canónica. El Monitor expone el mismo flujo dentro del modal de la copia. Si al Restaurar, Descargar o Ver contenido falta la clave, aparece un panel ámbar arriba del modal con las mismas tres vías de importación. Si la clave está instalada pero no es la correcta para esa copia, PBS devuelve un error de tipo wrong key — manifest's key XX does not match provided key YY y el modal muestra un panel estructurado con el fingerprint que PBS espera (el que quedó grabado en el manifest de la copia), para que el usuario sepa qué clave concreta necesita importar.", "monitorManagementTitle": "Gestionar la clave desde el Monitor", - "monitorManagementBody": "En la página de configuración de copias, la fila de cada destino PBS incluye los controles de la clave. Tres acciones directas: Descargar exporta la clave actual al navegador. Subir reemplaza la clave instalada por otra que aporta el usuario (la anterior solo se sobrescribe cuando la nueva ha llegado correctamente a su sitio). Eliminar retira la clave del host. Justo al lado está el interruptor del sobre de recuperación: un Sí/No para subir o no la clave a PBS, un campo de passphrase que aparece al elegir Sí, y un botón que se adapta al estado actual (empezar a subir, dejar de subir, o cambiar la passphrase). Los controles del Monitor y el diálogo del shell hacen lo mismo por dentro, así que el resultado en disco y en PBS es idéntico se use la que se use." + "monitorManagementBody": "El Monitor muestra los controles del archivo de clave en cada fila de destino PBS de la página de configuración de copias. Hay tres acciones: Descargar envía el archivo de clave actual al navegador del usuario; Subir sustituye el archivo de clave instalado por uno proporcionado por el usuario (el anterior solo se sobrescribe cuando el nuevo se ha guardado correctamente en la ruta canónica); y Eliminar borra el archivo de clave del host. Junto a estas acciones hay un único control de custodia para la recuperación: las opciones Sí y No de ¿Subir la clave a PBS?, un campo para la frase de contraseña al elegir Sí y un botón contextual para aplicar el cambio, con el texto Iniciar subida, Detener subida o Actualizar frase de contraseña según el estado actual. Todas las acciones utilizan los mismos endpoints de Flask que el menú de terminal, por lo que el resultado en disco y el estado en PBS son idénticos desde ambas interfaces." }, "restoreAccess": { "heading": "Recuperación del lado de la restauración", diff --git a/web/messages/es/docs/backup-restore/glossary.json b/web/messages/es/docs/backup-restore/glossary.json index c96e29a7..a4f35780 100644 --- a/web/messages/es/docs/backup-restore/glossary.json +++ b/web/messages/es/docs/backup-restore/glossary.json @@ -112,7 +112,7 @@ { "id": "repositorio-pbs", "term": "Repositorio PBS", - "def": "Combinación de servidor Proxmox Backup Server + datastore + usuario al que se suben las copias. Se identifica con el formato usuario@realm@host:datastore. Un mismo servidor PBS puede alojar varios repositorios." + "def": "Combinación de servidor Proxmox Backup Server + datastore + usuario al que se suben las copias. Se identifica con el formato user@realm@host:datastore. Un mismo servidor PBS puede alojar varios repositorios." }, { "id": "datastore", 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 0c5ec8c7..51c91934 100644 --- a/web/messages/es/docs/backup-restore/how-it-works.json +++ b/web/messages/es/docs/backup-restore/how-it-works.json @@ -121,7 +121,7 @@ } ], "schemaTitle": "Validación por esquema", - "schemaBody": "ProxMenux incluye una plantilla que describe qué campos debe contener el manifiesto y qué forma tiene cada uno. Cuando se genera una copia se puede comprobar automáticamente que el manifiesto respeta esa plantilla, de modo que si un colector produjera un JSON malformado o con un campo mal escrito se detectaría en el momento. Es una comprobación destinada al desarrollo del propio ProxMenux: si el sistema no la tiene instalada, la copia sigue funcionando con normalidad y el manifiesto se genera igual." + "schemaBody": "El manifiesto se valida con scripts/backup_restore/schema/manifest.schema.json. Ejecutar build_manifest.sh --validate activa una validación JSON Schema en Python (requiere python3 y jsonschema). Si el módulo no está instalado, se omite la comprobación sin mostrar ningún aviso: esta validación es principalmente una ayuda para el desarrollo, no una dependencia necesaria durante la ejecución." }, "applications": { "heading": "El inventario de aplicaciones", diff --git a/web/messages/es/docs/backup-restore/restoring.json b/web/messages/es/docs/backup-restore/restoring.json index 44689048..732fa278 100644 --- a/web/messages/es/docs/backup-restore/restoring.json +++ b/web/messages/es/docs/backup-restore/restoring.json @@ -26,7 +26,7 @@ }, { "action": "Descargar", - "detail": "Exporta el archivo como un fichero portable .tar.zst (tar comprimido con zstd). Se puede extraer en cualquier sistema Linux con tar --zstd -xf FICHERO.tar.zst, o desde macOS/Windows con herramientas que soporten zstd (7-Zip, PeaZip, Keka…). El árbol extraído contiene manifest.json, metadata/ y rootfs/, exactamente el mismo layout que consume la restauración. Es útil para inspección offline o para restaurar en un host que no tiene acceso al destino original (PBS, Borg)." + "detail": "Exporta el archivo como un fichero portable .tar.zst (tar comprimido con zstd). Se puede extraer en cualquier sistema Linux con tar --zstd -xf FILE.tar.zst, o desde macOS/Windows con herramientas que soporten zstd (7-Zip, PeaZip, Keka…). El árbol extraído contiene manifest.json, metadata/ y rootfs/, exactamente el mismo layout que consume la restauración. Es útil para inspección offline o para restaurar en un host que no tiene acceso al destino original (PBS, Borg)." }, { "action": "Restaurar", diff --git a/web/messages/es/docs/hardware/switch-gpu-mode.json b/web/messages/es/docs/hardware/switch-gpu-mode.json index b3aabdcf..bd4b1841 100644 --- a/web/messages/es/docs/hardware/switch-gpu-mode.json +++ b/web/messages/es/docs/hardware/switch-gpu-mode.json @@ -110,7 +110,7 @@ }, "apply": { "title": "Aplicar cambios del host + workloads", - "body": "Una vez confirmas, el script escribe los cambios del lado del host — vfio.conf, blacklist, módulos y (para NVIDIA) la regla udev por BDF en /etc/udev/rules.d/10-proxmenux-vfio-bind.rules más el estado BDF en /etc/proxmenux/vfio-bind.bdfs. También aplica la política de conflicto elegida a cada VM/LXC afectada. Si la config del host realmente cambió, ejecuta update-initramfs -u -k all — si no, salta ese paso." + "body": "Tras confirmar, el script escribe los cambios en el host: vfio.conf, la lista de módulos bloqueados, los módulos y, para NVIDIA, la regla udev por BDF en /etc/udev/rules.d/10-proxmenux-vfio-bind.rules y el estado BDF en /etc/proxmenux/vfio-bind.bdfs. También aplica la política de conflictos elegida a cada VM/LXC afectado. Si la configuración del host ha cambiado, ejecuta update-initramfs -u -k all y después proxmox-boot-tool refresh; si no ha cambiado, omite ambos comandos." }, "reboot": { "title": "Reiniciar", diff --git a/web/messages/es/docs/monitor/access-auth.json b/web/messages/es/docs/monitor/access-auth.json index 82207bf9..1600caa8 100644 --- a/web/messages/es/docs/monitor/access-auth.json +++ b/web/messages/es/docs/monitor/access-auth.json @@ -257,7 +257,7 @@ "Hecho. El 2FA está ahora activo. La próxima vez que te logues, el panel pide primero la contraseña; una vez aceptada pide el código actual de 6 dígitos." ], "setupStep4Sub": [ - "Camino fácil: en la app, pulsa Añadir cuenta → Escanear código QR, apunta la cámara al QR en la pantalla. La app nombra la entrada automáticamente (algo como ProxMenux Monitor (tu-usuario)) y empieza a mostrar un código de 6 dígitos que se refresca cada 30 segundos.", + "Camino fácil: en la app, pulsa Añadir cuenta → Escanear código QR, apunta la cámara al QR en la pantalla. La app nombra la entrada automáticamente (algo como ProxMenux Monitor (your-username)) y empieza a mostrar un código de 6 dígitos que se refresca cada 30 segundos.", "Plan B manual (cuando escanear no es posible — p. ej. configurando en el mismo móvil con el que abriste el panel): pulsa Añadir cuenta → Introducir clave de setup. Escribe cualquier nombre (p. ej. Proxmox Monitor), pega la cadena Base32 del diálogo, deja Tipo como Basado en tiempo, guarda." ], "testTitle": "Prueba antes de hacer logout", @@ -319,7 +319,7 @@ "heading": "Secure Gateway (Tailscale)", "intro": "Los reverse proxies son la respuesta clásica a \"llegar al panel desde fuera\" pero requieren un dominio público, certificado y un puerto abierto en el borde. Secure Gateway es la alternativa cero-puertos que viene dentro del propio Monitor — una app desplegable preconstruida que levanta un LXC Alpine ejecutando Tailscale como subnet router. Una vez unido a tu tailnet, cada dispositivo en él puede llegar al Monitor en la propia IP LAN del host — desde un portátil de vacaciones, un móvil con 5G u otro nodo — sin exponer TCP 8008 a internet.", "calloutTitle": "Por qué esto es cómodo", - "calloutBody": "La URL se mantiene igual que en la LAN — http://<ip-lan-proxmox>:8008 funciona donde Tailscale funcione. Sin certificados, sin DNS, sin port forwarding. El propio Monitor ve la petición viniendo de una IP de tailnet (típicamente 100.x.y.z), así que el log de auth y el hook de Fail2Ban siguen funcionando como en la LAN.", + "calloutBody": "La URL se mantiene igual que en la LAN — http://<proxmox-lan-ip>:8008 funciona donde Tailscale funcione. Sin certificados, sin DNS, sin port forwarding. El propio Monitor ve la petición viniendo de una IP de tailnet (típicamente 100.x.y.z), así que el log de auth y el hook de Fail2Ban siguen funcionando como en la LAN.", "deployBody": "El flujo de despliegue es una sola pantalla — elige el storage LXC del host, pega un auth-key de Tailscale (generado en login.tailscale.com/admin/settings/keys), elige qué subnets anunciar, pulsa Deploy. El LXC tarda ~30 segundos en arrancar y se registra en el tailnet automáticamente.", "outro": "El despliegue paso a paso, la configuración de subnet-routes, las ACLs de Tailscale y el modo Exit Node se documentan por separado en Dashboard → Security → Secure Gateway — ahí vive el wizard de despliegue en la UI del panel. Esta página solo cubre el patrón de acceso." }, diff --git a/web/messages/es/docs/monitor/api-reference.json b/web/messages/es/docs/monitor/api-reference.json index b2906347..11ffc9eb 100644 --- a/web/messages/es/docs/monitor/api-reference.json +++ b/web/messages/es/docs/monitor/api-reference.json @@ -55,7 +55,7 @@ "heading": "Convenciones", "items": [ "Todas las peticiones y respuestas son JSON salvo que se indique explícitamente (la descarga de logs es texto plano, /api/prometheus es text/plain en formato OpenMetrics, el log de tareas es texto plano).", - "Los endpoints que modifican estado con éxito devuelven '{'\"success\": true, ...'}'. Las respuestas de error usan un estado HTTP no-2xx con '{'\"success\": false, \"error\": \"'<'motivo'>'\"'}'.", + "Los endpoints que modifican estado con éxito devuelven '{'\"success\": true, ...'}'. Las respuestas de error usan un estado HTTP no-2xx con '{'\"success\": false, \"error\": \"'<'reason'>'\"'}'.", "Los endpoints de listado aceptan limit, offset, since opcionales y filtros específicos por categoría vía query string.", "Los campos de tiempo se devuelven como segundos epoch Unix o ISO-8601 con zona horaria explícita, nunca como cadenas de locale." ] @@ -174,7 +174,7 @@ "syncVsAsyncBody": "Las acciones a nivel de sistema (power del host, PVE update, self-update de ProxMenux) pueden tardar minutos — corren en su propia unit systemd transitoria y exponen un endpoint .../status más un DELETE para cancelar. Las acciones VM / LXC (start, stop, shutdown, reboot, backup) son rápidas y usan el estilo fire-and-return que la UI del Monitor ya expone: el POST devuelve el resultado directo. Ambos estilos comparten el mismo modelo de autenticación (JWT o token de API de larga duración).", "curlTitle": "Ejemplo con curl", "curlBody": "Disparar una actualización de PVE y hacer polling hasta que termine — exactamente la misma secuencia que ejecutaría una automatización de Home Assistant o un playbook de Ansible:", - "curlCode": "TOKEN=\n\ncurl -sSf -X POST \\\n -H \"Authorization: Bearer $TOKEN\" \\\n https://:8008/api/system/pve-update/run\n\nwhile true; do\n state=$(curl -sSf -H \"Authorization: Bearer $TOKEN\" \\\n https://:8008/api/system/pve-update/status | jq -r .state)\n [ \"$state\" != \"running\" ] && break\n sleep 30\ndone\necho \"estado final: $state\"", + "curlCode": "TOKEN=\n\ncurl -sSf -X POST \\\n -H \"Authorization: Bearer $TOKEN\" \\\n https://:8008/api/system/pve-update/run\n\nwhile true; do\n state=$(curl -sSf -H \"Authorization: Bearer $TOKEN\" \\\n https://:8008/api/system/pve-update/status | jq -r .state)\n [ \"$state\" != \"running\" ] && break\n sleep 30\ndone\necho \"final state: $state\"", "haTitle": "Snippet de integración con Home Assistant", "haBody": "Un sensor rest hace polling del estado y un rest_command dispara la actualización. Conecta ambos a un botón del dashboard y a las automatizaciones que quieras:", "haCode": "# configuration.yaml\nsensor:\n - platform: rest\n resource: https://pve.local:8008/api/system/pve-update/status\n name: pve_update_state\n value_template: \"{{ value_json.state }}\"\n scan_interval: 60\n headers:\n Authorization: \"Bearer !secret proxmenux_token\"\n\nrest_command:\n pve_update:\n url: https://pve.local:8008/api/system/pve-update/run\n method: POST\n headers:\n Authorization: \"Bearer !secret proxmenux_token\"\n\n # Encender VM 100 (funciona igual para LXC — mismo endpoint)\n vm_100_start:\n url: https://pve.local:8008/api/vms/100/control\n method: POST\n content_type: 'application/json'\n payload: '{\"action\": \"start\"}'\n headers:\n Authorization: \"Bearer !secret proxmenux_token\"\n\n # Backup de la VM 100 al storage 'pbs-main'\n vm_100_backup:\n url: https://pve.local:8008/api/vms/100/backup\n method: POST\n content_type: 'application/json'\n payload: '{\"storage\": \"pbs-main\", \"mode\": \"snapshot\", \"compress\": \"zstd\"}'\n headers:\n Authorization: \"Bearer !secret proxmenux_token\"" diff --git a/web/messages/es/docs/monitor/dashboard/vms-lxcs-app.json b/web/messages/es/docs/monitor/dashboard/vms-lxcs-app.json index 08895a8f..1cafcd5b 100644 --- a/web/messages/es/docs/monitor/dashboard/vms-lxcs-app.json +++ b/web/messages/es/docs/monitor/dashboard/vms-lxcs-app.json @@ -101,7 +101,7 @@ ], "sourcesHeading": "Fuentes para la versión disponible", "sources": [ - "Repositorio de GitHub: último lanzamiento o etiqueta de un repositorio público propietario/nombre.", + "Repositorio de GitHub: último lanzamiento o etiqueta de un repositorio público owner/name.", "HTTP JSON: un punto final público y una ruta como data.version o releases[0].tag_name.", "Docker Hub: etiquetas versionadas filtradas con una expresión regular. La vista previa muestra etiquetas reales coincidentes antes de guardar.", "Las etiquetas móviles como latest, stable o lts no contienen una versión. Esas imágenes se siguen por digest desde las actualizaciones de imágenes Docker." diff --git a/web/messages/es/docs/monitor/dashboard/vms-lxcs-updates.json b/web/messages/es/docs/monitor/dashboard/vms-lxcs-updates.json index 6bd3f77d..0fb53ddb 100644 --- a/web/messages/es/docs/monitor/dashboard/vms-lxcs-updates.json +++ b/web/messages/es/docs/monitor/dashboard/vms-lxcs-updates.json @@ -110,8 +110,8 @@ "No incluyas pct exec; ProxMenux ya entra en el contenedor y ejecuta el comando como root." ], "exampleLead": "Ejemplo de procedimiento completo dentro del contenedor:", - "example": "cd /opt/mi-app && ./update.sh", - "callout": "Un comando de versión como miapp --version solo lee una versión; no instala nada. Los comandos se ejecutan con privilegios administrativos y deben revisarse con el mismo cuidado que un comando de shell como root." + "example": "cd /opt/my-app && ./update.sh", + "callout": "Un comando de versión como myapp --version solo lee una versión; no instala nada. Los comandos se ejecutan con privilegios administrativos y deben revisarse con el mismo cuidado que un comando de shell como root." }, "bulk": { "heading": "Actualización en bloque", @@ -142,7 +142,7 @@ "La espera tras una versión solo se aplica a las apps seleccionadas con seguimiento. Las apps sin seguimiento ejecutan su actualizador cuando vence la programación.", "El estado de la última ejecución diferencia entre éxito, finalización parcial, error, retención de seguridad y ausencia de elementos pendientes. Después de la primera ejecución programada, Actualizaciones → Actualizaciones programadas → Ver log abre la salida completa capturada del actualizador y de los scripts secundarios que haya ejecutado.", "Después de cada ejecución, ProxMenux comprueba el marcador estándar de Debian que indica si es necesario reiniciar. Cuando hace falta, la pestaña Actualizaciones y la notificación de finalización lo indican; el aviso se elimina cuando ese LXC se inicia o reinicia.", - "En el host Proxmox, los logs de las ejecuciones programadas se guardan en /usr/local/share/proxmenux/logs/lxc-updates/ con el nombre <VMID>-scheduled-<id-de-ejecución>.log. Se conservan los diez últimos logs de cada LXC y los archivos más antiguos se eliminan automáticamente.", + "En el host Proxmox, los logs de las ejecuciones programadas se guardan en /usr/local/share/proxmenux/logs/lxc-updates/ con el nombre <VMID>-scheduled-<run-id>.log. Se conservan los diez últimos logs de cada LXC y los archivos más antiguos se eliminan automáticamente.", "Este historial corresponde a las actualizaciones programadas. Una actualización manual muestra su salida en tiempo real en la ventana de ejecución del Monitor y no crea un log de ejecución programada en ese directorio.", "Las programaciones externas detectadas de Proxmox VE Helper-Scripts se muestran aparte para hacer visible cualquier automatización coincidente." ], @@ -186,7 +186,7 @@ }, { "problem": "Una actualización programada indica que es necesario reiniciar", - "resolution": "Abre Ver log para revisar la ejecución completada y reinicia ese LXC. ProxMenux elimina el aviso mediante el evento de ciclo de vida existente cuando el contenedor vuelve a arrancar." + "resolution": "Abre Ver registro para revisar la ejecución completada y después reinicia ese LXC. ProxMenux borra el aviso cuando el evento de ciclo de vida detecta el nuevo arranque." } ] }, diff --git a/web/messages/es/docs/monitor/integrations.json b/web/messages/es/docs/monitor/integrations.json index 36896b10..13c8d5de 100644 --- a/web/messages/es/docs/monitor/integrations.json +++ b/web/messages/es/docs/monitor/integrations.json @@ -37,7 +37,7 @@ "headingHref": "https://gethomepage.dev", "intro": "Homepage es un panel de aplicaciones totalmente estático y personalizable. ProxMenux Monitor se enchufa vía el widget customapi integrado — pega una entrada de servicio en services.yaml, reinicia Homepage y la tarjeta aparece con números en vivo.", "iconCalloutTitle": "El logo oficial de ProxMenux está en dashboardicons.com", - "iconCalloutBody": "Las guías de abajo usan icon: proxmenux.png. Homepage resuelve automáticamente los nombres de archivo simples contra dashboardicons.com — una librería de iconos curada para paneles autoalojados. La entrada de ProxMenux vive en dashboardicons.com/icons/external/proxmenux y Homepage la descarga en el primer render. La misma búsqueda funciona para miles de otras herramientas (Telegram, Discord, Grafana, Tailscale, etc.) — basta con escribir icon: <nombre>.png en cualquier entrada de servicio.", + "iconCalloutBody": "Las guías de abajo usan icon: proxmenux.png. Homepage resuelve automáticamente los nombres de archivo simples contra dashboardicons.com — una librería de iconos curada para paneles autoalojados. La entrada de ProxMenux vive en dashboardicons.com/icons/external/proxmenux y Homepage la descarga en el primer render. La misma búsqueda funciona para miles de otras herramientas (Telegram, Discord, Grafana, Tailscale, etc.) — basta con escribir icon: <name>.png en cualquier entrada de servicio.", "imageAlt": "Panel Homepage mostrando tres tarjetas de ProxMenux Monitor (EDGE, VOID, DREAM) con uptime, CPU, RAM y temperatura para cada host Proxmox", "imageCaption": "Tres instancias de ProxMenux Monitor renderizadas como tarjetas Homepage — uptime, CPU, RAM y temperatura de CPU leídas en vivo desde /api/system en cada host cada 10 s.", "basicTitle": "Widget básico — sin autenticación", @@ -94,7 +94,7 @@ "twoColTipBody": "Reemplaza cualquier par de tarjetas (p. ej. Sistema + VMs, o Almacenamiento + Red) con un único horizontal-stack envolviendo ambas, para que siempre se rendericen lado a lado independientemente del ancho de pantalla:", "twoColTipOutro": "En móvil la fila se mantiene comprimida; el auto layout de HA (sin horizontal-stack) refluye mejor a anchos estrechos.", "step6Title": "6 · Automatizaciones", - "step6Body": "Tres automatizaciones que cubren los escenarios reactivos más comunes — reemplaza notify.mobile_app_<tu_movil> por el servicio notify que uses:", + "step6Body": "Tres automatizaciones que cubren los escenarios reactivos más comunes — reemplaza notify.mobile_app_<your_phone> por el servicio notify que uses:", "logoTitle": "Sobre el logo de ProxMenux", "logoBody": "La tarjeta picture-entity en la parte superior del YAML Lovelace descarga el logo oficial de ProxMenux desde dashboardicons.com — una librería de iconos gratuita curada para paneles autoalojados. La entrada de ProxMenux vive en dashboardicons.com/icons/external/proxmenux. Home Assistant descarga el SVG sobre HTTPS en el primer render y lo cachea.", "logoBrokenTitle": "Si la tarjeta del logo muestra una imagen rota", @@ -120,7 +120,7 @@ "tokenTipTitle": "Token vía archivo o env, no inline", "tokenTipBody": "Para despliegues en producción evita poner el token inline. Prometheus soporta credentials_file: /etc/prometheus/secrets/proxmenux.token como alternativa — guarda el token en un archivo 0600 y deja que Prometheus lo lea.", "step2Title": "2 · Verifica el scrape con un par de queries", - "step2Body": "Antes de configurar Grafana, confirma que Prometheus realmente tiene los datos. Abre la propia UI de Prometheus en http://<host-prometheus>:9090, pulsa Query y ejecuta cualquiera de estas — deberías obtener números en vivo de tu host Proxmox:", + "step2Body": "Antes de configurar Grafana, confirma que Prometheus realmente tiene los datos. Abre la propia UI de Prometheus en http://<prometheus-host>:9090, pulsa Query y ejecuta cualquiera de estas — deberías obtener números en vivo de tu host Proxmox:", "headerQuery": "Query", "headerConfirms": "Qué confirma", "verifyRows": [ diff --git a/web/messages/es/docs/monitor/notifications.json b/web/messages/es/docs/monitor/notifications.json index 85089fa0..6a0cf1f3 100644 --- a/web/messages/es/docs/monitor/notifications.json +++ b/web/messages/es/docs/monitor/notifications.json @@ -44,7 +44,7 @@ "Arranca el hilo de fondo de despacho. El hilo hace polling de la cola de eventos y camina cada evento por la pipeline diagramada arriba." ], "activeAlt": "Tarjeta de notificaciones activada con pestañas de canales, nombre visible y opciones avanzadas de IA", - "activeCaption": "Estado activo: pestañas Telegram, Gotify, Discord, Email, Pushover y Apprise, nombre visible, categorías por canal y opciones avanzadas de IA." + "activeCaption": "Estado activo: pestañas de canales en la parte superior (Telegram / Gotify / Discord / correo electrónico / Pushover / Apprise), campo Nombre para mostrar, lista de categorías de cada canal y sección desplegable Avanzado: mejora con IA." }, "sources": { "heading": "Fuentes de eventos", @@ -119,7 +119,7 @@ "groupItems": [ "Añade el bot al grupo como miembro (y hazlo admin si el grupo lo requiere para publicar).", "Envía cualquier mensaje en el grupo.", - "Abre https://api.telegram.org/bot<TU_TOKEN>/getUpdates en un navegador. Busca chat.id en la respuesta JSON — para grupos es un número negativo, para supergrupos empieza con -100.", + "Abre https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates en un navegador. Busca chat.id en la respuesta JSON — para grupos es un número negativo, para supergrupos empieza con -100.", "Para supergrupos con Topics activado, anota también el message_thread_id del tema al que quieres apuntar — eso va en el campo opcional Topic ID." ], "groupAlt": "Formulario de canal Telegram con Bot Token (enmascarado), Chat ID negativo prefijado con -100 indicando un supergrupo, y Topic ID 3 configurado para entregar a un tema específico", @@ -203,7 +203,7 @@ "stepsTitle": "Pasos", "steps": [ "Elige el servicio destino en la wiki de Apprise y copia la plantilla de URL para él. Cada página de servicio muestra el esquema exacto a usar (ntfy://, matrix://, pover://, slack://…) más cualquier token, canal u hostname requerido.", - "Rellena los placeholders con tus propias credenciales. Por ejemplo, un topic de ntfy.sh tiene aspecto de ntfy://ntfy.sh/mi-topic; una URL de Pushover tiene aspecto de pover://user@token; una URL de Matrix tiene aspecto de matrix://user:pass@host:port/#room.", + "Sustituye los marcadores por tus credenciales. Ejemplos: ntfy.sh ntfy://ntfy.sh/my-topic; Pushover pover://user@token; Matrix matrix://user:pass@host:port/#room.", "Pega la URL final en el campo Apprise URL en la pestaña Apprise del panel Notifications y guarda.", "Pulsa Send Test para verificar que la URL es alcanzable y las credenciales se aceptan." ], @@ -295,7 +295,7 @@ "rows": [ { "stage": "1. Deduplicación por huella", - "what": "Cada evento genera una huella (event_type + campos clave de data). Las huellas idénticas dentro de una ventana corta se consideran duplicados del primero.", + "what": "Cada evento genera una huella (event_type + key fields from data). Las huellas idénticas dentro de una ventana corta se consideran duplicados del primero.", "tunable": "No — lógica interna del dispatcher." }, { @@ -339,7 +339,7 @@ "Llamadores remotos. Cinco capas se apilan encima del rate-limiting: un shared secret en la cabecera X-Webhook-Secret, un timestamp de frescura en X-ProxMenux-Timestamp (rechazado si se desvía más allá de la ventana configurada), una búsqueda de caché de replay y una allowlist de IP opcional. El shared secret vive en la tabla de ajustes SQLite del Monitor — no en /etc/pve/priv/notifications.cfg — y se genera en el primer setup. Este camino existe para integraciones personalizadas que hacen POST desde fuera del host; el destino configurado por PVE nunca lo ejercita." ], "practiceTitle": "En la práctica", - "practiceBody": "El setup de PVE escribe el destino como http://127.0.0.1:8008, así que las notificaciones emitidas por PVE siempre pasan por el camino loopback con seguridad solo rate-limit. El camino de llamador remoto con shared secret es opt-in para integraciones personalizadas — apunta un servicio externo a https://<host-monitor>:<port>/api/notifications/webhook y suministra la cabecera X-Webhook-Secret para usarlo.", + "practiceBody": "El setup de PVE escribe el destino como http://127.0.0.1:8008, así que las notificaciones emitidas por PVE siempre pasan por el camino loopback con seguridad solo rate-limit. El camino de llamador remoto con shared secret es opt-in para integraciones personalizadas — apunta un servicio externo a https://<monitor-host>:<port>/api/notifications/webhook y suministra la cabecera X-Webhook-Secret para usarlo.", "actionsIntro": "El Monitor gestiona este destino a través de tres acciones en la pestaña Settings:", "actionsItems": [ "Setup — corre automáticamente cuando activas Notifications. Crea la entrada en /etc/pve/notifications.cfg. El archivo solo se escribe cuando su contenido tiene que cambiar, y se guarda un backup del anterior en /usr/local/share/proxmenux/backups/pve-notifications/ (los tres más recientes). No se escribe nada mientras /etc/pve no está montado: un Monitor que arranca durante un reinicio de pve-cluster registra la entrada cuando vuelve.", diff --git a/web/messages/es/docs/oci-manager/hardware.json b/web/messages/es/docs/oci-manager/hardware.json index b4f24154..92d0bf14 100644 --- a/web/messages/es/docs/oci-manager/hardware.json +++ b/web/messages/es/docs/oci-manager/hardware.json @@ -59,7 +59,7 @@ "table": { "headers": ["Tipo", "Identificación", "Validación"], "rows": [ - ["Intel/AMD DRM", "/dev/dri/renderD* y /sys/class/drm/NODO/device/vendor", "Un dispositivo de caracteres con fabricante 0x8086 (Intel) o 0x1002 (AMD)"], + ["Intel/AMD DRM", "/dev/dri/renderD* y /sys/class/drm/NODE/device/vendor", "Un dispositivo de caracteres con fabricante 0x8086 (Intel) o 0x1002 (AMD)"], ["OpenCL AMD", "El render node, más /dev/kfd cuando el perfil lo necesita", "Existencia, tipo, fabricante, permisos y compatibilidad declarada"], ["AMD ROCm", "El nodo de render, /dev/kfd y la generación que declara el driver de cómputo en /sys/class/kfd/kfd/topology/nodes/*/properties", "Una generación para la que la imagen ROCm de la aplicación trae kernels, las GPU RDNA2 que incluye y las posteriores, se ofrece tal cual. Las demás GPU de esas familias, como las Radeon 680M y 780M, se ofrecen como experimentales y nunca son la opción propuesta: ROCm no las soporta oficialmente, la aplicación las presenta como la generación de su familia (HSA_OVERRIDE_GFX_VERSION) y el instalador pide confirmación antes de descargar la imagen, que es más grande. Una GPU anterior, como los gráficos integrados de un Ryzen 5000U, no se ofrece e Immich deja el reconocimiento en la CPU"], ["NVIDIA", "nvidia-smi y nvidia-container-cli", "GPU, UUID, bus PCI, versión del driver, Toolkit, nodos /dev/nvidia*, binarios y librerías"], diff --git a/web/messages/es/docs/oci-manager/storage-network.json b/web/messages/es/docs/oci-manager/storage-network.json index 08d67c4e..00849e0c 100644 --- a/web/messages/es/docs/oci-manager/storage-network.json +++ b/web/messages/es/docs/oci-manager/storage-network.json @@ -112,7 +112,7 @@ "items": [ { "title": "Contenedor y remoto", "body": "El VMID del contenedor Rclone, el nombre exacto del remoto y, opcionalmente, una ruta dentro de él." }, { "title": "Nombre y caché", "body": "El nombre del montaje y el modo de caché VFS: off, minimal, writes o full (por defecto)." }, - { "title": "Vistas publicadas", "body": "Una raíz común, /mnt/oci-shared por defecto, contiene una vista de lectura y escritura en /mnt/oci-shared/remotes/NOMBRE y una de solo lectura en /mnt/oci-shared/remotes-ro/NOMBRE." }, + { "title": "Vistas publicadas", "body": "Una raíz común, /mnt/oci-shared de forma predeterminada, contiene una vista de lectura y escritura en /mnt/oci-shared/remotes/NAME y otra de solo lectura en /mnt/oci-shared/remotes-ro/NAME." }, { "title": "Activación", "body": "Tras una confirmación, el CT se detiene, se configuran su orden de arranque y un hookscript de Proxmox VE, y se vuelve a iniciar. La operación espera hasta que las dos vistas están montadas en el host." } ] } diff --git a/web/messages/es/docs/post-install/system.json b/web/messages/es/docs/post-install/system.json index 0861d3b7..1abad55d 100644 --- a/web/messages/es/docs/post-install/system.json +++ b/web/messages/es/docs/post-install/system.json @@ -20,7 +20,7 @@ "SystemMaxUse=64M / RuntimeMaxUse=60M — caps duros sobre el uso de disco/memoria del journal.", "Compress=yes, Seal=no — comprimir logs, saltar el forward-secure sealing (ahorra CPU).", "MaxLevelStore=info — almacenar info y superiores (requerido para el visor de logs del ProxMenux Monitor y para que Fail2Ban detecte fallos de autenticación SSH/Proxmox desde el journal).", - "Rate-limits: 1000 eventos / 30 s para prevenir flooding de logs.", + "Límite de frecuencia: 1000 events / 30 s para evitar una avalancha de registros.", "ForwardToSyslog=no, ForwardToWall=no — no duplicar mensajes a syslog ni broadcastear a consolas." ], "tipTitle": "Por qué importa MaxLevelStore=info", diff --git a/web/messages/es/docs/storage-share/host-iscsi.json b/web/messages/es/docs/storage-share/host-iscsi.json index 04be8a5f..e77d54ca 100644 --- a/web/messages/es/docs/storage-share/host-iscsi.json +++ b/web/messages/es/docs/storage-share/host-iscsi.json @@ -56,7 +56,7 @@ "Entrada del portal — escribe la IP o hostname del servidor del target iSCSI. Se asume el puerto 3260 a menos que escribas host:port.", "Descubrimiento de targets — ProxMenux llama a iscsiadm --mode discovery --type sendtargets --portal <ip:port>. El servidor responde con cada IQN que permite ver a este initiator. Si el descubrimiento falla, el script muestra el error exacto de iscsiadm y lista las causas comunes (IP incorrecta, firewall, IQN del initiator no autorizado).", "Selección del target — si hay un solo target se selecciona automáticamente; con varios targets se muestra un menú.", - "Storage ID — por defecto es iscsi-<último segmento del IQN, máx. 20 caracteres>. Solo letras, dígitos, - y _.", + "Storage ID — por defecto es iscsi-<last IQN segment, 20 chars max>. Solo letras, dígitos, - y _.", "Content type — a diferencia de NFS / Samba, el contenido iSCSI está fijado a images. iSCSI expone dispositivos de bloque raw, no un sistema de archivos, así que Proxmox solo te deja alojar discos de VM ahí." ], "authTitle": "Autorizar el initiator de Proxmox en el target", diff --git a/web/messages/es/docs/storage-share/lxc-mount-points.json b/web/messages/es/docs/storage-share/lxc-mount-points.json index d89b7a54..fa80e077 100644 --- a/web/messages/es/docs/storage-share/lxc-mount-points.json +++ b/web/messages/es/docs/storage-share/lxc-mount-points.json @@ -129,7 +129,7 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "noMountTitle": "El montaje aparece en la config del pct pero no dentro del contenedor", + "noMountTitle": "El montaje aparece en pct config, pero no dentro del contenedor", "noMountBody": "Los bind mounts solo se adjuntan cuando el contenedor arranca. Si añadiste el montaje mientras el CT estaba corriendo, reinícialo (pct reboot <ctid>). Si ya está reiniciado y sigue faltando, comprueba pct config <ctid> para confirmar que la entrada existe y que la ruta del host realmente contiene un directorio.", "noWriteTitle": "El contenedor puede ver el directorio pero no puede escribir", "noWriteBody": "Casi siempre un problema de permisos. Para contenedores unprivileged, ejecuta de nuevo el fix de permisos del script — las ACLs del directorio del host pueden haber sido reseteadas por otra herramienta (rsync, restore desde backup, chmod manual). Para CIFS, revisa las opciones de montaje en el host: findmnt /mnt/pve/cifs-…, busca valores uid= / gid= / file_mode=.", diff --git a/web/messages/fr/common.json b/web/messages/fr/common.json new file mode 100644 index 00000000..76c712d2 --- /dev/null +++ b/web/messages/fr/common.json @@ -0,0 +1,254 @@ +{ + "site": { + "name": "ProxMenux", + "tagline": "Menu interactif et tableau de bord web pour Proxmox VE" + }, + "nav": { + "home": "Accueil", + "docs": "Documentation", + "documentation": "Documentation", + "guides": "Guides", + "changelog": "Journal des modifications", + "github": "GitHub", + "rss": "RSS", + "rssTitle": "Flux RSS", + "menuOpen": "Ouvrir le menu" + }, + "footer": { + "sponsorHeading": "Soutenir le projet", + "sponsorBody": "Si vous souhaitez soutenir le projet.", + "sponsorAlt": "Soutenez-moi sur Ko-fi", + "connectHeading": "Communauté", + "connectBody": "Rejoignez les discussions de la communauté sur GitHub pour obtenir de l’aide, partager vos idées et contribuer au projet. Toutes les idées sont les bienvenues !", + "joinDiscussion": "Participer à la discussion", + "copyrightPrefix": "ProxMenux, un projet collaboratif et open source créé par", + "copyrightSuffix": "." + }, + "language": { + "switcher": "Langue", + "en": "English", + "es": "Español", + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" + }, + "resources": { + "documentation": { + "title": "Documentation", + "description": "Présentation du système et guides d’utilisation" + }, + "changelog": { + "title": "Journal des modifications", + "description": "Informations sur les dernières mises à jour" + }, + "guides": { + "title": "Guides", + "description": "Tutoriels et guides pas à pas pour les tâches courantes" + }, + "github": { + "title": "Dépôt GitHub", + "description": "Consultez le code source." + } + }, + "docSidebar": { + "documentation": "Documentation", + "items": { + "introduction": "Introduction", + "installation": "Installation", + "proxmenuxMonitor": "ProxMenux Monitor", + "monitorOverview": "Vue d’ensemble", + "architecture": "Architecture", + "accessAuth": "Accès et authentification", + "dashboard": "Tableau de bord", + "dashboardSystemOverview": "Onglet Vue d’ensemble du système", + "dashboardApps": "Onglet Applications", + "dashboardStorage": "Onglet Stockage", + "dashboardNetwork": "Onglet Réseau", + "dashboardVmsLxcs": "Onglet VM et LXC", + "dashboardVmsLxcsApp": "Application", + "dashboardVmsLxcsUpdates": "Mises à jour", + "dashboardHardware": "Onglet Matériel", + "dashboardSystemLogs": "Onglet Journaux système", + "dashboardTerminal": "Onglet Terminal", + "dashboardSecurity": "Onglet Sécurité", + "dashboardSettings": "Onglet Paramètres", + "healthMonitor": "Surveillance de l’état du système", + "auditReport": "Audit et rapports", + "auditReportOverview": "Vue d’ensemble", + "auditReportAssessment": "Évaluation et inventaire", + "auditReportPolicy": "Politique", + "auditReportChanges": "Modifications", + "auditReportReports": "Rapports et comparaisons", + "auditReportScope": "Périmètre et garanties", + "notifications": "Notifications", + "aiAssistant": "Assistant IA", + "apiReference": "Référence de l’API", + "integrations": "Intégrations", + "ociManager": "OCI manager Apps", + "ociOverview": "Vue d’ensemble", + "ociArchitecture": "Comment une image OCI est adaptée", + "ociCustomImage": "Image absente du catalogue", + "ociStorageNetwork": "Données, chemins et réseau", + "ociHardware": "Périphériques et accélération", + "ociStacks": "Applications à plusieurs conteneurs", + "ociLifecycle": "Installer, mettre à jour et modifier", + "ociMonitor": "Dans ProxMenux Monitor", + "proxmenuxScripts": "ProxMenux Scripts", + "postInstallScript": "Script de post-installation", + "postInstallOverview": "Vue d’ensemble", + "postInstallAutomated": "Automatique", + "postInstallCustomizable": "Personnalisable", + "postInstallBasicSettings": "Paramètres de base", + "postInstallSystem": "Système", + "postInstallVirtualization": "Virtualisation", + "postInstallNetwork": "Réseau", + "postInstallStorage": "Stockage", + "postInstallSecurity": "Sécurité", + "postInstallCustomization": "Personnalisation", + "postInstallMonitoring": "Surveillance", + "postInstallPerformance": "Performances", + "postInstallOptional": "Facultatif", + "postInstallUpdates": "Appliquer les mises à jour disponibles", + "postInstallUninstall": "Supprimer les optimisations", + "gpusCoralTpu": "GPU et Coral TPU", + "nvidiaHost": "Installer les pilotes NVIDIA sur l’hôte", + "coralHost": "Installer Coral TPU sur l’hôte", + "addGpuLxc": "Donner accès à un GPU dans un LXC", + "addCoralLxc": "Donner accès à Coral TPU dans un LXC", + "addGpuVm": "Attribuer un GPU à une VM (passthrough)", + "switchGpuMode": "Changer le mode du GPU (VM ↔ LXC)", + "createVm": "Créer une VM", + "createVmOverview": "Vue d’ensemble", + "createVmSystemNas": "Systèmes NAS", + "createVmSynology": "VM Synology", + "createVmNasOthers": "Autres systèmes NAS", + "createVmSystemWindows": "Systèmes Windows", + "createVmSystemLinux": "Systèmes Linux", + "diskManager": "Gestionnaire de disques", + "diskManagerOverview": "Vue d’ensemble", + "diskImportVm": "Importer un disque dans une VM", + "diskImportImageVm": "Importer une image disque dans une VM", + "diskAddController": "Ajouter un contrôleur ou un périphérique NVMe à une VM", + "diskImportLxc": "Importer un disque dans un LXC", + "diskFormat": "Formater ou effacer un disque physique", + "diskSmart": "État SMART et tests du disque", + "storageShareManager": "Gestion du stockage et des partages", + "storageShareOverview": "Vue d’ensemble", + "hostStorage": "Intégration du stockage à l’hôte", + "hostNfs": "Ajouter un partage NFS comme stockage Proxmox", + "hostSamba": "Ajouter un partage Samba comme stockage Proxmox", + "hostIscsi": "Ajouter une cible iSCSI comme stockage Proxmox", + "hostLocalDisk": "Ajouter un disque local comme stockage Proxmox", + "hostLocalShared": "Créer un répertoire partagé sur l’hôte", + "lxcMountPoints": "Points de montage LXC (hôte ↔ CT)", + "lxcNetworkSharing": "Partages réseau dans LXC", + "lxcNfsClient": "Client NFS dans LXC", + "lxcSambaClient": "Client Samba dans LXC", + "lxcNfsServer": "Serveur NFS dans LXC", + "lxcSambaServer": "Serveur Samba dans LXC", + "network": "Réseau", + "networkOverview": "Vue d’ensemble", + "networkDiagnostics": "Diagnostic", + "networkMonitoring": "Outils de surveillance en temps réel", + "networkBridge": "Analyse et réparation des ponts réseau", + "networkConfig": "Analyse et nettoyage de la configuration", + "networkPersistent": "Noms persistants des interfaces", + "networkBackup": "Sauvegarde et redémarrage des interfaces", + "security": "Sécurité", + "securityOverview": "Vue d’ensemble", + "securityFail2ban": "Fail2Ban", + "securityLynis": "Lynis", + "utilities": "Utilitaires", + "utilsOverview": "Vue d’ensemble", + "utilsUupDump": "Création d’ISO avec UUP Dump", + "utilsSystemUtils": "Installation d’utilitaires système", + "utilsSystemUpdate": "Mise à jour du système Proxmox", + "utilsUpgradePve": "Passer de PVE 8 à PVE 9", + "utilsExportVm": "Exporter une VM au format OVA/OVF", + "utilsImportVm": "Importer une VM depuis OVA/OVF", + "settingsProxmenux": "Paramètres de ProxMenux", + "settingsOverview": "Vue d’ensemble", + "settingsMonitor": "ProxMenux Monitor", + "settingsBeta": "Changer de canal de publication", + "settingsVersion": "Afficher les informations de version", + "settingsUninstall": "Désinstaller ProxMenux", + "commandsReference": "Référence des commandes", + "commandsOverview": "Vue d’ensemble", + "commandsSystem": "Commandes système utiles", + "commandsVmCt": "Gestion des VM et CT", + "commandsStorage": "Stockage et disques", + "commandsNetwork": "Commandes réseau", + "commandsUpdates": "Mises à jour et paquets", + "commandsGpu": "Passthrough GPU", + "commandsZfs": "Gestion de ZFS", + "commandsBackup": "Sauvegarde et restauration", + "commandsTools": "Outils système en ligne de commande", + "glossary": "Glossaire", + "about": "À propos", + "aboutOverview": "Vue d’ensemble", + "aboutFaq": "FAQ", + "aboutContributors": "Contributeurs", + "aboutContributing": "Contribuer", + "aboutCodeOfConduct": "Code de conduite", + "externalRepositories": "Dépôts externes", + "backupRestore": "Sauvegarde et restauration", + "backupRestoreOverview": "Vue d’ensemble", + "backupRestoreHowItWorks": "Fonctionnement", + "backupRestoreDestinations": "Destinations", + "backupRestoreDestOverview": "Vue d’ensemble", + "backupRestoreDestLocal": "Archive locale", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Créer des sauvegardes", + "backupRestoreJobs": "Tâches planifiées", + "backupRestoreRestoring": "Restaurer", + "backupRestoreCrossKernel": "Restauration entre versions du noyau", + "backupRestoreGlossary": "Glossaire" + } + }, + "hero": { + "title": "ProxMenux", + "tagline": "Un menu interactif et un tableau de bord web pour Proxmox VE", + "description": "ProxMenux réunit un menu interactif et un tableau de bord web pour Proxmox VE. Exécutez des commandes, des scripts et des assistants depuis le terminal, ou consultez l’état de l’hôte, ses métriques et ses notifications dans un navigateur.", + "installButton": "Installer maintenant", + "whatIsButton": "Qu’est-ce que ProxMenux ?", + "logoAlt": "Logo ProxMenux" + }, + "docNav": { + "previous": "Précédent", + "next": "Suivant" + }, + "tocPanel": { + "onThisPage": "Sur cette page" + }, + "supportProject": { + "heading": "Soutenez le projet !", + "body": "Si ProxMenux vous est utile, donnez-lui une ⭐ sur GitHub pour aider d’autres personnes à le découvrir.", + "button": "Ajouter une étoile sur GitHub" + }, + "rssLink": { + "heading": "Suivez les nouveautés !", + "body": "Abonnez-vous à notre flux RSS pour être informé des nouveautés.", + "copy": "Copier", + "copied": "Copié !", + "copyTitle": "Copier l’URL du flux RSS", + "openFeed": "Ouvrir le flux RSS", + "openTitle": "Ouvrir le flux RSS" + }, + "changelog": { + "pageTitle": "Journal des modifications", + "meta": { + "title": "Journal des modifications de ProxMenux", + "titleSuffix": "Notes de version", + "latest": "Dernière version", + "mostRecent": "Version la plus récente", + "description": "Notes de version et historique des mises à jour de ProxMenux, le script à menus pour administrer Proxmox VE.", + "descriptionTail": "Nouvelles fonctionnalités, corrections de bugs, mises à jour de sécurité, améliorations de ProxMenux Monitor, compatibilité avec les fournisseurs d’IA, sécurisation du passthrough GPU et bien plus.", + "ogDescription": "Notes de version de ProxMenux.", + "ogTail": "Abonnez-vous au flux RSS ou revenez consulter les nouvelles versions." + } + } +} diff --git a/web/messages/it/common.json b/web/messages/it/common.json new file mode 100644 index 00000000..659c356d --- /dev/null +++ b/web/messages/it/common.json @@ -0,0 +1,254 @@ +{ + "site": { + "name": "ProxMenux", + "tagline": "Menu interattivo e dashboard web per Proxmox VE" + }, + "nav": { + "home": "Home", + "docs": "Documentazione", + "documentation": "Documentazione", + "guides": "Guide", + "changelog": "Registro delle modifiche", + "github": "GitHub", + "rss": "RSS", + "rssTitle": "Feed RSS", + "menuOpen": "Apri menu" + }, + "footer": { + "sponsorHeading": "Sostieni il progetto", + "sponsorBody": "Se desideri sostenere il progetto.", + "sponsorAlt": "Sostienimi su Ko-fi", + "connectHeading": "Community", + "connectBody": "Partecipa alle discussioni della community su GitHub per ricevere aiuto, condividere idee e contribuire al progetto. Ogni idea è benvenuta!", + "joinDiscussion": "Partecipa alla discussione", + "copyrightPrefix": "ProxMenux, un progetto collaborativo e open source di", + "copyrightSuffix": "." + }, + "language": { + "switcher": "Lingua", + "en": "English", + "es": "Español", + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" + }, + "resources": { + "documentation": { + "title": "Documentazione", + "description": "Descrizione del sistema e guide per l’utente" + }, + "changelog": { + "title": "Registro delle modifiche", + "description": "Informazioni sugli ultimi aggiornamenti" + }, + "guides": { + "title": "Guide", + "description": "Tutorial e guide passo passo per le attività più comuni" + }, + "github": { + "title": "Repository GitHub", + "description": "Esplora il codice sorgente." + } + }, + "docSidebar": { + "documentation": "Documentazione", + "items": { + "introduction": "Introduzione", + "installation": "Installazione", + "proxmenuxMonitor": "ProxMenux Monitor", + "monitorOverview": "Panoramica", + "architecture": "Architettura", + "accessAuth": "Accesso e autenticazione", + "dashboard": "Dashboard", + "dashboardSystemOverview": "Scheda Panoramica del sistema", + "dashboardApps": "Scheda App", + "dashboardStorage": "Scheda Archiviazione", + "dashboardNetwork": "Scheda Rete", + "dashboardVmsLxcs": "Scheda VM e LXC", + "dashboardVmsLxcsApp": "App", + "dashboardVmsLxcsUpdates": "Aggiornamenti", + "dashboardHardware": "Scheda Hardware", + "dashboardSystemLogs": "Scheda Log di sistema", + "dashboardTerminal": "Scheda Terminale", + "dashboardSecurity": "Scheda Sicurezza", + "dashboardSettings": "Scheda Impostazioni", + "healthMonitor": "Monitoraggio dello stato", + "auditReport": "Verifica e report", + "auditReportOverview": "Panoramica", + "auditReportAssessment": "Valutazione e inventario", + "auditReportPolicy": "Criteri", + "auditReportChanges": "Modifiche", + "auditReportReports": "Report e confronti", + "auditReportScope": "Ambito e garanzie", + "notifications": "Notifiche", + "aiAssistant": "Assistente IA", + "apiReference": "Riferimento API", + "integrations": "Integrazioni", + "ociManager": "OCI manager Apps", + "ociOverview": "Panoramica", + "ociArchitecture": "Come viene adattata un’immagine OCI", + "ociCustomImage": "Immagine non presente nel catalogo", + "ociStorageNetwork": "Dati, percorsi e rete", + "ociHardware": "Dispositivi e accelerazione", + "ociStacks": "Applicazioni con più container", + "ociLifecycle": "Installazione, aggiornamento e modifica", + "ociMonitor": "In ProxMenux Monitor", + "proxmenuxScripts": "ProxMenux Scripts", + "postInstallScript": "Script di post-installazione", + "postInstallOverview": "Panoramica", + "postInstallAutomated": "Automatico", + "postInstallCustomizable": "Personalizzabile", + "postInstallBasicSettings": "Impostazioni di base", + "postInstallSystem": "Sistema", + "postInstallVirtualization": "Virtualizzazione", + "postInstallNetwork": "Rete", + "postInstallStorage": "Archiviazione", + "postInstallSecurity": "Sicurezza", + "postInstallCustomization": "Personalizzazione", + "postInstallMonitoring": "Monitoraggio", + "postInstallPerformance": "Prestazioni", + "postInstallOptional": "Facoltativo", + "postInstallUpdates": "Applica gli aggiornamenti disponibili", + "postInstallUninstall": "Rimuovi le ottimizzazioni", + "gpusCoralTpu": "GPU e Coral TPU", + "nvidiaHost": "Installa i driver NVIDIA sull’host", + "coralHost": "Installa Coral TPU sull’host", + "addGpuLxc": "Abilita una GPU in un LXC", + "addCoralLxc": "Abilita Coral TPU in un LXC", + "addGpuVm": "Assegna una GPU a una VM (passthrough)", + "switchGpuMode": "Cambia modalità GPU (VM ↔ LXC)", + "createVm": "Crea una VM", + "createVmOverview": "Panoramica", + "createVmSystemNas": "Sistemi NAS", + "createVmSynology": "VM Synology", + "createVmNasOthers": "Altri sistemi NAS", + "createVmSystemWindows": "Sistemi Windows", + "createVmSystemLinux": "Sistemi Linux", + "diskManager": "Gestione dischi", + "diskManagerOverview": "Panoramica", + "diskImportVm": "Importa un disco in una VM", + "diskImportImageVm": "Importa un’immagine disco in una VM", + "diskAddController": "Aggiungi un controller o un dispositivo NVMe a una VM", + "diskImportLxc": "Importa un disco in un LXC", + "diskFormat": "Formatta o cancella un disco fisico", + "diskSmart": "Stato SMART e test del disco", + "storageShareManager": "Gestione archiviazione e condivisioni", + "storageShareOverview": "Panoramica", + "hostStorage": "Integrazione dell’archiviazione nell’host", + "hostNfs": "Aggiungi una condivisione NFS come storage Proxmox", + "hostSamba": "Aggiungi una condivisione Samba come storage Proxmox", + "hostIscsi": "Aggiungi una destinazione iSCSI come storage Proxmox", + "hostLocalDisk": "Aggiungi un disco locale come storage Proxmox", + "hostLocalShared": "Crea una directory condivisa sull’host", + "lxcMountPoints": "Punti di montaggio LXC (host ↔ CT)", + "lxcNetworkSharing": "Condivisioni di rete in LXC", + "lxcNfsClient": "Client NFS in LXC", + "lxcSambaClient": "Client Samba in LXC", + "lxcNfsServer": "Server NFS in LXC", + "lxcSambaServer": "Server Samba in LXC", + "network": "Rete", + "networkOverview": "Panoramica", + "networkDiagnostics": "Diagnostica", + "networkMonitoring": "Strumenti di monitoraggio in tempo reale", + "networkBridge": "Analisi e riparazione dei bridge di rete", + "networkConfig": "Analisi e pulizia della configurazione", + "networkPersistent": "Nomi persistenti delle interfacce", + "networkBackup": "Backup e riavvio delle interfacce", + "security": "Sicurezza", + "securityOverview": "Panoramica", + "securityFail2ban": "Fail2Ban", + "securityLynis": "Lynis", + "utilities": "Utilità", + "utilsOverview": "Panoramica", + "utilsUupDump": "Creazione di ISO con UUP Dump", + "utilsSystemUtils": "Installazione di utilità di sistema", + "utilsSystemUpdate": "Aggiornamento del sistema Proxmox", + "utilsUpgradePve": "Passaggio da PVE 8 a PVE 9", + "utilsExportVm": "Esporta una VM in OVA/OVF", + "utilsImportVm": "Importa una VM da OVA/OVF", + "settingsProxmenux": "Impostazioni di ProxMenux", + "settingsOverview": "Panoramica", + "settingsMonitor": "ProxMenux Monitor", + "settingsBeta": "Cambia canale di rilascio", + "settingsVersion": "Mostra informazioni sulla versione", + "settingsUninstall": "Disinstalla ProxMenux", + "commandsReference": "Riferimento dei comandi", + "commandsOverview": "Panoramica", + "commandsSystem": "Comandi di sistema utili", + "commandsVmCt": "Gestione di VM e CT", + "commandsStorage": "Archiviazione e dischi", + "commandsNetwork": "Comandi di rete", + "commandsUpdates": "Aggiornamenti e pacchetti", + "commandsGpu": "Passthrough GPU", + "commandsZfs": "Gestione di ZFS", + "commandsBackup": "Backup e ripristino", + "commandsTools": "Strumenti di sistema da riga di comando", + "glossary": "Glossario", + "about": "Informazioni sul progetto", + "aboutOverview": "Panoramica", + "aboutFaq": "FAQ", + "aboutContributors": "Collaboratori", + "aboutContributing": "Come contribuire", + "aboutCodeOfConduct": "Codice di condotta", + "externalRepositories": "Repository esterni", + "backupRestore": "Backup e ripristino", + "backupRestoreOverview": "Panoramica", + "backupRestoreHowItWorks": "Come funziona", + "backupRestoreDestinations": "Destinazioni", + "backupRestoreDestOverview": "Panoramica", + "backupRestoreDestLocal": "Archivio locale", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Creare backup", + "backupRestoreJobs": "Attività pianificate", + "backupRestoreRestoring": "Ripristino", + "backupRestoreCrossKernel": "Ripristino tra versioni del kernel", + "backupRestoreGlossary": "Glossario" + } + }, + "hero": { + "title": "ProxMenux", + "tagline": "Un menu interattivo e una dashboard web per Proxmox VE", + "description": "ProxMenux unisce un menu interattivo e una dashboard web per Proxmox VE. Esegui comandi, script e procedure guidate dal terminale oppure controlla lo stato dell’host, le metriche e le notifiche dal browser.", + "installButton": "Installa ora", + "whatIsButton": "Che cos’è ProxMenux?", + "logoAlt": "Logo di ProxMenux" + }, + "docNav": { + "previous": "Precedente", + "next": "Successivo" + }, + "tocPanel": { + "onThisPage": "In questa pagina" + }, + "supportProject": { + "heading": "Sostieni il progetto!", + "body": "Se ProxMenux ti è utile, lascia una ⭐ su GitHub per aiutare altre persone a scoprirlo.", + "button": "Lascia una stella su GitHub" + }, + "rssLink": { + "heading": "Resta aggiornato!", + "body": "Iscriviti al feed RSS per ricevere le novità.", + "copy": "Copia", + "copied": "Copiato!", + "copyTitle": "Copia l’URL del feed RSS", + "openFeed": "Apri il feed RSS", + "openTitle": "Apri il feed RSS" + }, + "changelog": { + "pageTitle": "Registro delle modifiche", + "meta": { + "title": "Registro delle modifiche di ProxMenux", + "titleSuffix": "Note di rilascio", + "latest": "Ultima versione", + "mostRecent": "Versione più recente", + "description": "Note di rilascio e cronologia degli aggiornamenti di ProxMenux, lo script a menu per gestire Proxmox VE.", + "descriptionTail": "Nuove funzionalità, correzioni di bug, aggiornamenti di sicurezza, miglioramenti di ProxMenux Monitor, compatibilità con i fornitori IA, maggiore sicurezza del passthrough GPU e altro.", + "ogDescription": "Note di rilascio di ProxMenux.", + "ogTail": "Iscriviti al feed RSS o torna su questa pagina per le nuove versioni." + } + } +} diff --git a/web/messages/pt/common.json b/web/messages/pt/common.json new file mode 100644 index 00000000..a7cb872c --- /dev/null +++ b/web/messages/pt/common.json @@ -0,0 +1,254 @@ +{ + "site": { + "name": "ProxMenux", + "tagline": "Menu interativo e painel web para o Proxmox VE" + }, + "nav": { + "home": "Início", + "docs": "Documentação", + "documentation": "Documentação", + "guides": "Guias", + "changelog": "Histórico de alterações", + "github": "GitHub", + "rss": "RSS", + "rssTitle": "Feed RSS", + "menuOpen": "Abrir menu" + }, + "footer": { + "sponsorHeading": "Apoiar o projeto", + "sponsorBody": "Se quiser apoiar o projeto.", + "sponsorAlt": "Apoie-me no Ko-fi", + "connectHeading": "Comunidade", + "connectBody": "Participe nas discussões da comunidade no GitHub para obter ajuda, partilhar ideias e contribuir para o projeto. Todas as ideias são bem-vindas!", + "joinDiscussion": "Participar na discussão", + "copyrightPrefix": "ProxMenux, um projeto colaborativo e de código aberto criado por", + "copyrightSuffix": "." + }, + "language": { + "switcher": "Idioma", + "en": "English", + "es": "Español", + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" + }, + "resources": { + "documentation": { + "title": "Documentação", + "description": "Descrição do sistema e guias de utilização" + }, + "changelog": { + "title": "Histórico de alterações", + "description": "Informações sobre as últimas atualizações" + }, + "guides": { + "title": "Guias", + "description": "Tutoriais e guias passo a passo para tarefas comuns" + }, + "github": { + "title": "Repositório do GitHub", + "description": "Explore o código-fonte." + } + }, + "docSidebar": { + "documentation": "Documentação", + "items": { + "introduction": "Introdução", + "installation": "Instalação", + "proxmenuxMonitor": "ProxMenux Monitor", + "monitorOverview": "Visão geral", + "architecture": "Arquitetura", + "accessAuth": "Acesso e autenticação", + "dashboard": "Painel", + "dashboardSystemOverview": "Separador Visão geral do sistema", + "dashboardApps": "Separador Aplicações", + "dashboardStorage": "Separador Armazenamento", + "dashboardNetwork": "Separador Rede", + "dashboardVmsLxcs": "Separador VM e LXC", + "dashboardVmsLxcsApp": "Aplicação", + "dashboardVmsLxcsUpdates": "Atualizações", + "dashboardHardware": "Separador Hardware", + "dashboardSystemLogs": "Separador Registos do sistema", + "dashboardTerminal": "Separador Terminal", + "dashboardSecurity": "Separador Segurança", + "dashboardSettings": "Separador Definições", + "healthMonitor": "Monitor de estado", + "auditReport": "Auditoria e relatórios", + "auditReportOverview": "Visão geral", + "auditReportAssessment": "Avaliação e inventário", + "auditReportPolicy": "Política", + "auditReportChanges": "Alterações", + "auditReportReports": "Relatórios e comparações", + "auditReportScope": "Âmbito e garantias", + "notifications": "Notificações", + "aiAssistant": "Assistente de IA", + "apiReference": "Referência da API", + "integrations": "Integrações", + "ociManager": "OCI manager Apps", + "ociOverview": "Visão geral", + "ociArchitecture": "Como é adaptada uma imagem OCI", + "ociCustomImage": "Imagem fora do catálogo", + "ociStorageNetwork": "Dados, caminhos e rede", + "ociHardware": "Dispositivos e aceleração", + "ociStacks": "Aplicações com vários contentores", + "ociLifecycle": "Instalar, atualizar e modificar", + "ociMonitor": "No ProxMenux Monitor", + "proxmenuxScripts": "ProxMenux Scripts", + "postInstallScript": "Script de pós-instalação", + "postInstallOverview": "Visão geral", + "postInstallAutomated": "Automático", + "postInstallCustomizable": "Personalizável", + "postInstallBasicSettings": "Definições básicas", + "postInstallSystem": "Sistema", + "postInstallVirtualization": "Virtualização", + "postInstallNetwork": "Rede", + "postInstallStorage": "Armazenamento", + "postInstallSecurity": "Segurança", + "postInstallCustomization": "Personalização", + "postInstallMonitoring": "Monitorização", + "postInstallPerformance": "Desempenho", + "postInstallOptional": "Opcional", + "postInstallUpdates": "Aplicar as atualizações disponíveis", + "postInstallUninstall": "Remover otimizações", + "gpusCoralTpu": "GPU e Coral TPU", + "nvidiaHost": "Instalar controladores NVIDIA no host", + "coralHost": "Instalar Coral TPU no host", + "addGpuLxc": "Disponibilizar uma GPU num LXC", + "addCoralLxc": "Disponibilizar Coral TPU num LXC", + "addGpuVm": "Atribuir uma GPU a uma VM (passthrough)", + "switchGpuMode": "Alterar o modo da GPU (VM ↔ LXC)", + "createVm": "Criar uma VM", + "createVmOverview": "Visão geral", + "createVmSystemNas": "Sistemas NAS", + "createVmSynology": "VM Synology", + "createVmNasOthers": "Outros sistemas NAS", + "createVmSystemWindows": "Sistemas Windows", + "createVmSystemLinux": "Sistemas Linux", + "diskManager": "Gestor de discos", + "diskManagerOverview": "Visão geral", + "diskImportVm": "Importar um disco para uma VM", + "diskImportImageVm": "Importar uma imagem de disco para uma VM", + "diskAddController": "Adicionar um controlador ou dispositivo NVMe a uma VM", + "diskImportLxc": "Importar um disco para um LXC", + "diskFormat": "Formatar ou apagar um disco físico", + "diskSmart": "Estado SMART e testes do disco", + "storageShareManager": "Gestor de armazenamento e partilhas", + "storageShareOverview": "Visão geral", + "hostStorage": "Integração de armazenamento no host", + "hostNfs": "Adicionar uma partilha NFS como armazenamento do Proxmox", + "hostSamba": "Adicionar uma partilha Samba como armazenamento do Proxmox", + "hostIscsi": "Adicionar um destino iSCSI como armazenamento do Proxmox", + "hostLocalDisk": "Adicionar um disco local como armazenamento do Proxmox", + "hostLocalShared": "Criar um diretório partilhado no host", + "lxcMountPoints": "Pontos de montagem LXC (host ↔ CT)", + "lxcNetworkSharing": "Partilhas de rede em LXC", + "lxcNfsClient": "Cliente NFS em LXC", + "lxcSambaClient": "Cliente Samba em LXC", + "lxcNfsServer": "Servidor NFS em LXC", + "lxcSambaServer": "Servidor Samba em LXC", + "network": "Rede", + "networkOverview": "Visão geral", + "networkDiagnostics": "Diagnóstico", + "networkMonitoring": "Ferramentas de monitorização em tempo real", + "networkBridge": "Análise e reparação de bridges de rede", + "networkConfig": "Análise e limpeza da configuração", + "networkPersistent": "Nomes persistentes das interfaces", + "networkBackup": "Cópia de segurança e reinício das interfaces", + "security": "Segurança", + "securityOverview": "Visão geral", + "securityFail2ban": "Fail2Ban", + "securityLynis": "Lynis", + "utilities": "Utilitários", + "utilsOverview": "Visão geral", + "utilsUupDump": "Criação de ISO com UUP Dump", + "utilsSystemUtils": "Instalação de utilitários do sistema", + "utilsSystemUpdate": "Atualização do sistema Proxmox", + "utilsUpgradePve": "Atualizar do PVE 8 para o PVE 9", + "utilsExportVm": "Exportar uma VM para OVA/OVF", + "utilsImportVm": "Importar uma VM a partir de OVA/OVF", + "settingsProxmenux": "Definições do ProxMenux", + "settingsOverview": "Visão geral", + "settingsMonitor": "ProxMenux Monitor", + "settingsBeta": "Alterar o canal de versões", + "settingsVersion": "Mostrar informações da versão", + "settingsUninstall": "Desinstalar o ProxMenux", + "commandsReference": "Referência de comandos", + "commandsOverview": "Visão geral", + "commandsSystem": "Comandos úteis do sistema", + "commandsVmCt": "Gestão de VM e CT", + "commandsStorage": "Armazenamento e discos", + "commandsNetwork": "Comandos de rede", + "commandsUpdates": "Atualizações e pacotes", + "commandsGpu": "Passthrough de GPU", + "commandsZfs": "Gestão do ZFS", + "commandsBackup": "Cópia de segurança e restauro", + "commandsTools": "Ferramentas de sistema para a linha de comandos", + "glossary": "Glossário", + "about": "Sobre o projeto", + "aboutOverview": "Visão geral", + "aboutFaq": "FAQ", + "aboutContributors": "Colaboradores", + "aboutContributing": "Como contribuir", + "aboutCodeOfConduct": "Código de conduta", + "externalRepositories": "Repositórios externos", + "backupRestore": "Cópia de segurança e restauro", + "backupRestoreOverview": "Visão geral", + "backupRestoreHowItWorks": "Como funciona", + "backupRestoreDestinations": "Destinos", + "backupRestoreDestOverview": "Visão geral", + "backupRestoreDestLocal": "Arquivo local", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Criar cópias de segurança", + "backupRestoreJobs": "Tarefas agendadas", + "backupRestoreRestoring": "Restaurar", + "backupRestoreCrossKernel": "Restauro entre versões do kernel", + "backupRestoreGlossary": "Glossário" + } + }, + "hero": { + "title": "ProxMenux", + "tagline": "Um menu interativo e um painel web para o Proxmox VE", + "description": "O ProxMenux reúne um menu interativo e um painel web para o Proxmox VE. Execute comandos, scripts e assistentes a partir do terminal ou consulte o estado do host, as métricas e as notificações no navegador.", + "installButton": "Instalar agora", + "whatIsButton": "O que é o ProxMenux?", + "logoAlt": "Logótipo do ProxMenux" + }, + "docNav": { + "previous": "Anterior", + "next": "Seguinte" + }, + "tocPanel": { + "onThisPage": "Nesta página" + }, + "supportProject": { + "heading": "Apoie o projeto!", + "body": "Se o ProxMenux lhe é útil, dê-lhe uma ⭐ no GitHub para ajudar outras pessoas a descobri-lo.", + "button": "Dar uma estrela no GitHub" + }, + "rssLink": { + "heading": "Acompanhe as novidades!", + "body": "Subscreva o feed RSS para receber as novidades.", + "copy": "Copiar", + "copied": "Copiado!", + "copyTitle": "Copiar o URL do feed RSS", + "openFeed": "Abrir o feed RSS", + "openTitle": "Abrir o feed RSS" + }, + "changelog": { + "pageTitle": "Histórico de alterações", + "meta": { + "title": "Histórico de alterações do ProxMenux", + "titleSuffix": "Notas da versão", + "latest": "Última versão", + "mostRecent": "Versão mais recente", + "description": "Notas de versão e histórico de atualizações do ProxMenux, o script com menus para gerir o Proxmox VE.", + "descriptionTail": "Novas funcionalidades, correções de erros, atualizações de segurança, melhorias do ProxMenux Monitor, compatibilidade com fornecedores de IA, reforço de segurança do passthrough de GPU e muito mais.", + "ogDescription": "Notas de versão do ProxMenux.", + "ogTail": "Subscreva o feed RSS ou volte a esta página para consultar novas versões." + } + } +} diff --git a/web/messages/sk/common.json b/web/messages/sk/common.json index 1c34e6b1..b95d8c96 100644 --- a/web/messages/sk/common.json +++ b/web/messages/sk/common.json @@ -28,7 +28,12 @@ "switcher": "Jazyk", "en": "English", "es": "Español", - "sk": "Slovenčina" + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" }, "resources": { "documentation": { @@ -193,7 +198,15 @@ "backupRestoreJobs": "Plánované úlohy", "backupRestoreRestoring": "Obnova", "backupRestoreCrossKernel": "Obnova medzi kernelmi", - "backupRestoreGlossary": "Slovník pojmov" + "backupRestoreGlossary": "Slovník pojmov", + "dashboardApps": "Karta Aplikácie", + "auditReport": "Audit a správy", + "auditReportOverview": "Prehľad", + "auditReportAssessment": "Hodnotenie a inventár", + "auditReportPolicy": "Pravidlá", + "auditReportChanges": "Zmeny", + "auditReportReports": "Správy a porovnania", + "auditReportScope": "Rozsah a záruky" } }, "hero": { diff --git a/web/messages/sk/docs/monitor/dashboard/vms-lxcs-app.json b/web/messages/sk/docs/monitor/dashboard/vms-lxcs-app.json index 8abc8a2b..551b00fc 100644 --- a/web/messages/sk/docs/monitor/dashboard/vms-lxcs-app.json +++ b/web/messages/sk/docs/monitor/dashboard/vms-lxcs-app.json @@ -98,7 +98,7 @@ ], "sourcesHeading": "Zdroje dostupnej verzie", "sources": [ - "GitHub repozitár: najnovšie vydanie alebo značka verejného repozitára vlastník/názov.", + "GitHub repozitár: najnovšie vydanie alebo značka verejného repozitára owner/name.", "HTTP JSON: verejný koncový bod a cesta ako data.version alebo releases[0].tag_name.", "Docker Hub: verzované značky filtrované regulárnym výrazom. Živý náhľad pred uložením ukáže skutočné zodpovedajúce značky.", "Pohyblivé značky ako latest, stable alebo lts neobsahujú verziu. Tieto obrazy sledujte podľa digestu v aktualizáciách Docker obrazov." diff --git a/web/messages/sk/docs/monitor/dashboard/vms-lxcs-updates.json b/web/messages/sk/docs/monitor/dashboard/vms-lxcs-updates.json index 7f4438f6..52331ccf 100644 --- a/web/messages/sk/docs/monitor/dashboard/vms-lxcs-updates.json +++ b/web/messages/sk/docs/monitor/dashboard/vms-lxcs-updates.json @@ -110,7 +110,7 @@ "Nepridávajte pct exec; ProxMenux už vstupuje do kontajnera a príkaz spúšťa ako root." ], "exampleLead": "Príklad úplného postupu v kontajneri:", - "example": "cd /opt/moja-aplikacia && ./update.sh", + "example": "cd /opt/my-app && ./update.sh", "callout": "Príkaz ako myapp --version iba číta verziu a nič neaktualizuje. Príkazy sa spúšťajú s oprávneniami správcu a musia sa kontrolovať ako príkazy shellu root." }, "bulk": { @@ -140,7 +140,7 @@ "items": [ "Vyberte predvoľbu alebo cron výraz a potom presné ciele: balíky OS, jednotlivé aplikácie, Docker Engine, samostatné jednotky Dockeru alebo skupiny služieb Compose.", "Oneskorenie po vydaní platí iba pre vybrané aplikácie so sledovaním verzie. Aplikácie bez sledovania spustia aktualizátor pri každom termíne.", - "Stav posledného behu rozlišuje úspech, čiastočné dokončenie, zlyhanie, bezpečnostné pozdržanie a stav bez čakajúcich aktualizácií.", + "Stav posledného behu rozlišuje úspech, čiastočné dokončenie, zlyhanie, bezpečnostné pozastavenie a žiadne čakajúce aktualizácie. Po prvom plánovanom behu otvorí Aktualizácie → Plánované aktualizácie → Zobraziť záznam celý výstup aktualizátora a podradených skriptov.", "Zistené externé plány Proxmox VE Helper-Scripts sa zobrazia samostatne, aby bola viditeľná prekrývajúca sa automatizácia." ], "callout": "Pred zapnutím plánu otestujte každú vybranú metódu manuálne. Plánovaný príkaz nemôže odpovedať na interaktívne otázky." diff --git a/web/messages/sv/common.json b/web/messages/sv/common.json new file mode 100644 index 00000000..658933e3 --- /dev/null +++ b/web/messages/sv/common.json @@ -0,0 +1,254 @@ +{ + "site": { + "name": "ProxMenux", + "tagline": "Interaktiv meny och webbaserad instrumentpanel för Proxmox VE" + }, + "nav": { + "home": "Start", + "docs": "Dokumentation", + "documentation": "Dokumentation", + "guides": "Guider", + "changelog": "Ändringslogg", + "github": "GitHub", + "rss": "RSS", + "rssTitle": "RSS-flöde", + "menuOpen": "Öppna meny" + }, + "footer": { + "sponsorHeading": "Stöd projektet", + "sponsorBody": "Om du vill stödja projektet.", + "sponsorAlt": "Stöd mig på Ko-fi", + "connectHeading": "Gemenskap", + "connectBody": "Delta i diskussionerna på GitHub för att få hjälp, dela idéer och bidra till projektet. Alla idéer är välkomna!", + "joinDiscussion": "Delta i diskussionen", + "copyrightPrefix": "ProxMenux, ett samarbetsprojekt med öppen källkod av", + "copyrightSuffix": "." + }, + "language": { + "switcher": "Språk", + "en": "English", + "es": "Español", + "sk": "Slovenčina", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "pt": "Português", + "sv": "Svenska" + }, + "resources": { + "documentation": { + "title": "Dokumentation", + "description": "Systembeskrivning och användarguider" + }, + "changelog": { + "title": "Ändringslogg", + "description": "Information om de senaste uppdateringarna" + }, + "guides": { + "title": "Guider", + "description": "Stegvisa instruktioner och guider för vanliga uppgifter" + }, + "github": { + "title": "GitHub-arkiv", + "description": "Utforska källkoden." + } + }, + "docSidebar": { + "documentation": "Dokumentation", + "items": { + "introduction": "Introduktion", + "installation": "Installation", + "proxmenuxMonitor": "ProxMenux Monitor", + "monitorOverview": "Översikt", + "architecture": "Arkitektur", + "accessAuth": "Åtkomst och autentisering", + "dashboard": "Instrumentpanel", + "dashboardSystemOverview": "Fliken Systemöversikt", + "dashboardApps": "Fliken Program", + "dashboardStorage": "Fliken Lagring", + "dashboardNetwork": "Fliken Nätverk", + "dashboardVmsLxcs": "Fliken VM och LXC", + "dashboardVmsLxcsApp": "Program", + "dashboardVmsLxcsUpdates": "Uppdateringar", + "dashboardHardware": "Fliken Maskinvara", + "dashboardSystemLogs": "Fliken Systemloggar", + "dashboardTerminal": "Fliken Terminal", + "dashboardSecurity": "Fliken Säkerhet", + "dashboardSettings": "Fliken Inställningar", + "healthMonitor": "Systemövervakning", + "auditReport": "Granskning och rapporter", + "auditReportOverview": "Översikt", + "auditReportAssessment": "Bedömning och inventering", + "auditReportPolicy": "Policy", + "auditReportChanges": "Ändringar", + "auditReportReports": "Rapporter och jämförelser", + "auditReportScope": "Omfattning och garantier", + "notifications": "Aviseringar", + "aiAssistant": "AI-assistent", + "apiReference": "API-referens", + "integrations": "Integrationer", + "ociManager": "OCI manager Apps", + "ociOverview": "Översikt", + "ociArchitecture": "Så anpassas en OCI-avbildning", + "ociCustomImage": "Avbildning utanför katalogen", + "ociStorageNetwork": "Data, sökvägar och nätverk", + "ociHardware": "Enheter och acceleration", + "ociStacks": "Program med flera containrar", + "ociLifecycle": "Installera, uppdatera och ändra", + "ociMonitor": "I ProxMenux Monitor", + "proxmenuxScripts": "ProxMenux Scripts", + "postInstallScript": "Skript efter installationen", + "postInstallOverview": "Översikt", + "postInstallAutomated": "Automatiskt", + "postInstallCustomizable": "Anpassningsbart", + "postInstallBasicSettings": "Grundinställningar", + "postInstallSystem": "System", + "postInstallVirtualization": "Virtualisering", + "postInstallNetwork": "Nätverk", + "postInstallStorage": "Lagring", + "postInstallSecurity": "Säkerhet", + "postInstallCustomization": "Anpassning", + "postInstallMonitoring": "Övervakning", + "postInstallPerformance": "Prestanda", + "postInstallOptional": "Valfritt", + "postInstallUpdates": "Installera tillgängliga uppdateringar", + "postInstallUninstall": "Ta bort optimeringar", + "gpusCoralTpu": "GPU och Coral TPU", + "nvidiaHost": "Installera NVIDIA-drivrutiner på värden", + "coralHost": "Installera Coral TPU på värden", + "addGpuLxc": "Gör en GPU tillgänglig i en LXC", + "addCoralLxc": "Gör Coral TPU tillgänglig i en LXC", + "addGpuVm": "Tilldela en GPU till en VM (passthrough)", + "switchGpuMode": "Byt GPU-läge (VM ↔ LXC)", + "createVm": "Skapa en VM", + "createVmOverview": "Översikt", + "createVmSystemNas": "NAS-system", + "createVmSynology": "Synology-VM", + "createVmNasOthers": "Andra NAS-system", + "createVmSystemWindows": "Windows-system", + "createVmSystemLinux": "Linux-system", + "diskManager": "Diskhantering", + "diskManagerOverview": "Översikt", + "diskImportVm": "Importera en disk till en VM", + "diskImportImageVm": "Importera en diskavbildning till en VM", + "diskAddController": "Lägg till en styrenhet eller NVMe-enhet i en VM", + "diskImportLxc": "Importera en disk till en LXC", + "diskFormat": "Formatera eller radera en fysisk disk", + "diskSmart": "SMART-status och disktester", + "storageShareManager": "Hantering av lagring och delningar", + "storageShareOverview": "Översikt", + "hostStorage": "Anslut lagring till värden", + "hostNfs": "Lägg till en NFS-delning som Proxmox-lagring", + "hostSamba": "Lägg till en Samba-delning som Proxmox-lagring", + "hostIscsi": "Lägg till ett iSCSI-mål som Proxmox-lagring", + "hostLocalDisk": "Lägg till en lokal disk som Proxmox-lagring", + "hostLocalShared": "Skapa en delad katalog på värden", + "lxcMountPoints": "LXC-monteringspunkter (värd ↔ CT)", + "lxcNetworkSharing": "Nätverksdelning i LXC", + "lxcNfsClient": "NFS-klient i LXC", + "lxcSambaClient": "Samba-klient i LXC", + "lxcNfsServer": "NFS-server i LXC", + "lxcSambaServer": "Samba-server i LXC", + "network": "Nätverk", + "networkOverview": "Översikt", + "networkDiagnostics": "Diagnostik", + "networkMonitoring": "Verktyg för övervakning i realtid", + "networkBridge": "Analysera och reparera nätverksbryggor", + "networkConfig": "Analysera och rensa konfigurationen", + "networkPersistent": "Beständiga gränssnittsnamn", + "networkBackup": "Säkerhetskopiera och starta om gränssnitt", + "security": "Säkerhet", + "securityOverview": "Översikt", + "securityFail2ban": "Fail2Ban", + "securityLynis": "Lynis", + "utilities": "Verktyg", + "utilsOverview": "Översikt", + "utilsUupDump": "Skapa ISO med UUP Dump", + "utilsSystemUtils": "Installera systemverktyg", + "utilsSystemUpdate": "Uppdatera Proxmox-systemet", + "utilsUpgradePve": "Uppgradera från PVE 8 till PVE 9", + "utilsExportVm": "Exportera en VM till OVA/OVF", + "utilsImportVm": "Importera en VM från OVA/OVF", + "settingsProxmenux": "ProxMenux-inställningar", + "settingsOverview": "Översikt", + "settingsMonitor": "ProxMenux Monitor", + "settingsBeta": "Byt versionskanal", + "settingsVersion": "Visa versionsinformation", + "settingsUninstall": "Avinstallera ProxMenux", + "commandsReference": "Kommandoreferens", + "commandsOverview": "Översikt", + "commandsSystem": "Användbara systemkommandon", + "commandsVmCt": "Hantera VM och CT", + "commandsStorage": "Lagring och diskar", + "commandsNetwork": "Nätverkskommandon", + "commandsUpdates": "Uppdateringar och paket", + "commandsGpu": "GPU-passthrough", + "commandsZfs": "Hantera ZFS", + "commandsBackup": "Säkerhetskopiering och återställning", + "commandsTools": "Systemverktyg för kommandoraden", + "glossary": "Ordlista", + "about": "Om projektet", + "aboutOverview": "Översikt", + "aboutFaq": "FAQ", + "aboutContributors": "Medverkande", + "aboutContributing": "Bidra", + "aboutCodeOfConduct": "Uppförandekod", + "externalRepositories": "Externa arkiv", + "backupRestore": "Säkerhetskopiering och återställning", + "backupRestoreOverview": "Översikt", + "backupRestoreHowItWorks": "Så fungerar det", + "backupRestoreDestinations": "Mål", + "backupRestoreDestOverview": "Översikt", + "backupRestoreDestLocal": "Lokalt arkiv", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Skapa säkerhetskopior", + "backupRestoreJobs": "Schemalagda jobb", + "backupRestoreRestoring": "Återställning", + "backupRestoreCrossKernel": "Återställning mellan kärnversioner", + "backupRestoreGlossary": "Ordlista" + } + }, + "hero": { + "title": "ProxMenux", + "tagline": "En interaktiv meny och webbaserad instrumentpanel för Proxmox VE", + "description": "ProxMenux kombinerar en interaktiv meny med en webbaserad instrumentpanel för Proxmox VE. Kör kommandon, skript och guidade steg från terminalmenyn, eller följ värdens status, mätvärden och aviseringar i webbläsaren.", + "installButton": "Installera nu", + "whatIsButton": "Vad är ProxMenux?", + "logoAlt": "ProxMenux-logotyp" + }, + "docNav": { + "previous": "Föregående", + "next": "Nästa" + }, + "tocPanel": { + "onThisPage": "På den här sidan" + }, + "supportProject": { + "heading": "Stöd projektet!", + "body": "Om du har nytta av ProxMenux kan du ge projektet en ⭐ på GitHub så att fler upptäcker det.", + "button": "Ge en stjärna på GitHub" + }, + "rssLink": { + "heading": "Följ uppdateringarna!", + "body": "Prenumerera på vårt RSS-flöde för att få information om nyheter.", + "copy": "Kopiera", + "copied": "Kopierat!", + "copyTitle": "Kopiera RSS-adressen", + "openFeed": "Öppna RSS-flödet", + "openTitle": "Öppna RSS-flödet" + }, + "changelog": { + "pageTitle": "Ändringslogg", + "meta": { + "title": "Ändringslogg för ProxMenux", + "titleSuffix": "Versionsinformation", + "latest": "Senaste", + "mostRecent": "Senaste versionen", + "description": "Versionsinformation och uppdateringshistorik för ProxMenux, det menystyrda skriptet för administration av Proxmox VE.", + "descriptionTail": "Nya funktioner, felrättningar, säkerhetsuppdateringar, förbättringar i ProxMenux Monitor, stöd för AI-leverantörer, säkrare GPU-passthrough och mer.", + "ogDescription": "Versionsinformation för ProxMenux.", + "ogTail": "Prenumerera via RSS eller besök sidan igen för att se nya versioner." + } + } +} diff --git a/web/package.json b/web/package.json index ca3302cd..3996563d 100644 --- a/web/package.json +++ b/web/package.json @@ -4,10 +4,10 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build && pagefind --site out --output-path public/pagefind && cp -r public/pagefind out/pagefind", + "build": "next build && node scripts/localize-export.mjs && pagefind --site out --output-path public/pagefind && cp -r public/pagefind out/pagefind", "start": "next start", "lint": "next lint", - "index": "pagefind --site out --output-path public/pagefind && cp -r public/pagefind out/pagefind" + "index": "node scripts/localize-export.mjs && pagefind --site out --output-path public/pagefind && cp -r public/pagefind out/pagefind" }, "dependencies": { "@hookform/resolvers": "^3.9.1", diff --git a/web/scripts/localize-export.mjs b/web/scripts/localize-export.mjs new file mode 100644 index 00000000..89e7f221 --- /dev/null +++ b/web/scripts/localize-export.mjs @@ -0,0 +1,32 @@ +// Next's shared root layout cannot read its nested [locale] parameter. +// Set the static document language before Pagefind indexes the export, so +// search and no-JavaScript readers see the same locale as the hydrated app. +import { readdir, readFile, writeFile } from 'node:fs/promises' +import { join } from 'node:path' + +async function htmlFiles(directory) { + const entries = await readdir(directory, { withFileTypes: true }) + const files = await Promise.all(entries.map(entry => { + const path = join(directory, entry.name) + return entry.isDirectory() ? htmlFiles(path) : entry.isFile() && entry.name.endsWith('.html') ? [path] : [] + })) + return files.flat() +} + +let count = 0 +for (const entry of await readdir('out', { withFileTypes: true })) { + if (!entry.isDirectory() || !/^[a-z]{2}(?:-[a-z0-9]+)?$/.test(entry.name)) continue + const locale = entry.name + for (const file of await htmlFiles(join('out', locale))) { + const original = await readFile(file, 'utf8') + const updated = original.replace(/]*)>/i, (tag, attributes) => { + const next = /\blang\s*=\s*["'][^"']*["']/i.test(attributes) + ? attributes.replace(/\blang\s*=\s*["'][^"']*["']/i, `lang="${locale}"`) + : `${attributes} lang="${locale}"` + return `` + }) + if (updated !== original) await writeFile(file, updated, 'utf8') + count += 1 + } +} +console.log(`Static document language verified for ${count} localized pages.`) From efaeed370625d001f1820aa2524699a0aa806ea3 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:13:38 +0200 Subject: [PATCH 48/53] Keep Spanish networking docs aligned with the screen and the glossary - Quoted messages match the Spanish interface, and untranslated ones stay in English - backup and slot stay in English; iptraf-ng modes keep the program's own names --- web/messages/es/docs/network/backup-restore.json | 2 +- web/messages/es/docs/network/bridge-analysis.json | 6 +++--- web/messages/es/docs/network/config-analysis.json | 4 ++-- web/messages/es/docs/network/index.json | 8 ++++---- web/messages/es/docs/network/monitoring.json | 10 +++++----- web/messages/es/docs/network/persistent-names.json | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/web/messages/es/docs/network/backup-restore.json b/web/messages/es/docs/network/backup-restore.json index 75ead38d..c8759c70 100644 --- a/web/messages/es/docs/network/backup-restore.json +++ b/web/messages/es/docs/network/backup-restore.json @@ -27,7 +27,7 @@ "heading": "Crear backup de red", "body": "Copia el /etc/network/interfaces actual al directorio de backups con un timestamp nuevo. Ya está — sin análisis, sin prompts, solo un snapshot.", "whenTitle": "Cuándo hacer un backup manual", - "whenBody": "Antes de editar /etc/network/interfaces a mano, antes de instalar un paquete que pueda modificar la pila de red (p. ej. NetworkManager, Open vSwitch) o antes de cualquier cambio de hardware. Las reparaciones guiadas ya crean una copia automáticamente; esta opción es para las operaciones manuales." + "whenBody": "Antes de editar /etc/network/interfaces a mano, antes de instalar un paquete que pueda modificar la pila de red (p. ej. NetworkManager, Open vSwitch) o antes de cualquier cambio de hardware. Las reparaciones guiadas ya hacen un backup automáticamente; esta opción es para las operaciones manuales." }, "restore": { "heading": "Restaurar backup de red", diff --git a/web/messages/es/docs/network/bridge-analysis.json b/web/messages/es/docs/network/bridge-analysis.json index af1f211f..08511c30 100644 --- a/web/messages/es/docs/network/bridge-analysis.json +++ b/web/messages/es/docs/network/bridge-analysis.json @@ -35,7 +35,7 @@ "bridgeLabel": "Analizar (solo lectura)", "bridgeDetail": "ip link show enp3s0\n→ no existe", "targetLabel": "Informe + sugerencia", - "targetDetail": "❌ enp3s0: NO ENCONTRADA\nReemplazar por: eno1\n(aún sin cambios)" + "targetDetail": "❌ enp3s0: NO ENCONTRADO\nReemplazar por: eno1\n(aún sin cambios)" } }, "diagram2": { @@ -76,7 +76,7 @@ }, { "title": "3. Previsualizar los cambios propuestos", - "body": "Lista exactamente qué bridges se modificarán y qué sustituciones de puertos se harán. Si el análisis determina que no hay nada que reparar porque el puerto ha reaparecido entre las comprobaciones, el proceso termina con «No es necesario realizar cambios».", + "body": "Lista exactamente qué bridges se modificarán y qué sustituciones de puertos se harán. Si el análisis determina que no hay nada que reparar porque el puerto ha reaparecido entre las comprobaciones, el proceso termina con «No se necesitan cambios».", "tone": "blue" }, { @@ -99,7 +99,7 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "unsupportedTitle": "El análisis se detiene con «Pila de red no compatible»", + "unsupportedTitle": "El análisis se detiene con «Unsupported Network Stack»", "unsupportedBody": "El host usa netplan, systemd-networkd o NetworkManager. La herramienta solo admite /etc/network/interfaces. Cambia primero el host a la pila clásica o edita la configuración con las herramientas nativas del gestor (p. ej. netplan apply).", "noSuggestTitle": "No se imprime sugerencia para un puerto inválido", "noSuggestBody": "El host no tiene ninguna interfaz física libre (todas están ya en otro bridge o no existen). El análisis cae a sugerir bridge-ports none para que el bridge al menos pueda levantar sin puerto. Añade una NIC o migra puertos entre bridges manualmente antes de volver a ejecutar.", diff --git a/web/messages/es/docs/network/config-analysis.json b/web/messages/es/docs/network/config-analysis.json index 637b08f6..e04f23a2 100644 --- a/web/messages/es/docs/network/config-analysis.json +++ b/web/messages/es/docs/network/config-analysis.json @@ -60,7 +60,7 @@ }, { "title": "2. Confirmar la lista a eliminar", - "body": "Lista exactamente qué bloques de interfaz física se eliminarán. Si las NIC han reaparecido entre el análisis y la limpieza, por ejemplo al volver a insertar una tarjeta, el proceso termina con «No es necesaria ninguna limpieza».", + "body": "Lista exactamente qué bloques de interfaz física se eliminarán. Si las NIC han reaparecido entre el análisis y la limpieza, por ejemplo al volver a insertar una tarjeta, el proceso termina con «No se necesita limpieza».", "tone": "blue" }, { @@ -91,7 +91,7 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "notFoundTitle": "El análisis muestra mi NIC nueva como «NO ENCONTRADA»", + "notFoundTitle": "El análisis muestra mi NIC nueva como «NO ENCONTRADO»", "notFoundBody": "El kernel ve el dispositivo con un nombre distinto al que hay en el fichero. Ejecuta ip link show para obtener el nombre real. Este es exactamente el caso en el que Nombres persistentes de interfaz evitaría el problema de aquí en adelante.", "tooMuchTitle": "La limpieza eliminó demasiado (un bloque que quería conservar)", "tooMuchBody": "Restaura desde el backup impreso en el paso 5:", diff --git a/web/messages/es/docs/network/index.json b/web/messages/es/docs/network/index.json index 66c61ea1..07c20084 100644 --- a/web/messages/es/docs/network/index.json +++ b/web/messages/es/docs/network/index.json @@ -1,15 +1,15 @@ { "meta": { "title": "Gestión de red en Proxmox — bridges, bonds, diagnóstico, reparación | ProxMenux", - "description": "Diagnóstico de solo lectura, análisis con propuestas y reparaciones guiadas para la pila de red Debian / Proxmox (/etc/network/interfaces). Inspecciona bridges y bonds, ejecuta herramientas de monitorización en vivo, fija nombres de interfaz, crea copias de seguridad y reinicia la red de forma segura, con copias obligatorias y vistas previas paso a paso.", + "description": "Diagnóstico de solo lectura, análisis con propuestas y reparaciones guiadas para la pila de red Debian / Proxmox (/etc/network/interfaces). Inspecciona bridges y bonds, ejecuta herramientas de monitorización en vivo, fija nombres de interfaz, crea backups y reinicia la red de forma segura, con backups obligatorios y vistas previas paso a paso.", "ogTitle": "Gestión de red en Proxmox — bridges, bonds, diagnóstico, reparación", - "ogDescription": "Diagnóstico, análisis y reparaciones guiadas para la pila de red de Proxmox con copias de seguridad obligatorias y vistas previas paso a paso.", + "ogDescription": "Diagnóstico, análisis y reparaciones guiadas para la pila de red de Proxmox con backups obligatorios y vistas previas paso a paso.", "twitterTitle": "Gestión de red en Proxmox | ProxMenux", - "twitterDescription": "Bridges, bonds, diagnóstico y reparaciones guiadas para la pila de red de Proxmox con copias de seguridad obligatorias." + "twitterDescription": "Bridges, bonds, diagnóstico y reparaciones guiadas para la pila de red de Proxmox con backups obligatorios." }, "header": { "title": "Gestión de red", - "description": "Diagnóstico de solo lectura, informes de análisis con propuestas y reparaciones guiadas para la pila de red clásica de Debian/Proxmox. Cada operación destructiva crea primero una copia de seguridad y muestra los cambios exactos antes de aplicarlos.", + "description": "Diagnóstico de solo lectura, informes de análisis con propuestas y reparaciones guiadas para la pila de red clásica de Debian/Proxmox. Cada operación destructiva hace primero un backup y muestra los cambios exactos antes de aplicarlos.", "section": "Red" }, "intro": { diff --git a/web/messages/es/docs/network/monitoring.json b/web/messages/es/docs/network/monitoring.json index d2d6c671..5a04cbfb 100644 --- a/web/messages/es/docs/network/monitoring.json +++ b/web/messages/es/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Úsalo para", "rows": [ { - "mode": "Monitor de tráfico IP", + "mode": "IP traffic monitor", "useFor": "Lista en vivo de flujos TCP / UDP / ICMP / otro-IP con contadores de bytes y estado de conexión" }, { - "mode": "Estadísticas generales de interfaces", + "mode": "General interface stats", "useFor": "Conteos agregados de paquetes IPv4 / IPv6 / TCP / UDP / ICMP / no-IP por NIC" }, { - "mode": "Estadísticas de interfaz detalladas", + "mode": "Detailed interface stats", "useFor": "Igual que arriba pero para una interfaz, con detalle de tamaño y tasa de paquetes" }, { - "mode": "Desglose estadístico", + "mode": "Statistical breakdowns", "useFor": "Histograma de distribución de tamaño de paquete, por puerto TCP / UDP" }, { - "mode": "Monitor de estaciones LAN", + "mode": "LAN station monitor", "useFor": "Estadísticas de tráfico por MAC del dominio de broadcast local" } ], diff --git a/web/messages/es/docs/network/persistent-names.json b/web/messages/es/docs/network/persistent-names.json index c6c4de2a..5859a0ff 100644 --- a/web/messages/es/docs/network/persistent-names.json +++ b/web/messages/es/docs/network/persistent-names.json @@ -16,7 +16,7 @@ }, "problem": { "heading": "El problema que resuelve", - "intro": "Linux obtiene los nombres de interfaz predeterminados a partir de la topología PCI: eno1 = integrada en placa, enp3s0 = la tarjeta en el bus PCI 3, ranura 0, etc. El esquema de nombres es determinista con la misma disposición de hardware. Si cambia la disposición, cambian los nombres:", + "intro": "Linux obtiene los nombres de interfaz predeterminados a partir de la topología PCI: eno1 = integrada en placa, enp3s0 = la tarjeta en el bus PCI 3, slot 0, etc. El esquema de nombres es determinista con la misma disposición de hardware. Si cambia la disposición, cambian los nombres:", "items": [ "Añade una GPU delante de una NIC existente → el número de bus de la NIC puede cambiar → el nombre cambia.", "Mueve una tarjeta a otro slot PCIe → el nombre cambia.", @@ -87,7 +87,7 @@ "intro": "Si /etc/systemd/network/ ya contiene ficheros .link (de una ejecución anterior u otra herramienta), se copian a un directorio de backup con timestamp antes de generar los nuevos:", "outro": "Para hacer rollback: copia los ficheros desde el directorio de backup y reinicia.", "rebootTitle": "Tiene efecto en el siguiente reinicio, no inmediatamente", - "rebootBody": "Los cambios en ficheros .link solo se aplican en el arranque, cuando udev vuelve a enumerar los dispositivos. Por eso la herramienta indica «Los cambios se aplicarán tras reiniciar». No intenta renombrar una interfaz en vivo por el riesgo que supone: un vmbr0 activo con interfaces miembro tendría que reconfigurarse de forma atómica, así que la operación se aplaza al siguiente arranque limpio." + "rebootBody": "Los cambios en ficheros .link solo se aplican en el arranque, cuando udev vuelve a enumerar los dispositivos. Por eso la herramienta indica «Los cambios se aplicarán después del reinicio». No intenta renombrar una interfaz en vivo por el riesgo que supone: un vmbr0 activo con interfaces miembro tendría que reconfigurarse de forma atómica, así que la operación se aplaza al siguiente arranque limpio." }, "afterReboot": { "heading": "Tras el reinicio", @@ -100,7 +100,7 @@ }, "troubleshoot": { "heading": "Solución de problemas", - "emptyTitle": "«No se han encontrado interfaces físicas» tras ejecutar la herramienta", + "emptyTitle": "«No se encontraron interfaces físicas» tras ejecutar la herramienta", "emptyBody": "El host no tiene interfaces respaldadas por PCI / phy80211 visibles para el kernel. Confirma con ls -l /sys/class/net/ — cada entrada debería tener un symlink device (PCI) o una entrada phy80211 (Wi-Fi). Si faltan ambos para lo que debería ser una NIC real, el driver no está cargado.", "noChangeTitle": "Tras el reinicio, los nombres no han cambiado como esperaba", "noChangeBody": "Comprueba que el fichero está presente y bien formado: cat /etc/systemd/network/10-<iface>.link. Después mira los logs de udev: journalctl -u systemd-udevd -b | grep -i link. Una causa habitual es un parámetro de kernel net.ifnames=0 residual que deshabilita el naming predictivo por completo — quítalo de /etc/default/grub, ejecuta update-grub y reinicia.", From f6d3ad4337a85e198334d66bff71acb88becced2 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:15:58 +0200 Subject: [PATCH 49/53] Keep security docs aligned with the screen - Lynis output stays as the program prints it, in every language - The Spanish HTTPS steps use the Monitor's button names --- web/messages/de/docs/security/lynis.json | 2 +- web/messages/es/docs/security/lynis.json | 2 +- web/messages/es/docs/security/ssl-letsencrypt.json | 2 +- web/messages/fr/docs/security/lynis.json | 2 +- web/messages/it/docs/security/lynis.json | 2 +- web/messages/pt/docs/security/lynis.json | 2 +- web/messages/sk/docs/security/lynis.json | 2 +- web/messages/sv/docs/security/lynis.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/messages/de/docs/security/lynis.json b/web/messages/de/docs/security/lynis.json index 42c5b8bd..36545875 100644 --- a/web/messages/de/docs/security/lynis.json +++ b/web/messages/de/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "Wählen Sie im Verwaltungsmenü Sicherheitsprüfung jetzt ausführen. Das entspricht folgendem Aufruf :", "code": "lynis audit system --no-colors", "outro": "Die Prüfung dauert je nach Hostgröße 30 Sekunden bis einige Minuten. Die Ausgabe erscheint direkt im Terminal, ohne Warteanimation. Lynis gliedert die Prüfungen nach Kategorien und kennzeichnet Tests mit [ OK ], [ WARNING ] oder [ SUGGESTION ]. Die abschließende Zusammenfassung enthält die wichtigsten Zahlen :", - "summary": "================================================================================\n\n Details der Lynis-Sicherheitsprüfung:\n\n Härtungsindex : 76 [############ ]\n Durchgeführte Tests : 247\n Aktivierte Plugins : 0\n\n Komponenten:\n - Firewall [V]\n - Malware-Scanner [X]\n\n Lynis-Module:\n - Konformitätsstatus [?]\n - Sicherheitsprüfung [V]\n - Schwachstellenprüfung [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Lesen Sie den Bericht", diff --git a/web/messages/es/docs/security/lynis.json b/web/messages/es/docs/security/lynis.json index 452dad31..d5387aa7 100644 --- a/web/messages/es/docs/security/lynis.json +++ b/web/messages/es/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "En el menú de gestión, elige Ejecutar auditoría de seguridad ahora. Equivale a ejecutar:", "code": "lynis audit system --no-colors", "outro": "La auditoría tarda de 30 segundos a varios minutos según el tamaño del host. La salida se muestra directamente en el terminal, sin indicador giratorio. Lynis organiza los controles por categoría y marca cada prueba como [ OK ], [ WARNING ] o [ SUGGESTION ]. El resumen final muestra las cifras principales:", - "summary": "================================================================================\n\n Detalles del análisis de seguridad de Lynis:\n\n Índice de refuerzo : 76 [############ ]\n Pruebas realizadas : 247\n Plugins habilitados : 0\n\n Componentes:\n - Cortafuegos [V]\n - Escáner de malware [X]\n\n Módulos de Lynis:\n - Estado de cumplimiento [?]\n - Auditoría de seguridad [V]\n - Análisis de vulnerabilidades [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Leer el informe", diff --git a/web/messages/es/docs/security/ssl-letsencrypt.json b/web/messages/es/docs/security/ssl-letsencrypt.json index 0e97809e..f7710e7a 100644 --- a/web/messages/es/docs/security/ssl-letsencrypt.json +++ b/web/messages/es/docs/security/ssl-letsencrypt.json @@ -160,7 +160,7 @@ }, "disable": { "heading": "Deshabilitar HTTPS", - "bodyRich": "Pulsa Desactivar HTTPS en el mismo panel. Monitor reinicia con HTTP sin cifrar, útil si un certificado personalizado no se puede leer y el servicio no arranca. No hay otra desinstalación y los archivos de certificado no se modifican.", + "bodyRich": "Pulsa Deshabilitar HTTPS en el mismo panel. Monitor reinicia con HTTP sin cifrar, útil si un certificado personalizado no se puede leer y el servicio no arranca. No hay otra desinstalación y los archivos de certificado no se modifican.", "stateCallout": { "title": "Dónde se guarda el estado", "bodyRich": "/etc/proxmenux/ssl_config.json almacena las rutas de certificado activas y la fuente (none, proxmox, custom). Borrar el archivo equivale a deshabilitar HTTPS — en el siguiente arranque el Monitor recae en HTTP." diff --git a/web/messages/fr/docs/security/lynis.json b/web/messages/fr/docs/security/lynis.json index 0444e96d..5b9a5b43 100644 --- a/web/messages/fr/docs/security/lynis.json +++ b/web/messages/fr/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "Dans le menu de gestion, choisissez Lancer l'audit de sécurité maintenant. Cela revient à exécuter :", "code": "lynis audit system --no-colors", "outro": "L'audit prend de 30 secondes à quelques minutes selon la taille de l'hôte. La sortie apparaît directement dans le terminal, sans animation d'attente. Lynis classe les contrôles par catégorie et marque chaque test [ OK ], [ WARNING ] ou [ SUGGESTION ]. Le résumé final affiche les chiffres principaux :", - "summary": "================================================================================\n\n Détails de l’audit de sécurité Lynis:\n\n Indice de durcissement : 76 [############ ]\n Tests effectués : 247\n Plugins activés : 0\n\n Composants:\n - Pare-feu [V]\n - Analyseur de logiciels malveillants [X]\n\n Modules Lynis:\n - État de conformité [?]\n - Audit de sécurité [V]\n - Analyse de vulnérabilités [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Lire le rapport", diff --git a/web/messages/it/docs/security/lynis.json b/web/messages/it/docs/security/lynis.json index bf23305b..35cc85c1 100644 --- a/web/messages/it/docs/security/lynis.json +++ b/web/messages/it/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "Dal menu di gestione scegli Esegui audit di sicurezza ora. Equivale a eseguire:", "code": "lynis audit system --no-colors", "outro": "L'audit richiede da 30 secondi ad alcuni minuti secondo le dimensioni dell'host. Lo output viene mostrato direttamente nel terminale, senza animazione di attesa. Lynis organizza i controlli per categoria e marca ogni test come [ OK ], [ WARNING ] o [ SUGGESTION ]. Il riepilogo finale mostra i dati principali:", - "summary": "================================================================================\n\n Dettagli della scansione di sicurezza Lynis:\n\n Indice di rafforzamento : 76 [############ ]\n Test eseguiti : 247\n Plugin abilitati : 0\n\n Componenti:\n - Firewall [V]\n - Scanner malware [X]\n\n Moduli Lynis:\n - Stato di conformità [?]\n - Audit di sicurezza [V]\n - Scansione vulnerabilità [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Leggere il rapporto", diff --git a/web/messages/pt/docs/security/lynis.json b/web/messages/pt/docs/security/lynis.json index 6b019ae9..4054e6b4 100644 --- a/web/messages/pt/docs/security/lynis.json +++ b/web/messages/pt/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "No menu de gestão escolha Executar auditoria de segurança agora. Equivale a executar:", "code": "lynis audit system --no-colors", "outro": "A auditoria demora de 30 segundos a alguns minutos conforme a dimensão do anfitrião. A saída aparece diretamente no terminal, sem animação de espera. Lynis organiza os controlos por categoria e marca cada teste como [ OK ], [ WARNING ] ou [ SUGGESTION ]. O resumo final apresenta os números principais:", - "summary": "================================================================================\n\n Detalhes da análise de segurança Lynis:\n\n Índice de reforço : 76 [############ ]\n Testes realizados : 247\n Plugins ativados : 0\n\n Componentes:\n - Firewall [V]\n - Analisador de malware [X]\n\n Módulos Lynis:\n - Estado de conformidade [?]\n - Auditoria de segurança [V]\n - Análise de vulnerabilidades [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Ler o relatório", diff --git a/web/messages/sk/docs/security/lynis.json b/web/messages/sk/docs/security/lynis.json index c010cf27..41bd07bc 100644 --- a/web/messages/sk/docs/security/lynis.json +++ b/web/messages/sk/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "V menu správy vyberte Spustiť bezpečnostný audit teraz. Zodpovedá to spusteniu:", "code": "lynis audit system --no-colors", "outro": "Audit trvá od 30 sekúnd do niekoľkých minút podľa veľkosti hostiteľa. Výstup ide priamo do terminálu bez animácie čakania. Lynis člení kontroly podľa kategórií a každý test označí ako [ OK ], [ WARNING ] alebo [ SUGGESTION ]. Záverečné zhrnutie ukazuje hlavné údaje:", - "summary": "================================================================================\n\n Podrobnosti bezpečnostného auditu Lynis:\n\n Index zabezpečenia : 76 [############ ]\n Vykonané testy : 247\n Povolené doplnky : 0\n\n Komponenty:\n - Firewall [V]\n - Skener škodlivého softvéru [X]\n\n Moduly Lynis:\n - Stav súladu [?]\n - Bezpečnostný audit [V]\n - Kontrola zraniteľností [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Čítanie správy", diff --git a/web/messages/sv/docs/security/lynis.json b/web/messages/sv/docs/security/lynis.json index 2eb520b4..55e58af0 100644 --- a/web/messages/sv/docs/security/lynis.json +++ b/web/messages/sv/docs/security/lynis.json @@ -66,7 +66,7 @@ "intro": "Välj Kör säkerhetsgranskning nu i hanteringsmenyn. Det motsvarar att köra:", "code": "lynis audit system --no-colors", "outro": "Granskningen tar 30 sekunder till några minuter beroende på värdens storlek. Utdata visas direkt i terminalen utan väntanimation. Lynis grupperar kontroller efter kategori och märker varje test med [ OK ], [ WARNING ] eller [ SUGGESTION ]. Slutets sammanfattning visar de viktigaste siffrorna:", - "summary": "================================================================================\n\n Detaljer för Lynis säkerhetsgranskning:\n\n Härdningsindex : 76 [############ ]\n Utförda tester : 247\n Aktiverade insticksmoduler : 0\n\n Komponenter:\n - Brandvägg [V]\n - Skanner för skadlig kod [X]\n\n Lynis-moduler:\n - Efterlevnadsstatus [?]\n - Säkerhetsgranskning [V]\n - Sårbarhetsgranskning [V]\n\n================================================================================" + "summary": "================================================================================\n\n Lynis security scan details:\n\n Hardening index : 76 [############ ]\n Tests performed : 247\n Plugins enabled : 0\n\n Components:\n - Firewall [V]\n - Malware scanner [X]\n\n Lynis modules:\n - Compliance status [?]\n - Security audit [V]\n - Vulnerability scan [V]\n\n================================================================================" }, "report": { "heading": "Läsa rapporten", From 3e9ac6750bce3875c79cf6f7f318ffebc4df8700 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:16:10 +0200 Subject: [PATCH 50/53] Keep iptraf-ng mode names as the program shows them in every language --- web/messages/de/docs/network/monitoring.json | 10 +++++----- web/messages/fr/docs/network/monitoring.json | 10 +++++----- web/messages/it/docs/network/monitoring.json | 10 +++++----- web/messages/pt/docs/network/monitoring.json | 10 +++++----- web/messages/sk/docs/network/monitoring.json | 10 +++++----- web/messages/sv/docs/network/monitoring.json | 10 +++++----- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/web/messages/de/docs/network/monitoring.json b/web/messages/de/docs/network/monitoring.json index c0be503f..908aee12 100644 --- a/web/messages/de/docs/network/monitoring.json +++ b/web/messages/de/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Verwenden Sie es für", "rows": [ { - "mode": "IP-Verkehrsmonitor", + "mode": "IP traffic monitor", "useFor": "Live TCP / UDP/ICMP / Other-IP-Flow-Liste mit Byte-Zählern und Verbindungszustand" }, { - "mode": "Allgemeine Schnittstellenstatistiken", + "mode": "General interface stats", "useFor": "Aggregierte IPv4/IPv6/TCP/UDP/ICMP/Nicht-IP-Paketzahl pro NIC" }, { - "mode": "Detaillierte Schnittstellenstatistiken", + "mode": "Detailed interface stats", "useFor": "Gleiche wie oben, aber für eine Schnittstelle, mit Paketgröße und -rate Details" }, { - "mode": "Statistische Aufgliederungen", + "mode": "Statistical breakdowns", "useFor": "Histogramm der Paketgrößenverteilung nach TCP / UDP Port" }, { - "mode": "LAN-Stationenmonitor", + "mode": "LAN station monitor", "useFor": "Per-MAC-Verkehrsstatistiken für die lokale Broadcast-Domain" } ], diff --git a/web/messages/fr/docs/network/monitoring.json b/web/messages/fr/docs/network/monitoring.json index d81420cb..33f652a2 100644 --- a/web/messages/fr/docs/network/monitoring.json +++ b/web/messages/fr/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Utilisez-le pour", "rows": [ { - "mode": "Surveillance du trafic IP", + "mode": "IP traffic monitor", "useFor": "Liste de flux en direct TCP / UDP / ICMP / autre-IP avec compteurs d'octets et état de connexion" }, { - "mode": "Statistiques générales de l'interface", + "mode": "General interface stats", "useFor": "Nombre total de paquets IPv4 / IPv6 / TCP / UDP / ICMP / non IP par carte réseau" }, { - "mode": "Statistiques détaillées de l'interface", + "mode": "Detailed interface stats", "useFor": "Comme ci-dessus, mais pour une interface, avec la taille du paquet et le détail du taux" }, { - "mode": "Ventilations statistiques", + "mode": "Statistical breakdowns", "useFor": "Histogramme de distribution de la taille du paquet, par port TCP / UDP" }, { - "mode": "Moniteur de station LAN", + "mode": "LAN station monitor", "useFor": "Statistiques du trafic par adresse MAC pour le domaine de diffusion local" } ], diff --git a/web/messages/it/docs/network/monitoring.json b/web/messages/it/docs/network/monitoring.json index 3474dec7..1998637f 100644 --- a/web/messages/it/docs/network/monitoring.json +++ b/web/messages/it/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Usalo per", "rows": [ { - "mode": "Monitor di traffico IP", + "mode": "IP traffic monitor", "useFor": "Elenco in tempo reale dei flussi TCP / UDP / ICMP / altri protocolli IP con contatori di byte e stato della connessione" }, { - "mode": "Statistiche generali delle interfacce", + "mode": "General interface stats", "useFor": "Conteggi aggregati dei pacchetti IPv4 / IPv6 / TCP / UDP / ICMP / non IP per scheda di rete" }, { - "mode": "Statistiche dettagliate dell’interfaccia", + "mode": "Detailed interface stats", "useFor": "Come sopra, ma per una sola interfaccia, con dettagli sulle dimensioni e sulla frequenza dei pacchetti" }, { - "mode": "Ripartizioni statistiche", + "mode": "Statistical breakdowns", "useFor": "Istogramma della distribuzione delle dimensioni dei pacchetti, per porta TCP / UDP" }, { - "mode": "Monitor della stazione LAN", + "mode": "LAN station monitor", "useFor": "Per-MAC statistiche del traffico per il dominio broadcast locale" } ], diff --git a/web/messages/pt/docs/network/monitoring.json b/web/messages/pt/docs/network/monitoring.json index c1e8307b..4f279fae 100644 --- a/web/messages/pt/docs/network/monitoring.json +++ b/web/messages/pt/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Use-o para", "rows": [ { - "mode": "Monitor de tráfego IP", + "mode": "IP traffic monitor", "useFor": "Lista em tempo real de fluxos TCP / UDP / ICMP / outros protocolos IP, com contadores de bytes e estado da ligação" }, { - "mode": "Estatísticas gerais da interface", + "mode": "General interface stats", "useFor": "Contagens agregadas de pacotes IPv4 / IPv6 / TCP / UDP / ICMP / não IP por placa de rede" }, { - "mode": "Estatísticas de interface detalhadas", + "mode": "Detailed interface stats", "useFor": "O mesmo que acima, mas para uma interface, com tamanho de pacote e detalhe de taxa" }, { - "mode": "Desagregações estatísticas", + "mode": "Statistical breakdowns", "useFor": "Histograma de distribuição do tamanho do pacote, por porta TCP / UDP" }, { - "mode": "Monitor de estação LAN", + "mode": "LAN station monitor", "useFor": "Estatísticas de tráfego por MAC para o domínio de transmissão local" } ], diff --git a/web/messages/sk/docs/network/monitoring.json b/web/messages/sk/docs/network/monitoring.json index 6038905a..e46d34d3 100644 --- a/web/messages/sk/docs/network/monitoring.json +++ b/web/messages/sk/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Použiť pre", "rows": [ { - "mode": "Monitor prevádzky IP", + "mode": "IP traffic monitor", "useFor": "Zoznam tokov TCP / UDP / ICMP / iných protokolov IP v reálnom čase s počítadlami bajtov a stavom pripojenia" }, { - "mode": "Všeobecné štatistiky rozhraní", + "mode": "General interface stats", "useFor": "Súhrnný počet paketov IPv4 / IPv6 / TCP / UDP / ICMP / non-IP paketov na sieťovú kartu" }, { - "mode": "Podrobné štatistiky rozhrania", + "mode": "Detailed interface stats", "useFor": "Rovnaké ako vyššie, ale pre jedno rozhranie, s podrobnosťami o veľkosti a rýchlosti paketov" }, { - "mode": "Štatistické členenia", + "mode": "Statistical breakdowns", "useFor": "Histogram rozdelenia veľkosti paketov podľa portu TCP / UDP" }, { - "mode": "Monitor stanice LAN", + "mode": "LAN station monitor", "useFor": "Štatistiky prevádzky podľa MAC adresy pre lokálnu broadcastovú doménu" } ], diff --git a/web/messages/sv/docs/network/monitoring.json b/web/messages/sv/docs/network/monitoring.json index 6ef3b059..e09a8e9b 100644 --- a/web/messages/sv/docs/network/monitoring.json +++ b/web/messages/sv/docs/network/monitoring.json @@ -48,23 +48,23 @@ "headerUseFor": "Använd den för", "rows": [ { - "mode": "IP trafikövervakning", + "mode": "IP traffic monitor", "useFor": "Lista i realtid över TCP-/UDP-/ICMP-/övriga IP-flöden med byteräknare och anslutningstillstånd" }, { - "mode": "Allmän gränssnittsstatistik", + "mode": "General interface stats", "useFor": "Samlat antal IPv4-/IPv6-/TCP-/UDP-/ICMP-/icke-IP-paket per nätverkskort" }, { - "mode": "Detaljerad gränssnittsstatistik", + "mode": "Detailed interface stats", "useFor": "Samma som ovan men för ett gränssnitt, med paketstorlek och hastighetsdetalj" }, { - "mode": "Statistisk fördelning", + "mode": "Statistical breakdowns", "useFor": "Histogram över paketstorleksfördelningen per TCP-/UDP-port" }, { - "mode": "Övervakning av LAN-stationer", + "mode": "LAN station monitor", "useFor": "Per-MAC trafikstatistik för den lokala sändningsdomänen" } ], From fc5884cdc11624165d0195d07636a8ed8a78f028 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:18:20 +0200 Subject: [PATCH 51/53] Keep Spanish OCI manager docs aligned with the screen and the glossary - Backups of containers and host storage are called backup - The watchdog is named vigilancia, as in the menu and the Monitor - The Monitor tabs keep their names App and Logs --- .../es/docs/oci-manager/architecture.json | 2 +- .../es/docs/oci-manager/custom-image.json | 4 ++-- web/messages/es/docs/oci-manager/index.json | 4 ++-- web/messages/es/docs/oci-manager/monitor.json | 6 +++--- .../es/docs/oci-manager/storage-network.json | 18 +++++++++--------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/web/messages/es/docs/oci-manager/architecture.json b/web/messages/es/docs/oci-manager/architecture.json index 75094840..1a65c74f 100644 --- a/web/messages/es/docs/oci-manager/architecture.json +++ b/web/messages/es/docs/oci-manager/architecture.json @@ -163,7 +163,7 @@ } }, { - "p": "mp0 es un segundo disco que pertenece al contenedor, con el nombre vm-VMID-disk-N en el almacenamiento seleccionado. Se monta en /config y, con backup=1, se incluye en la copia de seguridad del contenedor. mp1 no crea ningún disco: monta el directorio del host /srv/downloads en /downloads dentro del LXC. El puerto 8080 no se mapea: el LXC tiene su propia dirección y el servicio responde en ella." + "p": "mp0 es un segundo disco que pertenece al contenedor, con el nombre vm-VMID-disk-N en el almacenamiento seleccionado. Se monta en /config y, con backup=1, forma parte del backup del contenedor. mp1 no crea ningún disco: monta el directorio del host /srv/downloads en /downloads dentro del LXC. El puerto 8080 no se mapea: el LXC tiene su propia dirección y el servicio responde en ella." } ] } diff --git a/web/messages/es/docs/oci-manager/custom-image.json b/web/messages/es/docs/oci-manager/custom-image.json index c1f9642d..ed75b621 100644 --- a/web/messages/es/docs/oci-manager/custom-image.json +++ b/web/messages/es/docs/oci-manager/custom-image.json @@ -232,7 +232,7 @@ }, { "title": "Instalar y mantener al día", - "body": "Esa cuenta se utiliza para descargar la imagen durante la instalación y también en las actualizaciones, Modificar, Recrear, el watchdog y la comprobación de actualizaciones del Monitor, en cualquier nodo del clúster." + "body": "Esa cuenta se utiliza para descargar la imagen durante la instalación y también en las actualizaciones, Modificar, Recrear, la vigilancia y la comprobación de actualizaciones del Monitor, en cualquier nodo del clúster." } ] } @@ -252,7 +252,7 @@ { "calloutInfo": { "title": "Dónde se guardan las cuentas", - "body": "En /etc/pve/priv/proxmenux/registry-auth.json, accesible solo por root, en la misma carpeta donde Proxmox guarda las contraseñas de almacenamiento. /etc/pve se comparte entre los nodos del clúster, de modo que una cuenta añadida en un nodo se utiliza en todos. La copia del host incluye ese archivo y desinstalar ProxMenux lo elimina de todos los nodos." + "body": "En /etc/pve/priv/proxmenux/registry-auth.json, accesible solo por root, en la misma carpeta donde Proxmox guarda las contraseñas de almacenamiento. /etc/pve se comparte entre los nodos del clúster, de modo que una cuenta añadida en un nodo se utiliza en todos. El backup del host incluye ese archivo y desinstalar ProxMenux lo elimina de todos los nodos." } } ] diff --git a/web/messages/es/docs/oci-manager/index.json b/web/messages/es/docs/oci-manager/index.json index 8eef2329..635b395f 100644 --- a/web/messages/es/docs/oci-manager/index.json +++ b/web/messages/es/docs/oci-manager/index.json @@ -79,7 +79,7 @@ "p": "OCI manager Apps mantiene la etiqueta Technology Preview que Proxmox VE asigna a los contenedores de aplicaciones creados a partir de imágenes OCI, cuyo soporte se introdujo en Proxmox VE 9.1. La etiqueta se refiere a la tecnología sobre la que funciona OCI manager Apps, no al motor de ProxMenux: el comportamiento de estos contenedores puede cambiar entre versiones de Proxmox VE. Se mantendrá hasta que Proxmox VE considere estable ese soporte." }, { - "p": "El motor de OCI manager Apps se prueba en el laboratorio de ProxMenux: instalación, actualización, modificación, recreación, copias de seguridad, restauración, migración y watchdog. Cada aplicación se verifica por separado. Las que se han probado realmente muestran Verificado y llevan un ✓ en las listas." + "p": "El motor de OCI manager Apps se prueba en el laboratorio de ProxMenux: instalación, actualización, modificación, recreación, backups, restauración, migración y vigilancia. Cada aplicación se verifica por separado. Las que se han probado realmente muestran Verificado y llevan un ✓ en las listas." } ] }, @@ -103,7 +103,7 @@ [ "Conservar la configuración", "volumes: /config", - "Un volumen persistente mpN incluido en la copia de seguridad, o bien un directorio del host." + "Un volumen persistente mpN incluido en el backup, o bien un directorio del host." ], [ "Publicar el servicio", diff --git a/web/messages/es/docs/oci-manager/monitor.json b/web/messages/es/docs/oci-manager/monitor.json index aae9c49f..72701fb7 100644 --- a/web/messages/es/docs/oci-manager/monitor.json +++ b/web/messages/es/docs/oci-manager/monitor.json @@ -30,7 +30,7 @@ "Un interruptor Vigilancia bajo Iniciar al arrancar: la aplicación se inicia de nuevo cuando se detiene sola" ], [ - "Aplicación", + "App", "La aplicación se identifica desde el registro y sus actualizaciones se siguen por la imagen" ], [ @@ -42,7 +42,7 @@ "Discos del contenedor y directorios del host, con su uso" ], [ - "Registros", + "Logs", "Solo en contenedores OCI: la salida de consola de la aplicación" ] ] @@ -150,7 +150,7 @@ }, { "id": "logs", - "title": "Registros", + "title": "Logs", "intro": "La pestaña Logs solo aparece en los contenedores instalados por OCI manager Apps, entre Montajes y Backups. Muestra la salida estándar y de errores del proceso principal de la imagen, la misma que muestra docker logs en un contenedor Docker.", "blocks": [ { diff --git a/web/messages/es/docs/oci-manager/storage-network.json b/web/messages/es/docs/oci-manager/storage-network.json index 554e3f91..5884786e 100644 --- a/web/messages/es/docs/oci-manager/storage-network.json +++ b/web/messages/es/docs/oci-manager/storage-network.json @@ -75,7 +75,7 @@ "mpN: /mnt/oci-shared/media,mp=/data/media" ], [ - "Copia del contenedor (vzdump)", + "Backup del contenedor (vzdump)", "Incluido, con backup=1", "No incluido" ], @@ -123,7 +123,7 @@ } }, { - "p": "Una actualización o recreación solo sustituye el rootfs: mp0 conserva usuarios, bibliotecas y ajustes, y mp1 sigue mostrando los mismos archivos multimedia. Al restaurar la copia del CT se recupera /config; si hace falta restaurar el directorio multimedia, se utiliza la copia de seguridad del almacenamiento del host." + "p": "Una actualización o recreación solo sustituye el rootfs: mp0 conserva usuarios, bibliotecas y ajustes, y mp1 sigue mostrando los mismos archivos multimedia. Al restaurar el backup del CT se recupera /config; si hace falta restaurar el directorio multimedia, se utiliza el backup del almacenamiento del host." }, { "flow": { @@ -261,30 +261,30 @@ }, { "id": "backup", - "title": "Qué contiene una copia del contenedor", + "title": "Qué contiene el backup de un contenedor", "blocks": [ { "table": { "headers": [ "Elemento", - "En la copia vzdump del CT", + "En el backup vzdump del CT", "Dónde se conserva" ], "rows": [ [ "Rootfs OCI", "Sí", - "En la copia del CT; también se puede reconstruir a partir de la imagen y el contrato" + "En el backup del CT; también se puede reconstruir a partir de la imagen y el contrato" ], [ "Disco del contenedor con backup=1", "Sí", - "La copia del CT" + "El backup del CT" ], [ "Directorio del host", "No", - "La copia de seguridad del almacenamiento del host" + "El backup del almacenamiento del host" ], [ "Contrato de la instancia", @@ -293,8 +293,8 @@ ], [ "Aplicación con varios contenedores", - "Cada miembro en su propia copia", - "Las copias de todos los miembros, más el contrato de la pila y su puente de red en el host" + "Cada miembro en su propio backup", + "Los backups de todos los miembros, más el contrato de la pila y su puente de red en el host" ] ] } From b6bce3cae4bcd59b96575973e3741c9f2d2ec4c1 Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:19:12 +0200 Subject: [PATCH 52/53] Keep Spanish project information aligned with the screen and the glossary - The uninstaller is reached from Ajustes, as the Spanish menu shows - Backups are called backup; GitHub Discussions keeps its name --- web/messages/es/docs/about/contributing.json | 2 +- web/messages/es/docs/about/faq.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/messages/es/docs/about/contributing.json b/web/messages/es/docs/about/contributing.json index 5ac006ce..f5090e22 100644 --- a/web/messages/es/docs/about/contributing.json +++ b/web/messages/es/docs/about/contributing.json @@ -163,7 +163,7 @@ }, { "function": "msg_success \"text\"", - "whenRich": "Mensaje final «Pulsa Intro para volver» al terminar la fase 2.", + "whenRich": "Mensaje final «Press Enter to return» al terminar la fase 2.", "spinner": "Para" }, { diff --git a/web/messages/es/docs/about/faq.json b/web/messages/es/docs/about/faq.json index 834d8ffa..c6d9252d 100644 --- a/web/messages/es/docs/about/faq.json +++ b/web/messages/es/docs/about/faq.json @@ -19,7 +19,7 @@ "uninstallLabel": "Desinstalación", "uninstallSuffix": " — eliminación limpia.", "issuesLabel": "GitHub Issues", - "discussionsLabel": "Discusiones" + "discussionsLabel": "Discussions" }, "q1": { "question": "¿Qué es ProxMenux y para qué sirve?", @@ -75,7 +75,7 @@ "item2Rich": "El árbol de scripts en /usr/local/share/proxmenux/.", "item3Rich": "El lanzador en /usr/local/bin/menu.", "item4Rich": "Opcional: el servicio systemd del Monitor en proxmenux-monitor.service.", - "p2Rich": "ProxMenux no modifica la configuración principal de Proxmox salvo que lo solicites expresamente desde un menú específico. Por ejemplo, la reparación de red edita /etc/network/interfaces, pero solo después de tu confirmación y de crear una copia de seguridad." + "p2Rich": "ProxMenux no modifica la configuración principal de Proxmox salvo que lo solicites expresamente desde un menú específico. Por ejemplo, la reparación de red edita /etc/network/interfaces, pero solo después de tu confirmación y de hacer un backup." }, "q9": { "question": "¿Es seguro usar ProxMenux en producción?", @@ -83,7 +83,7 @@ }, "q10": { "question": "¿Cómo desinstalo ProxMenux?", - "p1Rich": "Desde ProxMenux: Configuración → Desinstalar ProxMenux. El desinstalador elimina el árbol de scripts, el lanzador y el servicio Monitor, permite eliminar dependencias seleccionadas y restaura /root/.bashrc y /etc/motd desde las copias de la instalación, si existen.", + "p1Rich": "Desde ProxMenux: Ajustes → Desinstalar ProxMenux. El desinstalador elimina el árbol de scripts, el lanzador y el servicio Monitor, permite eliminar dependencias seleccionadas y restaura /root/.bashrc y /etc/motd desde los backups hechos al instalar, si existen.", "p2Rich": "Paso a paso completo: Desinstalar ProxMenux." }, "q11": { From 36a8e84491ccc26f932838bd7708ef0f9ce50aeb Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 18 Oct 2026 13:37:18 +0200 Subject: [PATCH 53/53] Update common.json --- AppImage/messages/es/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index 02a4260b..91a29007 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -2673,7 +2673,7 @@ "expires": "Vence", "selfSignedWarning": "Los certificados autofirmados pueden mostrar una advertencia en el navegador. Esto es normal para las configuraciones locales de Proxmox.", "configuring": "Configurando...", - "useProxmoxCertificate": "Utilice el certificado Proxmox", + "useProxmoxCertificate": "Usar certificado de Proxmox", "noProxmoxCertificate": "No se detectó ningún certificado Proxmox.", "useCustomCertificate": "Usar certificado personalizado", "customPaths": "Rutas de certificados personalizadas",