Compare commits

..
25 Commits
Author SHA1 Message Date
ProxMenuxBot 1ca077b6d4 Update helpers_cache.json 2026-11-04 18:03:37 +00:00
ProxMenuxBot 20e3321715 Update helpers_cache.json 2026-11-04 12:05:17 +00:00
github-actions[bot] 0441ab7968 chore: update repository growth [skip ci] 2026-11-04 04:22:28 +00:00
ProxMenuxBot ddbe4f7336 Update helpers_cache.json 2026-11-04 00:09:53 +00:00
ProxMenuxBot 8f3f4eecbd Update helpers_cache.json 2026-11-03 18:03:53 +00:00
ProxMenuxBot 44ed927fae Update app tracking hints 2026-11-03 18:02:13 +00:00
github-actions[bot] bf94c37d96 chore: update repository growth [skip ci] 2026-11-03 04:22:45 +00:00
ProxMenuxBot cb4848c31c Update helpers_cache.json 2026-11-03 00:08:57 +00:00
github-actions[bot] 1f70609b90 Update AppImage release build (2026-11-02 18:39:04) 2026-11-02 18:39:04 +00:00
MacRimiandGitHub b14049d457 New version 1.2.8
New version 1.2.8
2026-11-02 19:27:35 +01:00
MacRimi 98931fe4a7 New version 1.2.8 2026-11-02 19:22:47 +01:00
MacRimi 46337f162f New version 1.2.8 2026-11-02 19:20:30 +01:00
MacRimiandGitHub 064c2a6c39 Merge pull request #557 from MaEscalanteHe/feature/web-refresh
fix(web): dependency cleanup, HTML entity rendering and sidebar fixes
2026-11-02 19:10:00 +01:00
ProxMenuxBot aadef0ee44 Update helpers_cache.json 2026-11-02 18:04:14 +00:00
MacRimi 3feb5ca5cb fix(web): wrap inline code and scroll tables on narrow screens 2026-11-02 19:01:03 +01:00
MacRimi b0215d4bbb fix: OCI image data paths, LAN endpoints for OpenAI-compatible AI and lab-validated apps 2026-11-02 18:42:32 +01:00
MacRimi 9f3fffd249 fix: one source for the disk type and a config volume for SearXNG 2026-11-02 17:28:31 +01:00
ProxMenuxBot 9271521426 Update helpers_cache.json 2026-11-02 12:05:58 +00:00
Manuel Escalante 511211f10b fix(web): keep ICU quotes out of messages inserted as raw HTML 2026-11-02 10:32:41 +01:00
github-actions[bot] 41081121f8 Update AppImage beta build (2026-11-02 07:28:12) 2026-11-02 07:28:12 +00:00
MacRimi e83794b7e2 fix(web): send only the client translations to each page 2026-11-02 08:25:28 +01:00
Manuel Escalante 64fa4f8115 fix(web): restore Change Language entry in docs sidebar 2026-10-29 19:54:41 +01:00
Manuel Escalante c26eb49e2c fix(web): render HTML entities in translated text 2026-10-29 09:41:32 +01:00
Manuel Escalante db12972253 fix(web): pass em tag to host-local-disk format steps 2026-10-29 09:41:32 +01:00
Manuel Escalante fac7ac5021 chore(web): drop unused dependencies and apply minor updates and audit fixes 2026-10-29 09:41:32 +01:00
82 changed files with 2532 additions and 2553 deletions
+26 -4
View File
@@ -1,11 +1,11 @@
{
"version": 1,
"repository": "MacRimi/ProxMenux",
"updatedAt": "2026-11-02T04:23:29.007Z",
"updatedAt": "2026-11-04T04:22:27.770Z",
"backfilledAt": "2026-07-22T18:09:20.834Z",
"totals": {
"downloads": 0,
"clones": 68648
"clones": 71094
},
"assets": {},
"traffic": {
@@ -373,6 +373,14 @@
"2026-10-31": {
"count": 753,
"uniques": 475
},
"2026-11-01": {
"count": 827,
"uniques": 479
},
"2026-11-02": {
"count": 1619,
"uniques": 975
}
}
},
@@ -4029,14 +4037,28 @@
"stars": 3254,
"forks": 177,
"downloads": 0,
"clones": 68648
"clones": 69475
},
{
"date": "2026-11-02",
"stars": 3258,
"forks": 178,
"downloads": 0,
"clones": 68648
"clones": 71094
},
{
"date": "2026-11-03",
"stars": 3263,
"forks": 179,
"downloads": 0,
"clones": 71094
},
{
"date": "2026-11-04",
"stars": 3268,
"forks": 180,
"downloads": 0,
"clones": 71094
}
]
}
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
499ac86b6da30a59b805a970d3b408ae16e4d5e5ff477a8019ebbabcb7176f1b ProxMenux-1.2.8.AppImage
51f00f78f6e67f457308dd8a2407e79f59ad1a22925cda7acfa75d82e2f7086b ProxMenux-1.2.8.AppImage
+5 -1
View File
@@ -397,6 +397,10 @@ const SELFHST_THEME_LOGOS: Record<string, { lightTheme: string; darkTheme: strin
lightTheme: `${SELFHST_WEBP_BASE}/frigate-dark.webp`,
darkTheme: `${SELFHST_WEBP_BASE}/frigate-light.webp`,
},
"llama-cpp": {
lightTheme: `${SELFHST_WEBP_BASE}/llama-cpp.webp`,
darkTheme: `${SELFHST_WEBP_BASE}/llama-cpp-light.webp`,
},
portainer: {
lightTheme: `${SELFHST_WEBP_BASE}/portainer-dark.webp`,
darkTheme: `${SELFHST_WEBP_BASE}/portainer-light.webp`,
@@ -409,7 +413,7 @@ const SELFHST_THEME_LOGOS: Record<string, { lightTheme: string; darkTheme: strin
function selfhstThemeLogos(src: string) {
if (!src.toLowerCase().includes("cdn.jsdelivr.net/gh/selfhst/icons")) return null
const match = src.toLowerCase().match(/\/(frigate|portainer|vaultwarden)(?:-(?:dark|light))?\.webp(?:[?#].*)?$/)
const match = src.toLowerCase().match(/\/(frigate|llama-cpp|portainer|vaultwarden)(?:-(?:dark|light))?\.webp(?:[?#].*)?$/)
return match ? SELFHST_THEME_LOGOS[match[1]] : null
}
+13 -20
View File
@@ -804,7 +804,7 @@ export function StorageOverview({ disksOnly = false }: { disksOnly?: boolean } =
</div>
)}
{/* Reallocated only meaningful on rotating disks. */}
{disk.reallocated_sectors !== undefined && (disk.rotation_rate ?? 0) > 0 && (
{disk.reallocated_sectors !== undefined && resolveDiskType(disk.name, disk.rotation_rate) === "HDD" && (
<div className="flex items-baseline justify-between gap-3">
<span className="text-[11px] uppercase tracking-wider text-muted-foreground">
{t("storage.reallocatedShort")}
@@ -860,12 +860,7 @@ export function StorageOverview({ disksOnly = false }: { disksOnly?: boolean } =
// Resolve disk class → threshold pair from the user-configurable
// backend (single source of truth). The semantics: temp BELOW warn
// is green, between warn and hot is amber, hot or above is red.
let cls: keyof DiskTempMap = "HDD"
if (diskName?.startsWith("nvme")) {
cls = "NVMe"
} else if (!rotationRate || rotationRate === 0) {
cls = "SSD"
}
const cls: keyof DiskTempMap = resolveDiskType(diskName ?? "", rotationRate)
const t = dtThresholds[cls]
if (temp >= t.hot) return "text-red-500"
if (temp >= t.warn) return "text-yellow-500"
@@ -896,9 +891,9 @@ export function StorageOverview({ disksOnly = false }: { disksOnly?: boolean } =
return t("storage.duration.daysShort", { days: totalDays })
}
const formatRotationRate = (rpm: number | undefined) => {
if (!rpm || rpm === 0) return "SSD"
return `${rpm.toLocaleString()} RPM`
const formatRotationRate = (diskName: string, rpm: number | undefined) => {
if (getDiskType(diskName, rpm) !== "HDD") return "SSD"
return rpm && rpm > 0 ? `${rpm.toLocaleString()} RPM` : t("common.notAvailable")
}
// Thin wrapper over the shared classifier so the rest of the file
@@ -2626,7 +2621,7 @@ export function StorageOverview({ disksOnly = false }: { disksOnly?: boolean } =
</div>
<div>
<p className="text-sm text-muted-foreground">{t("storage.rotationRate")}</p>
<p className="font-medium">{formatRotationRate(selectedDisk.rotation_rate)}</p>
<p className="font-medium">{formatRotationRate(selectedDisk.name, selectedDisk.rotation_rate)}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">{t("storage.powerCycles")}</p>
@@ -2871,14 +2866,12 @@ function openSmartReport(disk: DiskInfo, testStatus: SmartTestStatus, smartAttri
// Determine disk type (SAS detected via backend flag or connection_type)
const isSasDisk = sd?.is_sas === true || disk.connection_type === 'sas'
let diskType = "HDD"
if (disk.name.startsWith("nvme")) {
diskType = "NVMe"
} else if (isSasDisk) {
let diskType: string = resolveDiskType(disk.name, disk.rotation_rate)
if (diskType !== "NVMe" && isSasDisk) {
diskType = "SAS"
} else if (!disk.rotation_rate || disk.rotation_rate === 0) {
diskType = "SSD"
}
const rpm = disk.rotation_rate && disk.rotation_rate > 0 ? disk.rotation_rate : 0
const known = (value?: string) => (value && value !== "Unknown" ? value : undefined)
// Health status styling
const healthStatus = String(testStatus.smart_status || (testStatus.smart_data?.smart_status) || 'unknown')
@@ -3824,11 +3817,11 @@ function pmxPrint(){
<div class="grid-4">
<div class="card">
<div class="card-label">${tSmart("labels.model")}</div>
<div class="card-value" style="font-size:11px;">${disk.model || sd?.model || t("app.unknown")}</div>
<div class="card-value" style="font-size:11px;">${known(disk.model) || known(sd?.model) || t("app.unknown")}</div>
</div>
<div class="card">
<div class="card-label">${tSmart("labels.serial")}</div>
<div class="card-value" style="font-size:11px;font-family:monospace;">${disk.serial || sd?.serial || t("app.unknown")}</div>
<div class="card-value" style="font-size:11px;font-family:monospace;">${known(disk.serial) || known(sd?.serial) || t("app.unknown")}</div>
</div>
<div class="card">
<div class="card-label">${tSmart("labels.capacity")}</div>
@@ -3836,7 +3829,7 @@ function pmxPrint(){
</div>
<div class="card">
<div class="card-label">${tSmart("labels.type")}</div>
<div class="card-value" style="font-size:11px;">${diskType === 'SAS' ? (disk.rotation_rate ? `SAS ${disk.rotation_rate} RPM` : 'SAS SSD') : diskType === 'HDD' && disk.rotation_rate ? `HDD ${disk.rotation_rate} RPM` : diskType}</div>
<div class="card-value" style="font-size:11px;">${diskType === 'SAS' ? (rpm ? `SAS ${rpm} RPM` : disk.rotation_rate === -1 ? 'SAS HDD' : 'SAS SSD') : diskType === 'HDD' && rpm ? `HDD ${rpm} RPM` : diskType}</div>
</div>
</div>
${(modelFamily || formFactor || sataVersion || ifaceSpeed) ? `
+7 -1
View File
@@ -27,6 +27,8 @@ import sys
import time
from typing import Any, Optional
import disk_identity
SCHEMA_VERSION = 2
# Disk entries in a guest configuration: rootfs and mpN for containers,
@@ -630,12 +632,16 @@ def _physical_disks(ctx) -> list[dict[str, Any]]:
size = row.get("SIZE", "")
name = row.get("NAME", "")
health = smart.get(name) or {}
# The Monitor's rotation when it read the disk, so both agree.
rate = health.get("rotation_rate")
if not isinstance(rate, int):
rate = -1 if row.get("ROTA") == "1" else 0
disks.append({
"name": name,
"model": (row.get("MODEL") or "").strip(),
"serial": (row.get("SERIAL") or "").strip(),
"size_bytes": int(size) if size.isdigit() else None,
"rotational": row.get("ROTA") == "1",
"rotational": disk_identity.media_type(name, rate) == "HDD",
"bus": (row.get("TRAN") or "").strip(),
"health": health.get("smart_status"),
"temperature": health.get("temperature"),
+22
View File
@@ -15,6 +15,7 @@ import subprocess
from typing import Any, Dict, List
_LSBLK_TIMEOUT = 5
_SYS_BLOCK = "/sys/block"
_FIELD_RE = re.compile(r'(\w+)="([^"]*)"')
_SKIP_PREFIXES = ("loop", "zd", "nbd", "ram", "sr")
@@ -69,6 +70,27 @@ def list_physical_disks() -> List[Dict[str, Any]]:
return disks
def rotation_rate(name: str, reported: Any = None) -> int:
"""The rotation every view of a disk shows: the RPM smartctl reported,
0 meaning solid state, or else the kernel's flag, where -1 is a spinning
disk of unknown speed. What smartctl reported wins, because USB-SATA
bridges mark every disk behind them as rotational."""
if isinstance(reported, int) and not isinstance(reported, bool) and reported >= 0:
return reported
try:
with open(f"{_SYS_BLOCK}/{name}/queue/rotational") as flag:
return -1 if flag.read().strip() == "1" else 0
except OSError:
return 0
def media_type(name: str, rate: int) -> str:
"""NVMe, SSD or HDD, from a rotation as ``rotation_rate`` returns it."""
if name.startswith("nvme"):
return "NVMe"
return "HDD" if rate else "SSD"
def names_for_keys(keys) -> set:
"""Current kernel names of the disks whose identity is in ``keys``."""
if not keys:
+10 -28
View File
@@ -52,6 +52,7 @@ import time
import threading
import urllib.parse
import uuid
import disk_identity
import hardware_monitor
from health_persistence import health_persistence
import xml.etree.ElementTree as ET
@@ -5263,11 +5264,7 @@ def _smart_identity_payload(disk_name):
base['serial'] = value.strip()
except OSError:
pass
try:
if Path(f'/sys/block/{disk_name}/queue/rotational').read_text().strip() == '1':
base['rotation_rate'] = -1 # HDD, RPM unknown
except OSError:
pass
base['rotation_rate'] = disk_identity.rotation_rate(disk_name)
return base
@@ -5327,7 +5324,8 @@ def get_smart_data(disk_name):
retry_at = _smart_fail_backoff.get(disk_name, 0)
if retry_at > now:
return dict(cached[1]) if cached else _smart_default_payload()
# smartctl keeps failing: show what the host knows of the disk.
return dict(cached[1]) if cached else _smart_identity_payload(disk_name)
result = _get_smart_data_uncached(disk_name)
@@ -5801,6 +5799,11 @@ def _get_smart_data_uncached(disk_name):
# print(f"[v0] Health: CRITICAL (SMART failed)")
pass
# Without an answer from smartctl, the kernel's flag; the temperature
# class below depends on it.
if not smart_data['_rotation_known']:
smart_data['rotation_rate'] = disk_identity.rotation_rate(disk_name)
# Temperature-based health (only if we have a valid temperature)
# Thresholds differ by disk class (HDD/SSD/NVMe/SAS) and are
# user-configurable via health_thresholds; the values below are
@@ -5831,27 +5834,6 @@ def _get_smart_data_uncached(disk_name):
smart_data['health'] = 'critical'
elif temp > warn:
smart_data['health'] = 'warning'
# Fallback: ask the kernel only when smartctl had no opinion.
# USB-SATA enclosures (ASM105x family etc.) advertise
# rotational=1 to the kernel even when there's an SSD behind
# them, so trusting /sys here without first checking what
# smartctl said would flip a correctly-identified SSD back to
# HDD. The previous condition (`rotation_rate == 0`) couldn't
# tell "smartctl confirmed SSD" from "smartctl never spoke",
# which is what bit the OCZ-SOLID2 behind an ASM105x dock on
# the lab host (sdd showed as HDD in the UI while smartctl
# was reporting it as SSD).
if not smart_data['_rotation_known']:
try:
rotational_path = f"/sys/block/{disk_name}/queue/rotational"
if os.path.exists(rotational_path):
with open(rotational_path, 'r') as f:
rotational = int(f.read().strip())
if rotational == 1:
smart_data['rotation_rate'] = -1 # HDD, RPM unknown
except Exception:
pass
except FileNotFoundError:
# print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.")
pass
@@ -11523,7 +11505,7 @@ def api_smart_status(disk_name):
'temperature': temp,
'power_on_hours': poh,
'power_cycles': cycles,
'rotation_rate': data.get('rotation_rate', 0),
'rotation_rate': disk_identity.rotation_rate(disk_name, data.get('rotation_rate')),
'form_factor': form_factor,
'physical_block_size': physical_block_size,
'trim_supported': trim_supported,
+4 -1
View File
@@ -3163,7 +3163,10 @@ class NotificationManager:
if not ok:
raise ValueError(f"Invalid ai_ollama_url: {err}")
if short_key == 'ai_openai_base_url' and str(value):
ok, err = validate_external_url(str(value), allow_loopback=False)
# Custom OpenAI-compatible servers can run on the LAN or
# localhost, just like Ollama. Match test-ai/provider-models;
# unsafe schemes, metadata hosts and link-local stay blocked.
ok, err = validate_external_url(str(value), allow_loopback=True)
if not ok:
raise ValueError(f"Invalid ai_openai_base_url: {err}")
@@ -69,6 +69,41 @@ class OpenAICompatibleModelTests(unittest.TestCase):
self.addCleanup(patcher.stop)
return db_path
def test_local_compatible_endpoint_can_be_saved_and_reloaded(self):
db_path = self._temp_db_patch()
# Synthetic addresses cover each RFC1918 range; no network is used.
for url in ('http://10.0.0.10:8080/v1', 'http://172.16.0.10:8080/v1',
'http://192.168.1.10:8080/v1', 'http://localhost:8080/v1',
'http://127.0.0.1:8080/v1'):
with self.subTest(url=url):
manager = self._manager({})
result = manager.save_settings({'ai_openai_base_url': url})
self.assertTrue(result['success'], result)
with sqlite3.connect(db_path) as conn:
saved = conn.execute(
'SELECT setting_value FROM user_settings WHERE setting_key=?',
('notification.ai_openai_base_url',),
).fetchone()
self.assertEqual(saved, (url,))
reloaded = self._manager({})
reloaded._load_config()
self.assertEqual(reloaded._config['ai_openai_base_url'], url)
def test_local_compatible_endpoint_keeps_unsafe_urls_blocked(self):
db_path = self._temp_db_patch()
for url in ('file:///etc/shadow', 'http://169.254.169.254/latest/meta-data/',
'http://metadata.google.internal/', 'http://[fe80::1]/',
'http://0.0.0.0:8080/v1', 'http://224.0.0.1/v1'):
with self.subTest(url=url):
result = self._manager({}).save_settings({'ai_openai_base_url': url})
self.assertFalse(result['success'], result)
with sqlite3.connect(db_path) as conn:
saved = conn.execute(
'SELECT setting_value FROM user_settings WHERE setting_key=?',
('notification.ai_openai_base_url',),
).fetchone()
self.assertIsNone(saved)
def test_custom_openai_endpoint_omits_temperature_for_opaque_alias(self):
provider = CapturingOpenAIProvider(
api_key="token",
+2
View File
@@ -23,6 +23,7 @@ A special thank you goes to [@Vaso73](https://github.com/Vaso73), [@f3rs3n](http
- **The Ollama entry describes what the image provides** ([PR #566](https://github.com/MacRimi/ProxMenux/pull/566), contributed by [@jcastro](https://github.com/jcastro)): its HTTP API on port 11434, with its link and startup check on `/api/version`.
- **An update clears the snapshot an interrupted backup leaves** ([#565](https://github.com/MacRimi/ProxMenux/issues/565), reported by [@kiosvantra](https://github.com/kiosvantra)). A snapshot made by the user stops the update, because a rebuilt container cannot keep it.
- **A container that ignores the shutdown signal is stopped after 60 seconds**, as `docker stop` does, during an update and when an interrupted operation is recovered.
- **Modify and updates accept every application whose image declares a data path**: SearXNG, Tor, Whoogle and BIND9 keep it in a volume of its own, Kanboard keeps its certificates, and Chrony, Jaeger, PlantUML, UrBackup, GoToSocial, AceStream HTTP Proxy and RomM mark the paths that hold no data. When an installation still keeps such a path in the container, the message names it.
## 🖥️ Monitor — Proxmox updates and pending reboots
@@ -59,6 +60,7 @@ A special thank you goes to [@Vaso73](https://github.com/Vaso73), [@f3rs3n](http
- **Bitwarden recognizes the 2FA field on the Monitor login** ([#570](https://github.com/MacRimi/ProxMenux/issues/570), reported by [@shtefko](https://github.com/shtefko)): the backup code hint keeps its text and place, outside the group of the field.
- **Secure Gateway installs on Proxmox hosts on BTRFS** ([#564](https://github.com/MacRimi/ProxMenux/issues/564), reported by [@78v5](https://github.com/78v5)): the Alpine template goes to an active storage that holds container templates.
- **A disk I/O error from the kernel is recorded on its disk**, with its serial number, also when the error names a partition.
- **Every view of a disk shows the same type**: the Storage page, the disk details, the SMART report and the Audit & Report inventory take it from one source. A hard disk smartctl cannot read keeps its type, model and serial number, and a hard disk that reports no speed shows as HDD.
- **The Monitor installs as an app (PWA) from the browser**: it serves its own web app manifest and service worker, with the 192 and 512 px icons Chrome requires.
- **The Y-axis titles stay inside the overview charts** ([PR #539](https://github.com/MacRimi/ProxMenux/pull/539), contributed by [@f3rs3n](https://github.com/f3rs3n)).
- **The application version-tracking catalog builds from the current Helper-Scripts repositories**.
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+1 -1
View File
@@ -5993,7 +5993,7 @@
"github_source": "releases",
"installed_via": "file",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/wiki-js.webp",
"repo": "requarks/wiki-v2",
"repo": "requarks/wiki",
"tag_regex": "(?i)(?:v|release[-_/]?)?(\\d+(?:\\.\\d+){1,3}(?:[-+._][0-9A-Za-z.-]+)?)",
"website": "https://js.wiki/"
},
+147 -43
View File
@@ -2134,6 +2134,37 @@
"config_path": "",
"os": "debian"
},
{
"name": "Bark Server",
"slug": "bark-server",
"desc": "Bark Server is the self-hosted backend for the Bark iOS app. It receives push requests over a simple HTTP API and forwards them to Apple's APNs, so scripts and home-automation flows can send custom notifications to your own iPhone. Device keys are stored locally in an embedded key-value database, so no external database is required.",
"script": "ct/bark-server.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/bark-server.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/bark-server.sh",
"type": "lxc",
"type_id": "nm9bra8mzye2scg",
"categories": [],
"category_names": [],
"notes": [
"Verify the server with: curl http://<IP>:8080/ping — it returns {\"code\":200,\"message\":\"pong\"}."
],
"warnings": [
"Bark Server has no authentication by default, set BARK_SERVER_BASIC_AUTH_USER / BARK_SERVER_BASIC_AUTH_PASSWORD to enable HTTP Basic auth."
],
"port": 8080,
"website": "https://bark.day.app/",
"documentation": "https://bark.day.app/",
"logo": "https://bark.day.app/logo.png",
"updateable": true,
"privileged": false,
"has_arm": false,
"is_dev": true,
"execute_in": [
"lxc"
],
"config_path": "",
"os": "debian"
},
{
"name": "Baserow",
"slug": "baserow",
@@ -6916,6 +6947,40 @@
"config_path": "",
"os": "debian"
},
{
"name": "Factorio",
"slug": "factorio",
"desc": "Factorio headless multiplayer server using the current stable Linux release.",
"script": "ct/factorio.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/factorio.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/factorio.sh",
"type": "lxc",
"type_id": "nm9bra8mzye2scg",
"categories": [
"scriptcat00024"
],
"category_names": [
"Gaming & Leisure"
],
"notes": [
"Connect using the Factorio client to <container IP>:34197. Clients need the same Factorio version and mods as the server.",
"Saves are stored in /opt/factorio/saves. The service loads the most recent save on restart."
],
"warnings": [],
"port": 0,
"website": "https://factorio.com/",
"documentation": "https://wiki.factorio.com/Multiplayer",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/factorio.webp",
"updateable": true,
"privileged": false,
"has_arm": false,
"is_dev": true,
"execute_in": [
"lxc"
],
"config_path": "",
"os": "debian"
},
{
"name": "FalkorDB",
"slug": "falkordb",
@@ -7944,6 +8009,46 @@
"config_path": "",
"os": "debian"
},
{
"name": "FreeScout",
"slug": "freescout",
"desc": "FreeScout is a self-hosted help desk and shared mailbox, an open-source alternative to Zendesk and Help Scout. It turns support email into conversations that agents assign, tag and answer together, with saved replies, internal notes, collision detection and customer history. Modules add features like a knowledge base, satisfaction ratings and workflows.",
"script": "ct/freescout.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/freescout.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/freescout.sh",
"type": "lxc",
"type_id": "nm9bra8mzye2scg",
"categories": [
"scriptcat00025"
],
"category_names": [
"Business & ERP"
],
"notes": [
"Sign in with the admin email and the generated password saved in ~/freescout.creds inside the container.",
"Mail servers on private IPs are rejected until listed in APP_REMOTE_HOST_WHITE_LIST in /opt/freescout/.env (clear the cache afterwards)."
],
"warnings": [
"FreeScout only answers on the host in APP_URL (the container IP). For a domain or reverse proxy, change APP_URL in /opt/freescout/.env, then click Clear Cache under Manage > System > Tools."
],
"port": 80,
"website": "https://freescout.net/",
"documentation": "https://github.com/freescout-help-desk/freescout/wiki",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/freescout.webp",
"updateable": true,
"privileged": false,
"has_arm": false,
"is_dev": true,
"execute_in": [
"lxc"
],
"config_path": "",
"default_credentials": {
"username": "admin@example.com",
"password": ""
},
"os": "debian"
},
{
"name": "FreshRSS",
"slug": "freshrss",
@@ -17067,7 +17172,6 @@
"Files & Downloads"
],
"notes": [
"One script serves both variants: the installer asks for the container OS, or pass var_os=alpine to pick Alpine unattended.",
"The admin password is generated during the installation and saved to /root/openlist.creds inside the container. Change it with `/opt/openlist/openlist --data /opt/openlist_data admin set <password>`.",
"Config, database, logs and search index live in /opt/openlist_data, outside the directory an update replaces, so updating never touches them."
],
@@ -17090,45 +17194,6 @@
},
"os": "debian"
},
{
"name": "OpenList",
"slug": "openlist",
"desc": "OpenList is a file list program that mounts many storage backends - S3, WebDAV, OneDrive, Google Drive, SFTP, local disks and more - behind one web UI, and serves them again over WebDAV, FTP and S3. It is the community-driven fork of AList.",
"script": "ct/openlist.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/openlist.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/openlist.sh",
"type": "lxc",
"type_id": "nm9bra8mzye2scg",
"categories": [
"scriptcat00011"
],
"category_names": [
"Files & Downloads"
],
"notes": [
"One script serves both variants: the installer asks for the container OS, or pass var_os=alpine to pick Alpine unattended.",
"The admin password is generated during the installation and saved to /root/openlist.creds inside the container. Change it with `/opt/openlist/openlist --data /opt/openlist_data admin set <password>`.",
"Config, database, logs and search index live in /opt/openlist_data, outside the directory an update replaces, so updating never touches them."
],
"warnings": [],
"port": 5244,
"website": "https://oplist.org/",
"documentation": "https://doc.oplist.org/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/openlist.webp",
"updateable": true,
"privileged": false,
"has_arm": false,
"is_dev": true,
"execute_in": [
"lxc"
],
"config_path": "",
"default_credentials": {
"username": "admin",
"password": ""
},
"os": "alpine"
},
{
"name": "OpenObserve",
"slug": "openobserve",
@@ -17980,7 +18045,7 @@
"notes": [
"First visit will prompt you to create an account",
"Tesseract OCR is pre-installed for all languages",
"Documents are stored in /opt/papra/app-data/documents",
"Documents are stored in /opt/papra_data/documents",
"Ingestion folder available at /opt/papra_data/ingestion for automatic document import"
],
"warnings": [
@@ -19363,7 +19428,7 @@
{
"name": "PowerDNS",
"slug": "powerdns",
"desc": "The PowerDNS Authoritative Server is a versatile nameserver which supports a large number of backends. These backends can either be plain zone files or be more dynamic in nature. PowerDNS has the concepts of ‘backends’. A backend is a datastore that the server will consult that contains DNS records (and some metadata). The backends range from database backends (MySQL, PostgreSQL) and BIND zone files to co-processes and JSON API’s.",
"desc": "<p>PowerDNS Authoritative Server with the Poweradmin web interface. The PowerDNS Authoritative Server is a versatile nameserver which supports a large number of backends. These backends can either be plain zone files or be more dynamic in nature. PowerDNS has the concepts of &lsquo;backends&rsquo;. A backend is a datastore that the server will consult that contains DNS records (and some metadata). The backends range from database backends (MySQL, PostgreSQL) and BIND zone files to co-processes and JSON API&rsquo;s.</p>",
"script": "ct/powerdns.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/powerdns.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/powerdns.sh",
@@ -23529,6 +23594,45 @@
"config_path": "",
"os": "debian"
},
{
"name": "SUB/WAVE",
"slug": "subwave",
"desc": "SUB/WAVE turns your own music library into a self-hosted internet radio station. An AI DJ picks the tracks from Navidrome, talks between them with a local or cloud voice and runs shows, jingles and listener requests, while everyone hears the same Icecast stream.",
"script": "ct/subwave.sh",
"script_url": "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/subwave.sh",
"script_url_mirror": "https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/subwave.sh",
"type": "lxc",
"type_id": "nm9bra8mzye2scg",
"categories": [
"scriptcat00013"
],
"category_names": [
"Media & Streaming"
],
"notes": [
"Open http://<IP>/onboarding and sign in with the admin password from ~/subwave.creds inside the container."
],
"warnings": [
"SUB/WAVE needs a Navidrome (Subsonic) server with your music and an LLM (Ollama, any OpenAI-compatible server or a cloud API key); the onboarding wizard connects both."
],
"port": 80,
"website": "https://getsubwave.com/",
"documentation": "https://getsubwave.com/manual",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/sub-wave.webp",
"updateable": true,
"privileged": false,
"has_arm": false,
"is_dev": true,
"execute_in": [
"lxc"
],
"config_path": "",
"default_credentials": {
"username": "admin",
"password": ""
},
"os": "debian"
},
{
"name": "SuggestArr",
"slug": "suggestarr",
@@ -25884,7 +25988,7 @@
],
"port": 0,
"website": "",
"documentation": "https://community-scripts.org/docs/tools/update-apps",
"documentation": "https://community-scripts.org/docs/tools/pve/update-apps",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/proxmox.webp",
"updateable": true,
"privileged": false,
+9 -1
View File
@@ -2,7 +2,7 @@
<!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. -->
44 of 532 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
52 of 532 applications in the OCI catalog have been tested for real: 49 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it.
@@ -38,6 +38,7 @@ Questions and ideas about OCI testing go in [discussion #440](https://github.com
| Alby Hub ✨ | Finance & Budgeting |
| Alist | Files & Sharing |
| aMule | Files & Downloads |
| Authentik | Authentication & Security |
| Chromium | Browsers & Web Desktops |
| CodeProject.AI Server | AI |
| CopyParty | Files & Sharing |
@@ -50,26 +51,33 @@ Questions and ideas about OCI testing go in [discussion #440](https://github.com
| Flexget | *Arr Suite |
| Frigate | NVR & Cameras |
| Grafana | Monitoring & Analytics |
| HAOS One | IoT & Smart Home |
| Immich | Media |
| JDownloader | Files & Downloads |
| Jenkins CI/CD | AI / Coding & Dev-Tools |
| Linkwarden | Documents & Notes |
| Mariadb | Databases |
| Memos | Documents & Notes |
| MineOS | Gaming & Leisure |
| Motioneye | NVR & Cameras |
| Navidrome | Media & Streaming |
| Nextcloud | Productivity & Workflows |
| Nextcloud Stack | Productivity & Workflows |
| OpenList | Files & Sharing |
| Openssh Server | Remote Access & VPN |
| Paperless-ngx | Productivity & Workflows |
| Phpmyadmin | Databases |
| Prowlarr | *Arr Suite |
| Qbittorrent | Files & Downloads |
| Rclone WebUI | Backup & Recovery |
| Real-Debrid Torrent Client | Files & Downloads |
| Scanopy | Network & Firewall |
| ShareCoveX | Files & Sharing |
| SnapOtter | Media & Streaming |
| Sonarr | *Arr Suite |
| Tandoor Recipes | Productivity & Workflows |
| Thelounge | Communication & Community |
| Trilium | Documents & Notes |
| Vaultwarden | Authentication & Security |
| Wireguard | Remote Access & VPN |
| WireGuard Easy | Remote Access & VPN |
@@ -323,6 +323,9 @@
"AceStream HTTP Proxy has no web interface: a media player opens its streams.",
"HLS stream: port 6878, path /ace/manifest.m3u8?id= followed by the channel ID.",
"MPEG-TS stream: port 6878, path /ace/getstream?id= followed by the channel ID."
],
"non_persistent_image_volumes": [
"/home/appuser/.ACEStream"
]
}
},
+62 -2
View File
@@ -76,6 +76,38 @@
"backup": true,
"default_size_gb": 2
}
},
{
"id": "volume-1",
"container_path": "/etc/bind",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
},
{
"id": "volume-2",
"container_path": "/var/lib/bind",
"compose_source_example": "./lib",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -123,7 +155,9 @@
"53:53/tcp"
],
"volumes": [
"./bind:/var/cache/bind"
"./bind:/var/cache/bind",
"./config:/etc/bind",
"./lib:/var/lib/bind"
]
}
}
@@ -151,6 +185,28 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "bind9-volume-1",
"service": "bind9",
"container_path": "/etc/bind",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "bind9-volume-2",
"service": "bind9",
"container_path": "/var/lib/bind",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -349,7 +405,11 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {},
"installer_profile": {
"non_persistent_image_volumes": [
"/var/log"
]
},
"catalog": {
"replaces_discovered_ids": []
}
+7 -1
View File
@@ -339,7 +339,13 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {}
"installer_profile": {
"non_persistent_image_volumes": [
"/etc/chrony",
"/run/chrony",
"/var/lib/chrony"
]
}
},
"compatibility": {
"automatic_install_candidate": true,
+4 -1
View File
@@ -409,7 +409,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/gotosocial/.cache"
]
}
},
"compatibility": {
+5 -1
View File
@@ -345,7 +345,11 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {},
"installer_profile": {
"non_persistent_image_volumes": [
"/tmp"
]
},
"catalog": {
"replaces_discovered_ids": []
}
+35 -1
View File
@@ -94,6 +94,22 @@
"backup": true,
"default_size_gb": 8
}
},
{
"id": "volume-2",
"container_path": "/etc/nginx/ssl",
"compose_source_example": "./ssl",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -134,7 +150,8 @@
],
"volumes": [
"./data:/var/www/app/data",
"./plugins:/var/www/app/plugins"
"./plugins:/var/www/app/plugins",
"./ssl:/etc/nginx/ssl"
]
}
}
@@ -171,6 +188,17 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "kanboard-volume-2",
"service": "kanboard",
"container_path": "/etc/nginx/ssl",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -391,6 +419,12 @@
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/etc/nginx/ssl",
"remove_lost_found": true,
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
+4 -1
View File
@@ -329,7 +329,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/tmp/jetty"
]
}
},
"compatibility": {
+3
View File
@@ -788,6 +788,9 @@
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"non_persistent_image_volumes": [
"/romm"
]
}
},
+35 -1
View File
@@ -85,6 +85,22 @@
"backup": true,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/etc/searxng",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -127,7 +143,8 @@
"8080:8080"
],
"volumes": [
"./cache:/var/cache/searxng"
"./cache:/var/cache/searxng",
"./config:/etc/searxng"
]
}
}
@@ -153,6 +170,17 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "searxng-volume-1",
"service": "searxng",
"container_path": "/etc/searxng",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -378,6 +406,12 @@
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/etc/searxng",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
+47 -3
View File
@@ -60,7 +60,24 @@
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/var/lib/tor",
"compose_source_example": "./tor-data",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 9050,
@@ -103,6 +120,9 @@
"ports": [
"9050:9050",
"8118:8118"
],
"volumes": [
"./tor-data:/var/lib/tor"
]
}
}
@@ -119,7 +139,19 @@
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [],
"storage": [
{
"id": "tor-volume-0",
"service": "tor",
"container_path": "/var/lib/tor",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
@@ -323,7 +355,19 @@
"SOCKS5 proxy: configure your application with the LXC IP address shown above and port 9050.",
"HTTP CONNECT proxy: use the same IP address and port 8118. These addresses are proxy settings, not web links.",
"Only applications configured to use this proxy send their traffic through Tor."
]
],
"volume_preparations": [
{
"container_path": "/var/lib/tor",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 101,
"gid": 102
}
}
},
"compatibility": {
+4 -1
View File
@@ -323,7 +323,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/var/log"
]
},
"adaptations": [
{
+47 -3
View File
@@ -77,7 +77,24 @@
"source": "upstream-documentation"
}
],
"volumes": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/config",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 5000,
@@ -117,7 +134,10 @@
"environment": {
"WHOOGLE_USER": "admin",
"WHOOGLE_PASS": "${GENERATED_WHOOGLE_PASS}"
}
},
"volumes": [
"./config:/config"
]
}
}
],
@@ -133,7 +153,19 @@
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [],
"storage": [
{
"id": "whoogle-volume-0",
"service": "whoogle",
"container_path": "/config",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
@@ -378,6 +410,18 @@
"bytes": 24,
"prompt": false
}
},
"volume_preparations": [
{
"container_path": "/config",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 927,
"gid": 927
}
},
"catalog": {
@@ -323,6 +323,9 @@
"AceStream HTTP Proxy has no web interface: a media player opens its streams.",
"HLS stream: port 6878, path /ace/manifest.m3u8?id= followed by the channel ID.",
"MPEG-TS stream: port 6878, path /ace/getstream?id= followed by the channel ID."
],
"non_persistent_image_volumes": [
"/home/appuser/.ACEStream"
]
}
},
+62 -2
View File
@@ -76,6 +76,38 @@
"backup": true,
"default_size_gb": 2
}
},
{
"id": "volume-1",
"container_path": "/etc/bind",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
},
{
"id": "volume-2",
"container_path": "/var/lib/bind",
"compose_source_example": "./lib",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -123,7 +155,9 @@
"53:53/tcp"
],
"volumes": [
"./bind:/var/cache/bind"
"./bind:/var/cache/bind",
"./config:/etc/bind",
"./lib:/var/lib/bind"
]
}
}
@@ -151,6 +185,28 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "bind9-volume-1",
"service": "bind9",
"container_path": "/etc/bind",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "bind9-volume-2",
"service": "bind9",
"container_path": "/var/lib/bind",
"mode": "managed-volume",
"user_selectable": true,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -349,7 +405,11 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {},
"installer_profile": {
"non_persistent_image_volumes": [
"/var/log"
]
},
"catalog": {
"replaces_discovered_ids": []
}
+7 -1
View File
@@ -339,7 +339,13 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {}
"installer_profile": {
"non_persistent_image_volumes": [
"/etc/chrony",
"/run/chrony",
"/var/lib/chrony"
]
}
},
"compatibility": {
"automatic_install_candidate": true,
+4 -1
View File
@@ -409,7 +409,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/gotosocial/.cache"
]
}
},
"compatibility": {
+5 -1
View File
@@ -345,7 +345,11 @@
"validation": "not-requested-by-compose"
}
],
"installer_profile": {},
"installer_profile": {
"non_persistent_image_volumes": [
"/tmp"
]
},
"catalog": {
"replaces_discovered_ids": []
}
+35 -1
View File
@@ -94,6 +94,22 @@
"backup": true,
"default_size_gb": 8
}
},
{
"id": "volume-2",
"container_path": "/etc/nginx/ssl",
"compose_source_example": "./ssl",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -134,7 +150,8 @@
],
"volumes": [
"./data:/var/www/app/data",
"./plugins:/var/www/app/plugins"
"./plugins:/var/www/app/plugins",
"./ssl:/etc/nginx/ssl"
]
}
}
@@ -171,6 +188,17 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "kanboard-volume-2",
"service": "kanboard",
"container_path": "/etc/nginx/ssl",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -391,6 +419,12 @@
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/etc/nginx/ssl",
"remove_lost_found": true,
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
+4 -1
View File
@@ -329,7 +329,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/tmp/jetty"
]
}
},
"compatibility": {
+35 -1
View File
@@ -85,6 +85,22 @@
"backup": true,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/etc/searxng",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
@@ -127,7 +143,8 @@
"8080:8080"
],
"volumes": [
"./cache:/var/cache/searxng"
"./cache:/var/cache/searxng",
"./config:/etc/searxng"
]
}
}
@@ -153,6 +170,17 @@
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "searxng-volume-1",
"service": "searxng",
"container_path": "/etc/searxng",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
@@ -378,6 +406,12 @@
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/etc/searxng",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"startup_healthcheck": {
+47 -3
View File
@@ -60,7 +60,24 @@
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [],
"volumes": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/var/lib/tor",
"compose_source_example": "./tor-data",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 9050,
@@ -103,6 +120,9 @@
"ports": [
"9050:9050",
"8118:8118"
],
"volumes": [
"./tor-data:/var/lib/tor"
]
}
}
@@ -119,7 +139,19 @@
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [],
"storage": [
{
"id": "tor-volume-0",
"service": "tor",
"container_path": "/var/lib/tor",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
@@ -323,7 +355,19 @@
"SOCKS5 proxy: configure your application with the LXC IP address shown above and port 9050.",
"HTTP CONNECT proxy: use the same IP address and port 8118. These addresses are proxy settings, not web links.",
"Only applications configured to use this proxy send their traffic through Tor."
]
],
"volume_preparations": [
{
"container_path": "/var/lib/tor",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 101,
"gid": 102
}
}
},
"compatibility": {
+4 -1
View File
@@ -323,7 +323,10 @@
"request_timeout_seconds": 10,
"stability_seconds": 4,
"verify_tls": true
}
},
"non_persistent_image_volumes": [
"/var/log"
]
},
"adaptations": [
{
+47 -3
View File
@@ -77,7 +77,24 @@
"source": "upstream-documentation"
}
],
"volumes": [],
"volumes": [
{
"id": "volume-0",
"container_path": "/config",
"compose_source_example": "./config",
"read_only": false,
"required": true,
"installation_choice": [
"managed-volume",
"host-bind"
],
"default": "managed-volume",
"managed_volume": {
"backup": true,
"default_size_gb": 1
}
}
],
"ports": [
{
"container_port": 5000,
@@ -117,7 +134,10 @@
"environment": {
"WHOOGLE_USER": "admin",
"WHOOGLE_PASS": "${GENERATED_WHOOGLE_PASS}"
}
},
"volumes": [
"./config:/config"
]
}
}
],
@@ -133,7 +153,19 @@
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [],
"storage": [
{
"id": "whoogle-volume-0",
"service": "whoogle",
"container_path": "/config",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
}
],
"orchestration": {
"reserve_vmids_atomically": 1,
"start_order": [
@@ -378,6 +410,18 @@
"bytes": 24,
"prompt": false
}
},
"volume_preparations": [
{
"container_path": "/config",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
],
"volume_owner": {
"uid": 927,
"gid": 927
}
},
"catalog": {
+3
View File
@@ -99,6 +99,9 @@
"owner_strategy": "mapped-root",
"only_when_mount_type": "managed-volume"
}
],
"non_persistent_image_volumes": [
"/romm"
]
}
}
+24
View File
@@ -19,6 +19,9 @@
"amule": {
"status": "laboratory-validated"
},
"authentik": {
"status": "laboratory-validated"
},
"chromium": {
"status": "laboratory-validated"
},
@@ -62,6 +65,9 @@
"grafana": {
"status": "laboratory-validated"
},
"haos-one": {
"status": "laboratory-validated"
},
"immich": {
"status": "laboratory-validated"
},
@@ -74,6 +80,9 @@
"linkwarden": {
"status": "laboratory-validated"
},
"mariadb": {
"status": "laboratory-validated"
},
"memos": {
"status": "laboratory-validated"
},
@@ -83,6 +92,9 @@
"motioneye": {
"status": "laboratory-validated"
},
"navidrome": {
"status": "laboratory-validated"
},
"nextcloud": {
"status": "laboratory-validated"
},
@@ -108,6 +120,9 @@
"report": "https://github.com/MacRimi/ProxMenux/discussions/392#discussioncomment-18623868"
}
},
"prowlarr": {
"status": "laboratory-validated"
},
"qbittorrent": {
"status": "laboratory-validated"
},
@@ -117,12 +132,18 @@
"rdtclient": {
"status": "laboratory-validated"
},
"scanopy": {
"status": "laboratory-validated"
},
"sharecovex": {
"status": "laboratory-validated"
},
"snapotter": {
"status": "laboratory-validated"
},
"sonarr": {
"status": "laboratory-validated"
},
"tandoor": {
"status": "laboratory-validated"
},
@@ -140,6 +161,9 @@
"trilium": {
"status": "laboratory-validated"
},
"vaultwarden": {
"status": "laboratory-validated"
},
"wg-easy": {
"status": "laboratory-validated"
},
+3 -2
View File
@@ -728,8 +728,9 @@ def preflight(record, candidate, config, coordinated=None, in_place=False, basel
raise ValueError(translate('Required persistent paths cannot be removed'))
image_paths = set(record['observed']['image']['defaults'].get('Volumes') or {})
image_paths -= non_persistent_image_volumes(record['template'])
if any(not any(p == target or p.startswith(target.rstrip('/') + '/') for target in old) for p in image_paths):
raise ValueError(translate('The image declares data paths that are still stored in the rootfs'))
in_rootfs = sorted(p for p in image_paths if not any(p == target or p.startswith(target.rstrip('/') + '/') for target in old))
if in_rootfs:
raise ValueError(f"{translate('The image declares data paths that are still stored in the rootfs')}: {', '.join(in_rootfs)}")
check = effective_healthcheck(candidate['template'])
if not check and not coordinated:
raise ValueError(translate('Updates are not available yet for this application in this beta'))
+9 -3
View File
@@ -219,6 +219,11 @@ def service_template(parent, service, main=False):
own_preparations.append(entry)
if own_preparations:
single['proxmox'].setdefault('installer_profile', {})['volume_preparations'] = own_preparations
# Image paths the oci-compose states hold no data; an image that does not
# declare one of them is not affected by it.
transient = parent.get('proxmox', {}).get('installer_profile', {}).get('non_persistent_image_volumes')
if transient:
single['proxmox'].setdefault('installer_profile', {})['non_persistent_image_volumes'] = list(transient)
return single
@@ -784,10 +789,11 @@ def apply_stack_support(template):
template['status'] = 'generated-review-required'
return
template['proxmox'].pop('generic_stack_review',None)
preserved_preparations = template['proxmox'].get('installer_profile',{}).get('volume_preparations')
profile = template['proxmox'].get('installer_profile',{})
template['proxmox']['installer_profile'] = {'stack_driver':'generic-multi-lxc-stack'}
if preserved_preparations:
template['proxmox']['installer_profile']['volume_preparations'] = preserved_preparations
for key in ('volume_preparations', 'non_persistent_image_volumes'):
if profile.get(key):
template['proxmox']['installer_profile'][key] = profile[key]
template['compatibility']['untranslated_blockers'] = []
template['compatibility']['automatic_install_candidate'] = True
template['status'] = 'generated-unvalidated'
@@ -0,0 +1,30 @@
"""Image paths a stack's oci-compose states hold no data reach the template
of every container of the stack, where updates and Modify read them."""
import copy
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
from proxmenux_oci.stack import apply_stack_support, build_stack, DefaultsUI
class StackTransientImageVolumesTests(unittest.TestCase):
def test_each_container_of_romm_receives_the_transient_paths(self):
result = build_stack(Catalog(ROOT).compose("romm"), DefaultsUI(), mode="simple")
for service in result["services"]:
profile = service["template"]["proxmox"]["installer_profile"]
self.assertEqual(profile.get("non_persistent_image_volumes"), ["/romm"], service["name"])
def test_the_stack_profile_keeps_them_when_it_is_rewritten(self):
template = copy.deepcopy(Catalog(ROOT).compose("romm"))
template["proxmox"]["installer_profile"]["stack_driver"] = "generic-multi-lxc-stack"
apply_stack_support(template)
self.assertEqual(template["proxmox"]["installer_profile"].get("non_persistent_image_volumes"), ["/romm"])
if __name__ == "__main__":
unittest.main()
+3 -1
View File
@@ -164,7 +164,9 @@ class UpdateCheckTests(unittest.TestCase):
check.check(self.root, self.configs, path=path, progress=lambda *event: events.append(event))
native.assert_called_once(); notify.assert_not_called(); external.assert_not_called()
self.assertEqual(events[0], (0, 1)); self.assertEqual(events[-1], (1, 1))
self.assertEqual([r['vmid'] for r in self.status()['available']], [110])
# The bridge stamps its result with the real clock, so read it at the real time too.
available = check.cached(self.root, self.configs, apps=self.apps)['available']
self.assertEqual([r['vmid'] for r in available], [110])
self.assertFalse((self.root / '.update-check.json').exists())
def test_missing_monitor_never_falls_back_to_registry_commands(self):
+54
View File
@@ -0,0 +1,54 @@
"""Every view of a disk takes its rotation and type from disk_identity."""
from pathlib import Path
import sys
import tempfile
import types
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "AppImage/scripts"))
import disk_identity
import audit_inventory
class DiskMediaTypeTests(unittest.TestCase):
def setUp(self):
folder = tempfile.TemporaryDirectory()
self.addCleanup(folder.cleanup)
self.root = Path(folder.name)
for name, flag in (("sda", "1"), ("sdb", "0"), ("nvme0n1", "0")):
(self.root / name / "queue").mkdir(parents=True)
(self.root / name / "queue/rotational").write_text(flag + "\n")
sys_block = patch.object(disk_identity, "_SYS_BLOCK", str(self.root))
sys_block.start()
self.addCleanup(sys_block.stop)
def test_the_kernel_flag_answers_when_smartctl_did_not(self):
self.assertEqual(disk_identity.rotation_rate("sda"), -1)
self.assertEqual(disk_identity.rotation_rate("sdb"), 0)
self.assertEqual(disk_identity.rotation_rate("sdz"), 0)
def test_what_smartctl_reported_wins(self):
self.assertEqual(disk_identity.rotation_rate("sda", 0), 0)
self.assertEqual(disk_identity.rotation_rate("sdb", 5400), 5400)
self.assertEqual(disk_identity.rotation_rate("sda", None), -1)
def test_media_type(self):
self.assertEqual(disk_identity.media_type("sda", -1), "HDD")
self.assertEqual(disk_identity.media_type("sda", 7200), "HDD")
self.assertEqual(disk_identity.media_type("sdb", 0), "SSD")
self.assertEqual(disk_identity.media_type("nvme0n1", 0), "NVMe")
def test_the_inventory_agrees_with_the_monitor(self):
rows = ('NAME="sda" MODEL="WDC WD7500BPVT" SERIAL="WX1" SIZE="750156374016" ROTA="1" TRAN="usb" TYPE="disk"\n'
'NAME="sdb" MODEL="Samsung SSD" SERIAL="S1" SIZE="500107862016" ROTA="0" TRAN="sata" TYPE="disk"\n')
ctx = types.SimpleNamespace(run=lambda *a, **k: (0, rows),
monitor_snapshot={"smart": {"/dev/sda": [0, {"rotation_rate": 0}]}})
with patch.object(audit_inventory, "_disk_observations", return_value={}):
disks = {d["name"]: d["rotational"] for d in audit_inventory._physical_disks(ctx)}
# The USB bridge marks sda rotational; smartctl, through the Monitor, says solid state.
self.assertEqual(disks, {"sda": False, "sdb": False})
if __name__ == "__main__":
unittest.main()
+14 -1
View File
@@ -209,10 +209,16 @@ class QuietDiskIdentityTests(unittest.TestCase):
self.read = Mock(return_value={"model": "WDC WD40EFRX-68N32N0", "serial": "WD-WCC7K4FV37J0",
"rotation_rate": 5400, "health": "healthy", "temperature": 31})
self.cache = {}
self.backoff = {}
import disk_identity
sys_block = patch.object(disk_identity, "_SYS_BLOCK", str(root / "sys/block"))
sys_block.start()
self.addCleanup(sys_block.stop)
namespace = {"Path": lambda path: root / path.lstrip("/"), "time": __import__("time"),
"disk_identity": disk_identity,
"_smart_result_cache": self.cache, "_SMART_RESULT_TTL": 30, "_hdd_in_standby": self.standby,
"_get_smart_data_uncached": self.read, "_smart_data_useful": lambda data: data.get("model") != "Unknown",
"_smart_fail_backoff": {}, "_smart_fail_counts": {}, "_smart_probe_cache": {},
"_smart_fail_backoff": self.backoff, "_smart_fail_counts": {}, "_smart_probe_cache": {},
"_SMART_FAIL_THRESHOLD": 3, "_SMART_FAIL_BACKOFF_SEC": 3600}
tree = ast.parse((SCRIPTS / "flask_server.py").read_text())
nodes = [n for n in tree.body if isinstance(n, ast.FunctionDef)
@@ -240,6 +246,13 @@ class QuietDiskIdentityTests(unittest.TestCase):
self.read.assert_not_called()
self.assertEqual((data["model"], data["rotation_rate"], data["excluded"]), ("WDC WD40EFRX-68N32N0", -1, True))
def test_a_disk_smartctl_keeps_failing_on_keeps_its_identity(self):
self.backoff["sdb"] = __import__("time").time() + 3600
data = self.get("sdb")
self.read.assert_not_called()
self.assertEqual((data["model"], data["serial"], data["rotation_rate"]),
("WDC WD40EFRX-68N32N0", "WD-WCC7K4FV37J0", -1))
if __name__ == "__main__":
unittest.main()
+14 -1
View File
@@ -55,12 +55,15 @@ console.log('PASS: saved-data badge guards and actual provider fallback/translat
const report = source.statements.find(n => ts.isFunctionDeclaration(n) && n.name?.text === 'openSmartReport');
assert.ok(report);
let reportHtml;
const diskTypeModule = {};
new Function('exports', compile(read('AppImage/lib/disk-type.ts')))(diskTypeModule);
const openReport = new Function('window', 'Blob', 'URL', 'setTimeout', 'getDiskTempThresholdsSync', 'getNvmeSmartAttributeKey',
'resolveDiskType',
compile('const APP_VERSION = "fixture";\n' + emit(report, source)) + '\nreturn openSmartReport;')(
{location: {origin: 'http://fixture.invalid'}, open() {}},
class { constructor(parts) { reportHtml = parts.join(''); } },
{createObjectURL() { return 'blob:fixture'; }, revokeObjectURL() {}}, () => {},
() => ({warning: 55, critical: 65}), () => undefined);
() => ({warning: 55, critical: 65}), () => undefined, diskTypeModule.getDiskType);
const expectedReport = {
passedAssessment: 'The supplied SMART status is PASSED. This status alone does not establish that every attribute is within range or that the disk is free of faults. Power-on time shown: {uptime}. Temperature shown: {temperature}. {sectors}',
noReallocatedSectorsReported: 'The supplied reallocated-sector value is zero or unavailable; this does not establish that there are no bad sectors.',
@@ -107,5 +110,15 @@ translated.recommendations = {passedTitle: 'TRANSLATED_REC_TITLE', passedText: '
const localized = renderReport({}, 'passed', 'synthetic', {...catalogs, synthetic: {storage: {smartReport: translated}}});
for (const [, value] of leaves(translated)) assert.ok(localized.includes(value.split(' {')[0]), value);
assert.ok(renderReport({name: 'nvme0n1'}, 'passed', 'en', catalogs, {nvme_raw: {critical_warning: 0, media_errors: 7}}).includes(expectedReport.passedMeaning));
// One classifier for the report and the Storage page: a disk the kernel marks
// rotational without an RPM is an HDD, and a real model wins over "Unknown".
const typeCard = html => html.match(/labels\.type[^]*?card-value[^>]*>([^<]*)</)?.[1] ?? html.match(/>Type<\/div>\s*<div class="card-value"[^>]*>([^<]*)</)?.[1];
assert.equal(typeCard(renderReport({rotation_rate: -1})), 'HDD');
assert.equal(typeCard(renderReport({rotation_rate: 7200})), 'HDD 7200 RPM');
assert.equal(typeCard(renderReport({rotation_rate: 0})), 'SSD');
assert.ok(!renderReport({rotation_rate: -1}).includes('-1 RPM'));
assert.equal(typeCard(renderReport({rotation_rate: -1, connection_type: 'sas'})), 'SAS HDD');
const unknownModel = renderReport({model: 'Unknown', serial: 'Unknown'}, 'passed', 'en', catalogs, {model: 'WDC WD7500BPVT', serial: 'WX11A1234'});
assert.ok(unknownModel.includes('WDC WD7500BPVT') && unknownModel.includes('WX11A1234'));
if (process.env.STORAGE_REPORT_HTML) fs.writeFileSync(process.env.STORAGE_REPORT_HTML, renderReport());
console.log('PASS: actual printable report, passed/failed/unknown/warning, missing/zero/nonzero sectors, NVMe errors and provider fallback');
@@ -1,6 +1,7 @@
import type { Metadata } from "next"
import { getTranslations, getMessages, setRequestLocale } from "next-intl/server"
import { Link } from "@/i18n/navigation"
import { icuQuotesToHtml } from "@/i18n/loadMessages"
import { DocHeader } from "@/components/ui/doc-header"
export async function generateMetadata({
@@ -114,7 +115,7 @@ export default async function GlossaryPage({
</dt>
<dd
className="text-gray-800 leading-relaxed [&_a]:text-blue-600 [&_a]:hover:underline [&_code]:bg-gray-100 [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-sm"
dangerouslySetInnerHTML={{ __html: entry.def }}
dangerouslySetInnerHTML={{ __html: icuQuotesToHtml(entry.def) }}
/>
</div>
))}
@@ -1,6 +1,7 @@
import type { Metadata } from "next"
import { getTranslations, getMessages, setRequestLocale } from "next-intl/server"
import { Link } from "@/i18n/navigation"
import { icuQuotesToHtml } from "@/i18n/loadMessages"
import { DocHeader } from "@/components/ui/doc-header"
import { Callout } from "@/components/ui/callout"
import CopyableCode from "@/components/CopyableCode"
@@ -135,7 +136,7 @@ export default async function SystemLogsTabPage({
className={idx < dataRows.length - 1 ? "border-b border-gray-100" : ""}
>
<td className="px-3 py-2 align-top">{row.subtab}</td>
<td className="px-3 py-2 align-top font-mono text-xs" dangerouslySetInnerHTML={{ __html: row.endpoint }} />
<td className="px-3 py-2 align-top font-mono text-xs" dangerouslySetInnerHTML={{ __html: icuQuotesToHtml(row.endpoint) }} />
<td className="px-3 py-2 align-top">
{t.rich(`dataCollected.rows.${idx}.source`, { code })}
</td>
@@ -615,7 +615,7 @@ running on node pve-01
<tr key={row.stage} className={idx < dispatchRows.length - 1 ? "border-b border-gray-100" : ""}>
<td className="px-3 py-2 align-top whitespace-nowrap"><strong>{row.stage}</strong></td>
<td className="px-3 py-2 align-top">{t.rich(`dispatch.rows.${idx}.what`, { code })}</td>
<td className="px-3 py-2 align-top">{row.tunable}</td>
<td className="px-3 py-2 align-top">{t.rich(`dispatch.rows.${idx}.tunable`, { code })}</td>
</tr>
))}
</tbody>
@@ -214,7 +214,7 @@ was changed │ PHASE 2 — Execute │
<p className="mb-4 text-gray-800 leading-relaxed">{t("format.intro")}</p>
<ol className="list-decimal pl-6 mb-4 text-gray-800 leading-relaxed space-y-2">
{formatItems.map((_, idx) => (
<li key={idx}>{t.rich(`format.items.${idx}`, { code, strong })}</li>
<li key={idx}>{t.rich(`format.items.${idx}`, { code, strong, em })}</li>
))}
</ol>
+36 -2
View File
@@ -5,7 +5,7 @@ import { LocaleHtmlSync } from "@/components/locale-html-sync"
import type React from "react"
import { notFound } from "next/navigation"
import { NextIntlClientProvider, hasLocale } from "next-intl"
import { setRequestLocale } from "next-intl/server"
import { getMessages, setRequestLocale } from "next-intl/server"
import { routing } from "@/i18n/routing"
/**
@@ -122,6 +122,39 @@ export const metadata = {
},
}
// The namespaces client components read. Server pages translate during the
// build, so the rest of the catalog stays out of every page.
const CLIENT_MESSAGES = [
"nav",
"footer",
"hero",
"language",
"resources",
"rssLink",
"supportProject",
"tocPanel",
"docSidebar",
"docNav",
"docs.createVm.synology",
"docs.createVm.systemNas.synology",
]
type Messages = Record<string, unknown>
function pickMessages(messages: Messages, paths: string[]): Messages {
const picked: Messages = {}
for (const path of paths) {
const keys = path.split(".")
let value: unknown = messages
for (const key of keys) value = (value as Messages | undefined)?.[key]
if (value === undefined) continue
let target = picked
for (const key of keys.slice(0, -1)) target = (target[key] ??= {}) as Messages
target[keys[keys.length - 1]] = value
}
return picked
}
export default async function LocaleLayout({
children,
params,
@@ -136,9 +169,10 @@ export default async function LocaleLayout({
}
setRequestLocale(locale)
const messages = (await getMessages({ locale })) as Messages
return (
<NextIntlClientProvider>
<NextIntlClientProvider messages={pickMessages(messages, CLIENT_MESSAGES)}>
{/* LocaleHtmlSync writes `document.documentElement.lang = locale`
after hydration so the active language is reflected in <html lang>
for accessibility tools and SEO crawlers that execute JS. The
+12
View File
@@ -83,6 +83,18 @@
}
/* Long commands and paths in running text wrap instead of widening the page
on a phone; tables scroll inside their own box. */
:not(pre) > code {
overflow-wrap: anywhere;
}
.prose table {
display: block;
overflow-x: auto;
}
button code {
background-color: transparent;
color: inherit;
+1 -5
View File
@@ -275,11 +275,7 @@ export const sidebarItems: MenuItem[] = [
{ title: "Overview", i18nKey: "settingsOverview", href: "/docs/settings" },
{ title: "ProxMenux Monitor", i18nKey: "settingsMonitor", href: "/docs/settings/proxmenux-monitor" },
{ title: "Change Release Channel", i18nKey: "settingsBeta", href: "/docs/settings/beta-program" },
// "Change Language" is intentionally hidden until the translation
// install flow is reactivated. The page file at
// /docs/settings/change-language/page.tsx is preserved so we can
// restore this entry in a single line edit once the feature ships.
// { title: "Change Language", i18nKey: "settingsLanguage", href: "/docs/settings/change-language" },
{ title: "Change Language", i18nKey: "settingsLanguage", href: "/docs/settings/change-language" },
{ title: "Show Version Information", i18nKey: "settingsVersion", href: "/docs/settings/show-version-information" },
{ title: "Uninstall ProxMenux", i18nKey: "settingsUninstall", href: "/docs/settings/uninstall-proxmenux" },
],
+2
View File
@@ -23,6 +23,7 @@ Un agradecimiento especial a [@Vaso73](https://github.com/Vaso73), [@f3rs3n](htt
- **La entrada de Ollama describe lo que ofrece la imagen** ([PR #566](https://github.com/MacRimi/ProxMenux/pull/566), aportado por [@jcastro](https://github.com/jcastro)): su API HTTP en el puerto 11434, con el enlace y la comprobación de arranque en `/api/version`.
- **Una actualización elimina el snapshot que deja un backup interrumpido** ([#565](https://github.com/MacRimi/ProxMenux/issues/565), reportado por [@kiosvantra](https://github.com/kiosvantra)). Un snapshot hecho por el usuario detiene la actualización, porque un contenedor reconstruido no puede conservarlo.
- **Un contenedor que ignora la señal de apagado se detiene a los 60 segundos**, como hace `docker stop`, durante una actualización y al recuperar una operación interrumpida.
- **Modificar y las actualizaciones aceptan toda aplicación cuya imagen declara una ruta de datos**: SearXNG, Tor, Whoogle y BIND9 la guardan en un volumen propio, Kanboard conserva sus certificados, y Chrony, Jaeger, PlantUML, UrBackup, GoToSocial, AceStream HTTP Proxy y RomM marcan las rutas que no guardan datos. Cuando una instalación aún tiene una de esas rutas dentro del contenedor, el mensaje la nombra.
## 🖥️ Monitor — actualizaciones de Proxmox y reinicios pendientes
@@ -59,6 +60,7 @@ Un agradecimiento especial a [@Vaso73](https://github.com/Vaso73), [@f3rs3n](htt
- **Bitwarden reconoce el campo 2FA en el inicio de sesión del Monitor** ([#570](https://github.com/MacRimi/ProxMenux/issues/570), reportado por [@shtefko](https://github.com/shtefko)): la pista del código de backup mantiene su texto y su lugar, fuera del grupo del campo.
- **Secure Gateway se instala en hosts Proxmox sobre BTRFS** ([#564](https://github.com/MacRimi/ProxMenux/issues/564), reportado por [@78v5](https://github.com/78v5)): la plantilla de Alpine va a un almacenamiento activo que admite plantillas de contenedor.
- **Un error de E/S de disco del kernel se registra en su disco**, con su número de serie, también cuando el error nombra una partición.
- **Todas las vistas de un disco muestran el mismo tipo**: la página de almacenamiento, el detalle del disco, el informe SMART y el inventario de Audit & Report lo toman de una sola fuente. Un disco duro que smartctl no consigue leer conserva su tipo, modelo y número de serie, y un disco duro que no informa de su velocidad aparece como HDD.
- **El Monitor se instala como aplicación (PWA) desde el navegador**: sirve su propio manifiesto de aplicación web y su service worker, con los iconos de 192 y 512 px que exige Chrome.
- **Los títulos del eje Y se mantienen dentro de los gráficos de la vista general** ([PR #539](https://github.com/MacRimi/ProxMenux/pull/539), aportado por [@f3rs3n](https://github.com/f3rs3n)).
- **El catálogo de seguimiento de versiones de aplicaciones se genera desde los repositorios actuales de Helper-Scripts**.
+20 -1
View File
@@ -45,7 +45,7 @@ function readDir(dir: string): Record<string, unknown> {
if (!entry.name.endsWith(".json")) continue
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf-8"))
const parsed = decodeEntities(JSON.parse(fs.readFileSync(fullPath, "utf-8")))
const base = entry.name.replace(/\.json$/, "")
if (base === "common" || base === "index") {
@@ -59,6 +59,25 @@ function readDir(dir: string): Record<string, unknown> {
return out
}
// Catalogs write "&lt;x&gt;" so the ICU parser does not read "<x>" as a rich
// tag, but React renders text, not HTML. ICU quoting ('<') keeps the parser
// off and renders the literal character.
function decodeEntities(value: unknown): unknown {
if (typeof value === "string") {
return value.replace(/&lt;/g, "'<'").replace(/&gt;/g, "'>'").replace(/&amp;/g, "&")
}
if (Array.isArray(value)) return value.map(decodeEntities)
if (value && typeof value === "object") {
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, decodeEntities(v)]))
}
return value
}
// Messages inserted as raw HTML skip ICU, so its quoting goes back to entities.
export function icuQuotesToHtml(html: string): string {
return html.replace(/'<'/g, "&lt;").replace(/'>'/g, "&gt;")
}
function toCamel(name: string): string {
return name.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase())
}
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Übersicht",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Release-Kanal wechseln",
"settingsLanguage": "Sprache ändern",
"settingsVersion": "Versionsinformationen anzeigen",
"settingsUninstall": "ProxMenux deinstallieren",
"commandsReference": "Befehlsreferenz",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "Der <code>[proxmenux]</code> jail liest <code>/var/log/proxmenux-auth.log</code>, 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",
"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 <code>/etc/apt/sources.list.d/debian.sources</code> mit dem richtigen Codenamen (<code>bookworm</code> / <code>trixie</code>), bevor er es erneut versucht. Wenn es immer noch fehlschlägt, überprüfen Sie <code>/etc/os-release</code> für <code>VERSION_CODENAME</code> und bestätigen Sie, dass die Repo-URL erreichbar ist.",
"lockoutTitle": "SSH sperrt mich nach der Installation aus",
@@ -95,7 +95,7 @@
},
"flowHeading": "Ablauf im automatischen Modus",
"flowIntro": "Sechs aufeinanderfolgende Phasen. Phase 2 aktualisiert die PVE-8-Pakete, Phase 3 schreibt die Repository-Konfiguration für PVE 9. Ein Fehler hält den Ablauf an, macht diese Änderungen aber nicht rückgängig; schlägt das PVE-8-Update fehl oder wird es abgebrochen, bleiben die Repositories unverändert. Phase 5 führt <code>dist-upgrade</code> auf PVE 9 aus.",
"flowDiagram": "1. PRÜFUNGEN\ntermproxy / vncshell blockieren; Upgrade bestätigen; PVE 9 ablehnen.\n\n2. PVE 8 VORBEREITEN\nscripts/global/update-pve8.sh\nPVE-8-Pakete aktualisieren; Abbruch, wenn das Update fehlschlägt oder abgebrochen wird.\npve8to9 --full (kann Reparaturen anbieten).\nSpeicher und Verbindung prüfen; Ceph 19.x Squid auf hyperkonvergenten Knoten.\n\n3. REPOSITORIES UMSTELLEN\nBookworm → Trixie; deb822-Dateien .sources schreiben.\nAlte .list-Dateien auskommentieren; apt-get update.\n\n4. PVE-9-KANDIDAT PRÜFEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nAbbruch, wenn der Kandidat nicht 9.x ist oder proxmox-ve entfernt würde.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nOhne Subskription: pve-enterprise.sources deaktivieren.\nProtokoll: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. PRÜFEN UND NEU STARTEN\nEFI: apt install grub-efi-amd64\npve8to9 --full; Ergebnis und Neustart-Rückfrage prüfen.",
"flowDiagram": "1. PRÜFUNGEN\ntermproxy / vncshell blockieren; Upgrade bestätigen; PVE 9 ablehnen.\n\n2. PVE 8 VORBEREITEN\nscripts/global/update-pve8.sh\nPVE-8-Pakete aktualisieren; Abbruch, wenn das Update fehlschlägt oder abgebrochen wird.\npve8to9 --full (kann Reparaturen anbieten).\nSpeicher und Verbindung prüfen; Ceph 19.x Squid auf hyperkonvergenten Knoten.\n\n3. REPOSITORIES UMSTELLEN\nBookworm → Trixie; deb822-Dateien .sources schreiben.\nAlte .list-Dateien auskommentieren; apt-get update.\n\n4. PVE-9-KANDIDAT PRÜFEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nAbbruch, wenn der Kandidat nicht 9.x ist oder proxmox-ve entfernt würde.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nOhne Subskription: pve-enterprise.sources deaktivieren.\nProtokoll: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. PRÜFEN UND NEU STARTEN\nEFI: apt install grub-efi-amd64\npve8to9 --full; Ergebnis und Neustart-Rückfrage prüfen.",
"preflightHeading": "Vor dem Upgrade: diese Punkte werden geprüft",
"preflightTable": {
"checkHeader": "Prüfung",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Beteiligte Dateien",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Offizielle Quellen",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Overview",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Change Release Channel",
"settingsLanguage": "Change Language",
"settingsVersion": "Show Version Information",
"settingsUninstall": "Uninstall ProxMenux",
"commandsReference": "Commands Reference",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "The <code>[proxmenux]</code> jail reads <code>/var/log/proxmenux-auth.log</code>, 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",
"selfBanIntro": "From a console / IPMI / iKVM (or another whitelisted 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",
"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 fails: ''Unable to locate package fail2ban''",
"aptFailBody": "The host is missing the Debian repos (common on barebones Proxmox installs). The installer detects this and writes <code>/etc/apt/sources.list.d/debian.sources</code> with the right codename (<code>bookworm</code> / <code>trixie</code>) before retrying. If it still fails, check <code>/etc/os-release</code> for <code>VERSION_CODENAME</code> and confirm the repo URL is reachable.",
"lockoutTitle": "SSH locks me out after install",
@@ -95,7 +95,7 @@
},
"flowHeading": "Auto-mode flow",
"flowIntro": "Six sequential phases. Phase 2 updates the PVE 8 packages and phase 3 writes the PVE 9 repository configuration. A failure stops the flow without rolling those changes back; if the PVE 8 update fails or is cancelled, the repositories are left unchanged. Phase 5 runs the PVE 9 <code>dist-upgrade</code>.",
"flowDiagram": "1. CHECKS\nBlock termproxy / vncshell; confirm the upgrade; reject PVE 9.\n\n2. PREPARE PVE 8\nscripts/global/update-pve8.sh\nUpdate PVE 8 packages; stop if the update fails or is cancelled.\npve8to9 --full (may offer repairs).\nCheck space and connectivity; Ceph 19.x Squid on hyper-converged nodes.\n\n3. MIGRATE REPOSITORIES\nBookworm → Trixie; write deb822 .sources files.\nComment legacy .list files; apt-get update.\n\n4. VALIDATE THE PVE 9 CANDIDATE\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStop if the candidate is not 9.x or proxmox-ve would be removed.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nNo subscription: disable pve-enterprise.sources.\nLog: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. VERIFY AND REBOOT\nEFI: apt install grub-efi-amd64\npve8to9 --full; review results and the reboot prompt.",
"flowDiagram": "1. CHECKS\nBlock termproxy / vncshell; confirm the upgrade; reject PVE 9.\n\n2. PREPARE PVE 8\nscripts/global/update-pve8.sh\nUpdate PVE 8 packages; stop if the update fails or is cancelled.\npve8to9 --full (may offer repairs).\nCheck space and connectivity; Ceph 19.x Squid on hyper-converged nodes.\n\n3. MIGRATE REPOSITORIES\nBookworm → Trixie; write deb822 .sources files.\nComment legacy .list files; apt-get update.\n\n4. VALIDATE THE PVE 9 CANDIDATE\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStop if the candidate is not 9.x or proxmox-ve would be removed.\n\n5. UPGRADE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nNo subscription: disable pve-enterprise.sources.\nLog: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. VERIFY AND REBOOT\nEFI: apt install grub-efi-amd64\npve8to9 --full; review results and the reboot prompt.",
"preflightHeading": "Pre-flight: what gets checked",
"preflightTable": {
"checkHeader": "Check",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Files involved",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Official references",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Resumen",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Cambiar canal de release",
"settingsLanguage": "Cambiar idioma",
"settingsVersion": "Mostrar información de versión",
"settingsUninstall": "Desinstalar ProxMenux",
"commandsReference": "Referencia de comandos",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "El jail <code>[proxmenux]</code> lee <code>/var/log/proxmenux-auth.log</code>, 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",
"selfBanIntro": "Desde una consola / IPMI / iKVM (u otra IP whitelisted):",
"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",
"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 falla: ''Unable to locate package fail2ban''",
"aptFailBody": "Al host le faltan los repos de Debian (común en instalaciones de Proxmox a pelo). El instalador detecta esto y escribe <code>/etc/apt/sources.list.d/debian.sources</code> con el codename correcto (<code>bookworm</code> / <code>trixie</code>) antes de reintentarlo. Si sigue fallando, comprueba <code>/etc/os-release</code> en busca de <code>VERSION_CODENAME</code> y confirma que la URL del repo es alcanzable.",
"lockoutTitle": "SSH me bloquea el acceso tras la instalación",
@@ -95,7 +95,7 @@
},
"flowHeading": "Flujo del modo automático",
"flowIntro": "Seis fases consecutivas. La fase 2 actualiza los paquetes de PVE 8 y la fase 3 escribe la configuración de repositorios de PVE 9. Un fallo detiene el proceso sin revertir esos cambios; si la actualización de PVE 8 falla o se cancela, los repositorios no se modifican. La fase 5 ejecuta <code>dist-upgrade</code> para PVE 9.",
"flowDiagram": "1. COMPROBACIONES\nBloquea termproxy / vncshell; confirma la actualización; rechaza PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nActualiza los paquetes de PVE 8; se detiene si la actualización falla o se cancela.\npve8to9 --full (puede ofrecer reparaciones).\nComprueba espacio y conectividad; Ceph 19.x Squid en nodos hiperconvergentes.\n\n3. MIGRAR REPOSITORIOS\nBookworm → Trixie; escribe archivos deb822 .sources.\nComenta los archivos .list antiguos; apt-get update.\n\n4. VALIDAR EL CANDIDATO DE PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSe detiene si el candidato no es 9.x o se eliminaría proxmox-ve.\n\n5. ACTUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSin suscripción: desactiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. VERIFICAR Y REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; revisa el resultado y la pregunta de reinicio.",
"flowDiagram": "1. COMPROBACIONES\nBloquea termproxy / vncshell; confirma la actualización; rechaza PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nActualiza los paquetes de PVE 8; se detiene si la actualización falla o se cancela.\npve8to9 --full (puede ofrecer reparaciones).\nComprueba espacio y conectividad; Ceph 19.x Squid en nodos hiperconvergentes.\n\n3. MIGRAR REPOSITORIOS\nBookworm → Trixie; escribe archivos deb822 .sources.\nComenta los archivos .list antiguos; apt-get update.\n\n4. VALIDAR EL CANDIDATO DE PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSe detiene si el candidato no es 9.x o se eliminaría proxmox-ve.\n\n5. ACTUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSin suscripción: desactiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. VERIFICAR Y REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; revisa el resultado y la pregunta de reinicio.",
"preflightHeading": "Comprobaciones previas: qué se chequea",
"preflightTable": {
"checkHeader": "Chequeo",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Archivos implicados",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Referencias oficiales",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Vue d’ensemble",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Changer de canal de publication",
"settingsLanguage": "Changer de langue",
"settingsVersion": "Afficher les informations de version",
"settingsUninstall": "Désinstaller ProxMenux",
"commandsReference": "Référence des commandes",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "Le <code>[proxmenux]</code> jail lit <code>/var/log/proxmenux-auth.log</code>, 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",
"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 <code>/etc/apt/sources.list.d/debian.sources</code> avec le bon nom de code (<code>bookworm</code> / <code>trixie</code>), puis réessaie. Si cela échoue encore, vérifiez <code>VERSION_CODENAME</code> dans <code>/etc/os-release</code> et l'accessibilité du dépôt.",
"lockoutTitle": "SSH me verrouille après l'installation",
@@ -95,7 +95,7 @@
},
"flowHeading": "Déroulement du mode automatique",
"flowIntro": "Six phases successives. La phase 2 met à jour les paquets PVE 8 et la phase 3 écrit la configuration des dépôts PVE 9. Un échec arrête le déroulement sans annuler ces modifications ; si la mise à jour PVE 8 échoue ou est annulée, les dépôts restent inchangés. La phase 5 lance <code>dist-upgrade</code> vers PVE 9.",
"flowDiagram": "1. CONTRÔLES\nBloque termproxy / vncshell ; confirme la mise à niveau ; refuse PVE 9.\n\n2. PRÉPARER PVE 8\nscripts/global/update-pve8.sh\nMet à jour les paquets PVE 8 ; arrêt si la mise à jour échoue ou est annulée.\npve8to9 --full (peut proposer des réparations).\nVérifie espace et connectivité ; Ceph 19.x Squid sur les nœuds hyperconvergés.\n\n3. MIGRER LES DÉPÔTS\nBookworm → Trixie ; écrit les fichiers deb822 .sources.\nCommente les anciens fichiers .list ; apt-get update.\n\n4. VALIDER LE CANDIDAT PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nArrêt si le candidat n’est pas 9.x ou si proxmox-ve serait supprimé.\n\n5. METTRE À NIVEAU\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSans abonnement : désactive pve-enterprise.sources.\nJournal : /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. VÉRIFIER ET REDÉMARRER\nEFI : apt install grub-efi-amd64\npve8to9 --full ; examiner le résultat et la demande de redémarrage.",
"flowDiagram": "1. CONTRÔLES\nBloque termproxy / vncshell ; confirme la mise à niveau ; refuse PVE 9.\n\n2. PRÉPARER PVE 8\nscripts/global/update-pve8.sh\nMet à jour les paquets PVE 8 ; arrêt si la mise à jour échoue ou est annulée.\npve8to9 --full (peut proposer des réparations).\nVérifie espace et connectivité ; Ceph 19.x Squid sur les nœuds hyperconvergés.\n\n3. MIGRER LES DÉPÔTS\nBookworm → Trixie ; écrit les fichiers deb822 .sources.\nCommente les anciens fichiers .list ; apt-get update.\n\n4. VALIDER LE CANDIDAT PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nArrêt si le candidat n’est pas 9.x ou si proxmox-ve serait supprimé.\n\n5. METTRE À NIVEAU\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSans abonnement : désactive pve-enterprise.sources.\nJournal : /var/log/pve8-a-pve9-<timestamp>.log\n\n6. VÉRIFIER ET REDÉMARRER\nEFI : apt install grub-efi-amd64\npve8to9 --full ; examiner le résultat et la demande de redémarrage.",
"preflightHeading": "Contrôles avant la mise à niveau",
"preflightTable": {
"checkHeader": "Vérifier",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Fichiers concernés",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Références officielles",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Panoramica",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Cambia canale di rilascio",
"settingsLanguage": "Cambia lingua",
"settingsVersion": "Mostra informazioni sulla versione",
"settingsUninstall": "Disinstalla ProxMenux",
"commandsReference": "Riferimento dei comandi",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "Il <code>[proxmenux]</code> jail legge <code>/var/log/proxmenux-auth.log</code>, 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",
"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 <code>/etc/apt/sources.list.d/debian.sources</code> con il nome giusto in codice (<code>bookworm</code> / <code>trixie</code>) prima di riprovare. Se non riesce ancora, controllare <code>/etc/os-release</code> per <code>VERSION_CODENAME</code> e confermare che l'URL repo è raggiungibile.",
"lockoutTitle": "SSH mi blocca dopo l'installazione",
@@ -95,7 +95,7 @@
},
"flowHeading": "Percorso della modalità automatica",
"flowIntro": "Sei fasi consecutive. La fase 2 aggiorna i pacchetti PVE 8 e la fase 3 scrive la configurazione dei repository di PVE 9. Un errore interrompe il percorso senza annullare quelle modifiche; se l’aggiornamento di PVE 8 non riesce o viene annullato, i repository restano invariati. La fase 5 esegue <code>dist-upgrade</code> a PVE 9.",
"flowDiagram": "1. CONTROLLI\nBlocca termproxy / vncshell; conferma l’aggiornamento; rifiuta PVE 9.\n\n2. PREPARARE PVE 8\nscripts/global/update-pve8.sh\nAggiorna i pacchetti PVE 8; si ferma se l’aggiornamento non riesce o viene annullato.\npve8to9 --full (può proporre riparazioni).\nControlla spazio e connettività; Ceph 19.x Squid sui nodi iperconvergenti.\n\n3. MIGRARE I REPOSITORY\nBookworm → Trixie; scrive file deb822 .sources.\nCommenta i vecchi file .list; apt-get update.\n\n4. VERIFICARE IL CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSi ferma se il candidato non è 9.x o se proxmox-ve verrebbe rimosso.\n\n5. AGGIORNARE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSenza abbonamento: disattiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. VERIFICARE E RIAVVIARE\nEFI: apt install grub-efi-amd64\npve8to9 --full; esaminare risultati e richiesta di riavvio.",
"flowDiagram": "1. CONTROLLI\nBlocca termproxy / vncshell; conferma l’aggiornamento; rifiuta PVE 9.\n\n2. PREPARARE PVE 8\nscripts/global/update-pve8.sh\nAggiorna i pacchetti PVE 8; si ferma se l’aggiornamento non riesce o viene annullato.\npve8to9 --full (può proporre riparazioni).\nControlla spazio e connettività; Ceph 19.x Squid sui nodi iperconvergenti.\n\n3. MIGRARE I REPOSITORY\nBookworm → Trixie; scrive file deb822 .sources.\nCommenta i vecchi file .list; apt-get update.\n\n4. VERIFICARE IL CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nSi ferma se il candidato non è 9.x o se proxmox-ve verrebbe rimosso.\n\n5. AGGIORNARE\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSenza abbonamento: disattiva pve-enterprise.sources.\nRegistro: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. VERIFICARE E RIAVVIARE\nEFI: apt install grub-efi-amd64\npve8to9 --full; esaminare risultati e richiesta di riavvio.",
"preflightHeading": "Controlli prima dell’aggiornamento",
"preflightTable": {
"checkHeader": "Controllo",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "File coinvolti",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Riferimenti ufficiali",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Visão geral",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Alterar o canal de versões",
"settingsLanguage": "Alterar idioma",
"settingsVersion": "Mostrar informações da versão",
"settingsUninstall": "Desinstalar o ProxMenux",
"commandsReference": "Referência de comandos",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "O <code>[proxmenux]</code> jail lê <code>/var/log/proxmenux-auth.log</code>, 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",
"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 <code>/etc/apt/sources.list.d/debian.sources</code> com o codinome certo (<code>bookworm</code> / <code>trixie</code>) antes de tentar novamente. Se ainda falhar, verifique <code>/etc/os-release</code> para <code>VERSION_CODENAME</code> e confirme que o URL do repo está acessível.",
"lockoutTitle": "SSH me bloqueia depois de instalar",
@@ -95,7 +95,7 @@
},
"flowHeading": "Processo do modo automático",
"flowIntro": "Seis fases consecutivas. A fase 2 atualiza os pacotes PVE 8 e a fase 3 escreve a configuração dos repositórios do PVE 9. Uma falha interrompe o processo sem reverter essas alterações; se a atualização do PVE 8 falhar ou for cancelada, os repositórios ficam inalterados. A fase 5 executa <code>dist-upgrade</code> para PVE 9.",
"flowDiagram": "1. VERIFICAÇÕES\nBloqueia termproxy / vncshell; confirma a atualização; rejeita PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nAtualiza os pacotes PVE 8; para se a atualização falhar ou for cancelada.\npve8to9 --full (pode propor reparações).\nVerifica espaço e conectividade; Ceph 19.x Squid em nós hiperconvergentes.\n\n3. MIGRAR REPOSITÓRIOS\nBookworm → Trixie; escreve ficheiros deb822 .sources.\nComenta os ficheiros .list antigos; apt-get update.\n\n4. VALIDAR O CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nPara se o candidato não for 9.x ou se proxmox-ve fosse removido.\n\n5. ATUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSem subscrição: desativa pve-enterprise.sources.\nRegisto: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. VERIFICAR E REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; examine os resultados e a pergunta de reinício.",
"flowDiagram": "1. VERIFICAÇÕES\nBloqueia termproxy / vncshell; confirma a atualização; rejeita PVE 9.\n\n2. PREPARAR PVE 8\nscripts/global/update-pve8.sh\nAtualiza os pacotes PVE 8; para se a atualização falhar ou for cancelada.\npve8to9 --full (pode propor reparações).\nVerifica espaço e conectividade; Ceph 19.x Squid em nós hiperconvergentes.\n\n3. MIGRAR REPOSITÓRIOS\nBookworm → Trixie; escreve ficheiros deb822 .sources.\nComenta os ficheiros .list antigos; apt-get update.\n\n4. VALIDAR O CANDIDATO PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nPara se o candidato não for 9.x ou se proxmox-ve fosse removido.\n\n5. ATUALIZAR\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nSem subscrição: desativa pve-enterprise.sources.\nRegisto: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. VERIFICAR E REINICIAR\nEFI: apt install grub-efi-amd64\npve8to9 --full; examine os resultados e a pergunta de reinício.",
"preflightHeading": "Verificações antes da atualização",
"preflightTable": {
"checkHeader": "Verificar",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Ficheiros envolvidos",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Referências oficiais",
+1
View File
@@ -166,6 +166,7 @@
"settingsOverview": "Prehľad",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Zmeniť kanál vydaní",
"settingsLanguage": "Zmeniť jazyk",
"settingsVersion": "Zobraziť informácie o verzii",
"settingsUninstall": "Odinštalovať ProxMenux",
"commandsReference": "Prehľad príkazov",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "<code>[proxmenux]</code> jail číta <code>/var/log/proxmenux-auth.log</code>, 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",
"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í <code>/etc/apt/sources.list.d/debian.sources</code> so správnym kódovým označením (<code>bookworm</code> / <code>trixie</code>). Ak chyba pretrváva, skontrolujte <code>VERSION_CODENAME</code> v <code>/etc/os-release</code> a dostupnosť URL repozitára.",
"lockoutTitle": "SSH ma po inštalácii zamkne",
@@ -95,7 +95,7 @@
},
"flowHeading": "Postup automatického režimu",
"flowIntro": "Šesť po sebe idúcich fáz. Fáza 2 aktualizuje balíky PVE 8 a fáza 3 zapíše konfiguráciu repozitárov PVE 9. Chyba postup zastaví, tieto zmeny však nevráti; ak aktualizácia PVE 8 zlyhá alebo sa zruší, repozitáre zostanú nezmenené. Fáza 5 vykoná <code>dist-upgrade</code> na PVE 9.",
"flowDiagram": "1. KONTROLY\nBlokuje termproxy / vncshell; potvrdí aktualizáciu; odmietne PVE 9.\n\n2. PRÍPRAVA PVE 8\nscripts/global/update-pve8.sh\nAktualizuje balíky PVE 8; zastaví sa, ak aktualizácia zlyhá alebo sa zruší.\npve8to9 --full (môže ponúknuť opravy).\nOverí miesto a pripojenie; Ceph 19.x Squid na hyperkonvergovaných uzloch.\n\n3. MIGRÁCIA REPOZITÁROV\nBookworm → Trixie; zapíše súbory deb822 .sources.\nZakomentuje staré súbory .list; apt-get update.\n\n4. OVERENIE KANDIDÁTA PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nZastaví sa, ak kandidát nie je 9.x alebo by sa odstránil proxmox-ve.\n\n5. AKTUALIZÁCIA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nBez predplatného: vypne pve-enterprise.sources.\nProtokol: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. OVERENIE A REŠTART\nEFI: apt install grub-efi-amd64\npve8to9 --full; skontrolujte výsledok a otázku na reštart.",
"flowDiagram": "1. KONTROLY\nBlokuje termproxy / vncshell; potvrdí aktualizáciu; odmietne PVE 9.\n\n2. PRÍPRAVA PVE 8\nscripts/global/update-pve8.sh\nAktualizuje balíky PVE 8; zastaví sa, ak aktualizácia zlyhá alebo sa zruší.\npve8to9 --full (môže ponúknuť opravy).\nOverí miesto a pripojenie; Ceph 19.x Squid na hyperkonvergovaných uzloch.\n\n3. MIGRÁCIA REPOZITÁROV\nBookworm → Trixie; zapíše súbory deb822 .sources.\nZakomentuje staré súbory .list; apt-get update.\n\n4. OVERENIE KANDIDÁTA PVE 9\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nZastaví sa, ak kandidát nie je 9.x alebo by sa odstránil proxmox-ve.\n\n5. AKTUALIZÁCIA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nBez predplatného: vypne pve-enterprise.sources.\nProtokol: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. OVERENIE A REŠTART\nEFI: apt install grub-efi-amd64\npve8to9 --full; skontrolujte výsledok a otázku na reštart.",
"preflightHeading": "Kontroly pred aktualizáciou",
"preflightTable": {
"checkHeader": "Kontrola",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Príslušné súbory",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Oficiálne zdroje",
+1
View File
@@ -174,6 +174,7 @@
"settingsOverview": "Översikt",
"settingsMonitor": "ProxMenux Monitor",
"settingsBeta": "Byt versionskanal",
"settingsLanguage": "Byt språk",
"settingsVersion": "Visa versionsinformation",
"settingsUninstall": "Avinstallera ProxMenux",
"commandsReference": "Kommandoreferens",
+1 -1
View File
@@ -130,7 +130,7 @@
"monitorEmptyBody": "Jailen <code>[proxmenux]</code> läser <code>/var/log/proxmenux-auth.log</code>, 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",
"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 <code>/etc/apt/sources.list.d/debian.sources</code> med rätt kodnamn (<code>bookworm</code> / <code>trixie</code>) innan du går tillbaka. Om det fortfarande misslyckas, kontrollera <code>/etc/os-release</code> för <code>VERSION_CODENAME</code> och bekräfta att repo URL är tillgänglig.",
"lockoutTitle": "SSH låser mig efter installation",
@@ -95,7 +95,7 @@
},
"flowHeading": "Förlopp i automatiskt läge",
"flowIntro": "Sex faser i följd. Fas 2 uppdaterar PVE 8-paketen och fas 3 skriver konfigurationen för PVE 9-paketkällorna. Ett fel stoppar förloppet men återställer inte ändringarna; om PVE 8-uppdateringen misslyckas eller avbryts lämnas paketkällorna orörda. Fas 5 kör <code>dist-upgrade</code> till PVE 9.",
"flowDiagram": "1. KONTROLLER\nBlockera termproxy / vncshell; bekräfta uppgraderingen; avvisa PVE 9.\n\n2. FÖRBERED PVE 8\nscripts/global/update-pve8.sh\nUppdatera PVE 8-paket; stoppa om uppdateringen misslyckas eller avbryts.\npve8to9 --full (kan erbjuda reparationer).\nKontrollera utrymme och anslutning; Ceph 19.x Squid på hyperkonvergerade noder.\n\n3. ÄNDRA PAKETKÄLLOR\nBookworm → Trixie; skriv deb822-filer .sources.\nKommentera bort gamla .list-filer; apt-get update.\n\n4. KONTROLLERA PVE 9-KANDIDATEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStoppa om kandidaten inte är 9.x eller om proxmox-ve skulle tas bort.\n\n5. UPPGRADERA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nUtan prenumeration: inaktivera pve-enterprise.sources.\nLogg: /var/log/pve8-a-pve9-'<'timestamp'>'.log\n\n6. KONTROLLERA OCH STARTA OM\nEFI: apt install grub-efi-amd64\npve8to9 --full; granska resultatet och frågan om omstart.",
"flowDiagram": "1. KONTROLLER\nBlockera termproxy / vncshell; bekräfta uppgraderingen; avvisa PVE 9.\n\n2. FÖRBERED PVE 8\nscripts/global/update-pve8.sh\nUppdatera PVE 8-paket; stoppa om uppdateringen misslyckas eller avbryts.\npve8to9 --full (kan erbjuda reparationer).\nKontrollera utrymme och anslutning; Ceph 19.x Squid på hyperkonvergerade noder.\n\n3. ÄNDRA PAKETKÄLLOR\nBookworm → Trixie; skriv deb822-filer .sources.\nKommentera bort gamla .list-filer; apt-get update.\n\n4. KONTROLLERA PVE 9-KANDIDATEN\napt-cache policy proxmox-ve\napt-get -s dist-upgrade\nStoppa om kandidaten inte är 9.x eller om proxmox-ve skulle tas bort.\n\n5. UPPGRADERA\napt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade\nUtan prenumeration: inaktivera pve-enterprise.sources.\nLogg: /var/log/pve8-a-pve9-<timestamp>.log\n\n6. KONTROLLERA OCH STARTA OM\nEFI: apt install grub-efi-amd64\npve8to9 --full; granska resultatet och frågan om omstart.",
"preflightHeading": "Förkontroll: vad som kontrolleras",
"preflightTable": {
"checkHeader": "Kontroll",
@@ -273,7 +273,7 @@
},
"files": {
"heading": "Filer involverade",
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-'<'timestamp'>'.log # full upgrade log"
"code": "scripts/utilities/upgrade_pve8_to_pve9.sh # main upgrade orchestrator\nscripts/utilities/pve8to9_check.sh # pre-check (separate menu option)\nscripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh # 17-step runbook\nscripts/global/update-pve8.sh # PVE 8 worker (final patches)\n/etc/apt/sources.list # bookworm → trixie sed\n/etc/apt/sources.list.d/proxmox.sources # created (deb822 PVE 9)\n/etc/apt/sources.list.d/debian.sources # created (deb822 trixie)\n/etc/apt/sources.list.d/ceph.sources # created if Ceph\n/etc/apt/sources.list.d/*.list # legacy files commented out / removed\n/var/log/pve8-a-pve9-<timestamp>.log # full upgrade log"
},
"references": {
"heading": "Officiella referenser",
+1402 -2292
View File
File diff suppressed because it is too large Load Diff
+50 -60
View File
@@ -10,75 +10,65 @@
"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",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-accordion": "^1.3.1",
"@radix-ui/react-alert-dialog": "^1.2.1",
"@radix-ui/react-aspect-ratio": "^1.2.0",
"@radix-ui/react-avatar": "^1.3.1",
"@radix-ui/react-checkbox": "^1.4.1",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-context-menu": "^2.4.1",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-menubar": "^1.1.4",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "latest",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-dropdown-menu": "^2.2.1",
"@radix-ui/react-hover-card": "^1.2.1",
"@radix-ui/react-label": "^2.1.17",
"@radix-ui/react-menubar": "^1.2.1",
"@radix-ui/react-navigation-menu": "^1.4.1",
"@radix-ui/react-popover": "^1.3.1",
"@radix-ui/react-progress": "^1.2.1",
"@radix-ui/react-radio-group": "^1.5.1",
"@radix-ui/react-scroll-area": "^1.4.0",
"@radix-ui/react-select": "^2.4.1",
"@radix-ui/react-separator": "^1.2.0",
"@radix-ui/react-slider": "^1.6.1",
"@radix-ui/react-slot": "^1.5.0",
"@radix-ui/react-switch": "^1.4.1",
"@radix-ui/react-tabs": "^1.2.1",
"@radix-ui/react-toast": "^1.3.1",
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"autoprefixer": "^10.4.20",
"@radix-ui/react-toggle-group": "^1.2.1",
"@radix-ui/react-tooltip": "^1.4.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "^2.30.0",
"embla-carousel-react": "8.5.1",
"fs": "latest",
"gray-matter": "^4.0.3",
"html-react-parser": "^3.0.0",
"input-otp": "1.4.1",
"cmdk": "1.1.1",
"embla-carousel-react": "8.6.0",
"html-react-parser": "^3.0.16",
"input-otp": "1.5.0",
"lucide-react": "^0.454.0",
"mermaid": "^11.14.0",
"next": "15.5.15",
"next-intl": "^4.12.0",
"next-themes": "^0.4.4",
"path": "latest",
"react": "^19",
"react-dom": "^19",
"react-hook-form": "^7.54.1",
"react-markdown": "^9.0.0",
"react-resizable-panels": "^2.1.7",
"mermaid": "^11.17.2",
"next": "15.5.28",
"next-intl": "^4.14.9",
"next-themes": "^0.4.6",
"react": "^19.3.1",
"react-dom": "^19.3.1",
"react-hook-form": "^7.91.0",
"react-resizable-panels": "^2.1.9",
"recharts": "2.15.0",
"remark": "latest",
"remark-breaks": "3.0.0",
"remark-gfm": "^4.0.0",
"remark-html": "latest",
"shiki": "^4.0.2",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.0",
"zod": "^3.24.1"
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"remark-html": "^16.0.1",
"shiki": "^4.5.0",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.1",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"@tailwindcss/typography": "^0.5.20",
"@types/node": "^22.20.5",
"@types/react": "^18.3.31",
"@types/react-dom": "^18.3.7",
"pagefind": "^1.5.2",
"postcss": "^8",
"tailwindcss": "^3.4.17",
"typescript": "^5"
"postcss": "^8.5.32",
"tailwindcss": "^3.4.19",
"typescript": "^5.9.3"
}
}