refine post-install and hardware GPU docs, Monitor UX and CLI styling

- rewrite the 15 post-install pages and the 3 hardware GPU pages so they reflect the current scripts (reversibility, tracked-tool counts, kernel parameters, per-tool commands, Alpine LXC propagation flow)
- migrate the legacy step-badge helper on post-install/optional and create-vm/synology to the canonical pill component, with the stepLabel key added in each locale
- fix rich-text i18n calls missing helpers across network, automated, optional, security, customization and the post-install landing pages, and escape the `<iface>` placeholder in automated so intl no longer parses it as a tag
- remove the mouse-follow blue overlay from the docs landing layout
- reposition the App-tab Edit button and stack the Search and Register controls vertically on mobile
- move the Bulk update Configure/Edit control into the section header so it behaves the same on desktop and mobile
- show a spinner during the final autoremove/autoclean pass of update-pve-safe so the cleanup step reads as active instead of silent
- restyle the shell spinner and msg_info in a distinctive purple and drop the unused msg_lang duplicate
- add a web-docs i18n build script and its CI workflow, plus tests for the pushover notification channel
This commit is contained in:
MacRimi
2026-08-26 17:23:09 +02:00
parent b71dd65898
commit fcfe8da765
106 changed files with 3376 additions and 1358 deletions
@@ -51,13 +51,13 @@ function ImageWithCaption({ src, alt, caption }: { src: string; alt: string; cap
)
}
function StepNumber({ number }: { number: number }) {
function StepHeading({ number, label, title, id }: { number: number; label: string; title: string; id?: string }) {
return (
<div
className="inline-flex items-center justify-center w-8 h-8 mr-3 text-white bg-blue-500 rounded-full"
aria-hidden="true"
>
<span className="text-sm font-bold">{number}</span>
<div className="flex items-center gap-3 mb-4" id={id}>
<span className="inline-flex items-center rounded-full border border-blue-200 bg-blue-50 px-2.5 py-0.5 text-xs font-semibold text-blue-800">
{label} {number}
</span>
<h2 className="text-xl font-semibold m-0">{title}</h2>
</div>
)
}
@@ -271,10 +271,7 @@ export default function Page() {
const media = step.loaders[activeLoader] || []
return (
<section key={step.id} className="mb-12 border-b pb-8">
<h2 className="text-xl font-semibold mb-4 flex items-center" id={step.id}>
<StepNumber number={stepIdx + 1} />
{step.title}
</h2>
<StepHeading number={stepIdx + 1} label={t("stepLabel")} title={step.title} id={step.id} />
<p className="mb-4">{step.intro}</p>
<div className="mt-6">
@@ -169,18 +169,11 @@ export default async function GpuVmPassthroughPage({
├─ Not in SR-IOV
├─ Not D3cold (AMD)
├─ Has FLR or equivalent reset
├─ Not on the block-list (Intel Arc, Apollo Lake)
├─ Warn if single-GPU host
└─ Resolve IOMMU group
Audio companion
├─ Has .1 sibling? (dGPU: NVIDIA/AMD HDMI)
│ → auto-include (never used by host)
└─ No .1 sibling? (Intel iGPU, split audio)
→ checklist of host audio controllers,
default = none (user opts in)
User selects VM
@@ -190,14 +183,24 @@ export default async function GpuVmPassthroughPage({
GPU already assigned elsewhere?
├─ To LXC → offer to remove it from LXC
├─ To other VM → offer to remove it there
│ + clean up orphan audio
│ (skips audio whose
display sibling stays)
├─ To LXC → menu: keep + disable onboot
│ OR remove GPU lines + keep onboot
├─ To other VM (running) → abort
├─ To other VM (stopped) → menu: keep + disable onboot
OR remove GPU lines + keep onboot
│ (fast-path: already vfio-pci → no
│ host reconfig, no reboot needed)
└─ Free → continue
Audio companion
├─ Has .1 sibling? (dGPU: NVIDIA/AMD HDMI)
│ → auto-include (never used by host)
└─ No .1 sibling? (Intel iGPU, split audio)
→ checklist of host audio controllers,
default = none (user opts in)
Show confirmation summary
(GPU + IOMMU siblings + audio + target VM)
@@ -209,12 +212,17 @@ export default async function GpuVmPassthroughPage({
Host:
├─ /etc/modules (vfio_*)
├─ /etc/modprobe.d/vfio.conf (ids=...)
├─ /etc/modprobe.d/blacklist.conf
├─ /etc/modprobe.d/vfio.conf (ids=... disable_vga=1)
├─ /etc/modprobe.d/blacklist.conf (vendor drivers only)
├─ kernel cmdline (IOMMU if missing)
├─ NVIDIA: disable udev rule + hard blacklist
├─ AMD: dump ROM → /usr/share/kvm/*.bin
├─ NVIDIA: per-BDF udev rule at
│ 10-proxmenux-vfio-bind.rules
│ + BDF state at vfio-bind.bdfs
│ (blacklist nvidia only when
│ every NVIDIA GPU is in VFIO)
├─ AMD: dump ROM → vbios_<vendor>_<device>.bin
└─ update-initramfs -u -k all
+ proxmox-boot-tool refresh
VM config (qm set <vmid>):
├─ hostpci0 = GPU (x-vga=1 unless Intel iGPU)
@@ -336,7 +344,7 @@ export default async function GpuVmPassthroughPage({
code={`# Example — what ends up in the VM config after a GPU + audio passthrough
# (you don't type this, ProxMenux does it for you)
hostpci0: 0000:01:00.0,pcie=1,x-vga=1[,romfile=vbios_card.bin] # GPU video
hostpci0: 0000:01:00.0,pcie=1,x-vga=1[,romfile=vbios_1002_15dd.bin] # GPU video (AMD ROM file named vbios_<vendor>_<device>.bin)
hostpci1: 0000:01:00.1,pcie=1 # GPU audio
vga: std
@@ -455,9 +463,9 @@ qm set <vmid> --delete hostpci0
# Release the GPU back to the host driver:
rm -f /etc/modprobe.d/vfio.conf
rm -f /etc/modprobe.d/blacklist.conf # careful — this file may have other blacklists
# NVIDIA only — re-enable the udev rule + unpin the hard blacklist
# NVIDIA only — re-enable the udev rule + drop the all-NVIDIA-in-VFIO blacklist (if present)
mv /etc/udev/rules.d/70-nvidia.rules.proxmenux-disabled /etc/udev/rules.d/70-nvidia.rules 2>/dev/null
rm -f /etc/modprobe.d/nvidia-blacklist.conf
rm -f /etc/modprobe.d/proxmenux-nvidia-vfio-blacklist.conf
update-initramfs -u -k all
reboot`}
@@ -22,7 +22,6 @@ export async function generateMetadata({
}
}
type MatrixRow = { kernel: string; pve: string; minCode: string; minTail: string }
type StringItem = string
type RelatedItem = { label: string; href: string; tail?: string }
@@ -38,7 +37,6 @@ export default async function NvidiaHostPage({
const messages = (await getMessages({ locale })) as unknown as {
docs: { hardware: { nvidiaHost: {
walkthrough: {
version: { rows: MatrixRow[] }
prepare: { items: StringItem[] }
}
reinstallUninstall: { uninstallItems: StringItem[] }
@@ -46,7 +44,6 @@ export default async function NvidiaHostPage({
related: { items: RelatedItem[] }
} } }
}
const matrixRows = messages.docs.hardware.nvidiaHost.walkthrough.version.rows
const prepareItems = messages.docs.hardware.nvidiaHost.walkthrough.prepare.items
const uninstallItems = messages.docs.hardware.nvidiaHost.reinstallUninstall.uninstallItems
const kindsItems = messages.docs.hardware.nvidiaHost.updates.kindsItems
@@ -255,29 +252,6 @@ export default async function NvidiaHostPage({
<p className="mb-3 text-gray-800">{t.rich("walkthrough.version.body1", { strong, em })}</p>
<p className="mb-3 text-gray-800">{t("walkthrough.version.body2")}</p>
<div className="my-4 overflow-x-auto">
<table className="min-w-full border border-gray-200 text-sm">
<thead className="bg-gray-100">
<tr>
<th className="border border-gray-200 px-3 py-2 text-left text-gray-900">{t("walkthrough.version.headerKernel")}</th>
<th className="border border-gray-200 px-3 py-2 text-left text-gray-900">{t("walkthrough.version.headerPve")}</th>
<th className="border border-gray-200 px-3 py-2 text-left text-gray-900">{t("walkthrough.version.headerMin")}</th>
</tr>
</thead>
<tbody className="text-gray-800">
{matrixRows.map((row, idx) => (
<tr key={idx}>
<td className="border border-gray-200 px-3 py-2">{row.kernel}</td>
<td className="border border-gray-200 px-3 py-2">{row.pve}</td>
<td className="border border-gray-200 px-3 py-2">
<code>{row.minCode}</code>{row.minTail}
</td>
</tr>
))}
</tbody>
</table>
</div>
<Callout variant="tip" title={t("walkthrough.version.whyTitle")}>
{t("walkthrough.version.whyBody")}
</Callout>
@@ -170,7 +170,8 @@ export default async function SwitchGpuModePage({
Validations
├─ SR-IOV VF / active PF? → block
├─ Target = VM and blocked ID? → block
└─ IOMMU parameter present? → warn if missing
└─ IOMMU parameter present? → auto-add to boot
cmdline if missing
Find affected workloads
@@ -122,6 +122,9 @@ export default async function UpdatesTabPage({
</tbody>
</table>
</div>
<Callout variant="tip">
{t.rich("mechanisms.officialReference", { helper: linkHelper })}
</Callout>
<Callout variant="warning">{t.rich("mechanisms.callout", { strong, em, code })}</Callout>
<h2 className="mt-10 mb-4 text-2xl font-semibold text-gray-900">{t("docker.heading")}</h2>
@@ -24,6 +24,7 @@ export async function generateMetadata({
"proxmox discord",
"proxmox email alerts",
"proxmox gotify",
"proxmox pushover",
"proxmox apprise",
"proxmox ntfy",
"proxmox matrix notifications",
@@ -72,6 +73,7 @@ export default async function NotificationsPage({
discord: { items: string[] }
gotify: { items: string[] }
email: { gmailItems: string[]; outlookItems: string[] }
pushover: { steps: string[] }
apprise: { listItems: string[]; steps: string[] }
rich: { togglesItems: string[] }
quiet: { purposeItems: string[]; howItems: string[] }
@@ -101,6 +103,7 @@ export default async function NotificationsPage({
const gotifyItems = n.gotify.items
const gmailItems = n.email.gmailItems
const outlookItems = n.email.outlookItems
const pushoverSteps = n.pushover.steps
const appriseListItems = n.apprise.listItems
const appriseSteps = n.apprise.steps
const togglesItems = n.rich.togglesItems
@@ -152,7 +155,7 @@ export default async function NotificationsPage({
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={18}
estimatedMinutes={20}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -392,6 +395,34 @@ export default async function NotificationsPage({
{t("email.relayBody")}
</Callout>
<h3 id="pushover" className="text-xl font-semibold mt-8 mb-3 text-gray-900">{t("pushover.heading")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("pushover.intro", { a: ext("https://pushover.net/api") })}
</p>
<h4 className="text-base font-semibold mt-6 mb-2 text-gray-900">{t("pushover.stepsTitle")}</h4>
<ol className="list-decimal pl-6 mb-4 text-gray-800 leading-relaxed space-y-1">
{pushoverSteps.map((_, idx) => (
<li key={idx}>
{t.rich(`pushover.steps.${idx}`, {
em,
code,
a: ext(idx === 2 ? "https://pushover.net/apps/build" : "https://pushover.net/"),
})}
</li>
))}
</ol>
<Callout variant="info" title={t("pushover.priorityTitle")}>
{t.rich("pushover.priorityBody", { strong })}
</Callout>
<Callout variant="warning" title={t("pushover.secretTitle")}>
{t("pushover.secretBody")}
</Callout>
<h3 id="apprise" className="text-xl font-semibold mt-8 mb-3 text-gray-900">{t("apprise.heading")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">{t("apprise.intro")}</p>
@@ -402,7 +433,7 @@ export default async function NotificationsPage({
{appriseListItems.map((_, idx) => (
<li key={idx}>
{t.rich(`apprise.listItems.${idx}`, {
a: idx === 0 ? ext("https://github.com/caronc/apprise/wiki") : ext("https://github.com/caronc/apprise/wiki/URLBasics"),
a: idx === 0 ? ext("https://appriseit.com/services/") : ext("https://github.com/caronc/apprise/wiki/URLBasics"),
})}
</li>
))}
@@ -412,7 +443,7 @@ export default async function NotificationsPage({
<ol className="list-decimal pl-6 mb-4 text-gray-800 leading-relaxed space-y-1">
{appriseSteps.map((_, idx) => (
<li key={idx}>{t.rich(`apprise.steps.${idx}`, { em, code, a: ext("https://github.com/caronc/apprise/wiki") })}</li>
<li key={idx}>{t.rich(`apprise.steps.${idx}`, { em, code, a: ext("https://appriseit.com/services/") })}</li>
))}
</ol>
@@ -120,7 +120,7 @@ export default async function AutomatedPage({
<td className="px-4 py-2 text-gray-500 font-mono">{i + 1}</td>
<td className="px-4 py-2 font-semibold">{o.tool}</td>
<td className="px-4 py-2 text-gray-700 leading-relaxed">
{t.rich(`optimizations.${i}.what`, { link: log2ramLink })}
{t.rich(`optimizations.${i}.what`, { link: log2ramLink, code: (chunks) => <code>{chunks}</code>, strong: (chunks) => <strong>{chunks}</strong>, em: (chunks) => <em>{chunks}</em> })}
</td>
<td className="px-4 py-2">
<Link
@@ -66,6 +66,7 @@ export default async function PostInstallBasicSettingsPage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={10}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -229,8 +230,7 @@ Acquire::Languages "none";`}
<CopyableCode
code={`# Remove a utility you no longer want
apt purge htop
apt autoremove --purge`}
apt purge htop`}
className="my-4"
/>
@@ -48,6 +48,7 @@ export default async function PostInstallCustomizationPage({
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={7}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -142,7 +143,7 @@ cat /etc/motd
/>
<Callout variant="tip" title={t("verify.reversibleTitle")}>
{t.rich("verify.reversibleBody", { code, link: uninstallLink })}
{t.rich("verify.reversibleBody", { code, strong, link: uninstallLink })}
</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("related.heading")}</h2>
@@ -58,6 +58,7 @@ export default async function PostInstallMonitoringPage({
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={6}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -110,10 +111,6 @@ journalctl -u ovh-rtm --since "10 min ago"`}
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("ovh.troubleTitle")}</h3>
<Callout variant="tip" title={t("ovh.spuriousTitle")}>
{t.rich("ovh.spuriousBody", { em, code })}
</Callout>
<Callout variant="tip" title={t("ovh.revertTitle")}>
{t.rich("ovh.revertBody", { code })}
</Callout>
@@ -50,6 +50,7 @@ export default async function PostInstallNetworkPage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={8}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -116,7 +117,7 @@ export default async function PostInstallNetworkPage({
</Callout>
<Callout variant="warning" title={t("ovs.revertTitle")}>
{t("ovs.revertBody")}
{t.rich("ovs.revertBody", { code, em, link: uninstallLink })}
</Callout>
<CopyableCode
@@ -158,8 +159,8 @@ sysctl net.ipv4.tcp_fastopen`}
{t("bbr.impactBody")}
</Callout>
<Callout variant="warning" title={t("bbr.revertTitle")}>
{t("bbr.revertBody")}
<Callout variant="tip" title={t("bbr.revertTitle")}>
{t.rich("bbr.revertBody", { code, link: uninstallLink })}
</Callout>
<CopyableCode
@@ -38,10 +38,13 @@ export async function generateMetadata({
type Logo = { name: string; alt: string; src: string }
function StepNumber({ number }: { number: number }) {
function StepHeading({ number, label, title }: { number: number; label: string; title: string }) {
return (
<div className="inline-flex items-center justify-center w-8 h-8 mr-3 text-white bg-blue-500 rounded-full">
<span className="text-sm font-bold">{number}</span>
<div className="flex items-center gap-3 mt-16 mb-4">
<span className="inline-flex items-center rounded-full border border-blue-200 bg-blue-50 px-2.5 py-0.5 text-xs font-semibold text-blue-800">
{label} {number}
</span>
<h3 className="text-xl font-semibold m-0">{title}</h3>
</div>
)
}
@@ -60,7 +63,7 @@ export default async function OptionalSettingsPage({
ceph: { doesItems: string[] }
amd: { doesItems: string[] }
ha: { doesItems: string[] }
testing: { doesItems: string[] }
pveam: { doesItems: string[] }
fastfetch: { doesItems: string[]; customItems: string[]; logos: Logo[] }
figurine: { doesItems: string[] }
log2ram: { doesItems: string[] }
@@ -69,7 +72,7 @@ export default async function OptionalSettingsPage({
const cephItems = messages.docs.postInstall.optional.ceph.doesItems
const amdItems = messages.docs.postInstall.optional.amd.doesItems
const haItems = messages.docs.postInstall.optional.ha.doesItems
const testingItems = messages.docs.postInstall.optional.testing.doesItems
const pveamItems = messages.docs.postInstall.optional.pveam.doesItems
const fastfetchItems = messages.docs.postInstall.optional.fastfetch.doesItems
const fastfetchCustomItems = messages.docs.postInstall.optional.fastfetch.customItems
const fastfetchLogos = messages.docs.postInstall.optional.fastfetch.logos
@@ -91,10 +94,7 @@ export default async function OptionalSettingsPage({
</p>
<h2 className="text-2xl font-semibold mt-8 mb-4">{t("available")}</h2>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={1} />
{t("ceph.title")}
</h3>
<StepHeading number={1} label={t("stepLabel")} title={t("ceph.title")} />
<p className="mb-4">{t("ceph.intro")}</p>
<p className="mb-4">{t("ceph.doesIntro")}</p>
<ul className="list-disc pl-5 mb-4">
@@ -106,8 +106,18 @@ export default async function OptionalSettingsPage({
<p className="text-lg mb-2">{t("ceph.automates")}</p>
<CopyableCode
code={`
# Add Ceph repository
echo "deb https://download.proxmox.com/debian/ceph-squid $(lsb_release -cs) no-subscription" > /etc/apt/sources.list.d/ceph-squid.list
# On Proxmox VE 9 (Debian trixie) — deb822 format
cat > /etc/apt/sources.list.d/ceph.sources <<'EOF'
Types: deb
URIs: http://download.proxmox.com/debian/ceph-squid
Suites: trixie
Components: no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
EOF
# On Proxmox VE 8 (Debian bookworm) — legacy one-liner
# echo "deb https://download.proxmox.com/debian/ceph-squid $(lsb_release -cs) no-subscription" \\
# > /etc/apt/sources.list.d/ceph-squid.list
# Update package lists
apt-get update
@@ -120,10 +130,7 @@ pveceph status
`}
/>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={2} />
{t("amd.title")}
</h3>
<StepHeading number={2} label={t("stepLabel")} title={t("amd.title")} />
<p className="mb-4">{t("amd.intro")}</p>
<p className="mb-4">{t("amd.doesIntro")}</p>
<ul className="list-disc pl-5 mb-4">
@@ -135,23 +142,21 @@ pveceph status
<p className="text-lg mb-2">{t("amd.automates")}</p>
<CopyableCode
code={`
# Set kernel parameter
# Set kernel parameter — GRUB path
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="idle=nomwait /g' /etc/default/grub
update-grub
# Set kernel parameter — systemd-boot path (ZFS-on-root)
# Adds 'idle=nomwait' to /etc/kernel/cmdline and runs:
# proxmox-boot-tool refresh
# Configure KVM
echo "options kvm ignore_msrs=Y" >> /etc/modprobe.d/kvm.conf
echo "options kvm report_ignored_msrs=N" >> /etc/modprobe.d/kvm.conf
# Install latest Proxmox VE kernel
apt-get install pve-kernel-$(uname -r | cut -d'-' -f1-2)
`}
/>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={3} />
{t("ha.title")}
</h3>
<StepHeading number={3} label={t("stepLabel")} title={t("ha.title")} />
<p className="mb-4">{t("ha.intro")}</p>
<p className="mb-4">{t("ha.doesIntro")}</p>
<ul className="list-disc pl-5 mb-4">
@@ -167,39 +172,23 @@ systemctl enable --now pve-ha-lrm pve-ha-crm corosync
`}
/>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={4} />
{t("testing.title")}
</h3>
<p className="mb-4">{t("testing.intro")}</p>
<p className="mb-4">{t("testing.doesIntro")}</p>
<StepHeading number={4} label={t("stepLabel")} title={t("pveam.title")} />
<p className="mb-4">{t.rich("pveam.intro", { code })}</p>
<p className="mb-4">{t("pveam.doesIntro")}</p>
<ul className="list-disc pl-5 mb-4">
{testingItems.map((_, idx) => (
<li key={idx}>{t(`testing.doesItems.${idx}`)}</li>
{pveamItems.map((_, idx) => (
<li key={idx}>{t.rich(`pveam.doesItems.${idx}`, { code })}</li>
))}
</ul>
<p className="mb-4">{t("testing.howUse")}</p>
<p className="text-lg mb-2">{t("testing.manualIntro")}</p>
<p className="mb-4">{t("pveam.howUse")}</p>
<p className="text-lg mb-2">{t("pveam.automates")}</p>
<CopyableCode
code={`
# Add Proxmox testing repository
echo "deb http://download.proxmox.com/debian/pve $(lsb_release -cs) pvetest" | sudo tee /etc/apt/sources.list.d/pve-testing-repo.list
# Update package lists
sudo apt update
pveam update
`}
/>
<p className="mt-4 text-sm text-gray-600">
<strong>{t("testing.noteLabel")}</strong> {t("testing.noteBody")}
</p>
<p className="mt-4 text-yellow-600">
<strong>{t("testing.warnLabel")}</strong> {t("testing.warnBody")}
</p>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={5} />
{t("fastfetch.title")}
</h3>
<StepHeading number={5} label={t("stepLabel")} title={t("fastfetch.title")} />
<p className="mb-4">{t("fastfetch.intro")}</p>
@@ -255,24 +244,39 @@ systemctl enable --now pve-ha-lrm pve-ha-crm corosync
<p className="text-lg mb-2">{t("fastfetch.automates")}</p>
<CopyableCode
code={`
# Download and install the latest version of Fastfetch
FASTFETCH_URL=$(curl -s https://api.github.com/repos/fastfetch-cli/fastfetch/releases/latest | grep "browser_download_url.*fastfetch-linux-amd64.deb" | cut -d '"' -f 4)
# Remove any previous install so the newest .deb lands clean
apt-get remove --purge -y fastfetch 2>/dev/null
rm -f /usr/bin/fastfetch /usr/local/bin/fastfetch
# Download and install the latest .deb via the GitHub Releases API
FASTFETCH_URL=$(curl -sSf --connect-timeout 5 --max-time 15 \\
https://api.github.com/repos/fastfetch-cli/fastfetch/releases/latest \\
| jq -r '.assets[] | select(.name | test("fastfetch-linux-amd64.deb")) | .browser_download_url')
wget -q -O /tmp/fastfetch.deb "$FASTFETCH_URL"
dpkg -i /tmp/fastfetch.deb
apt-get install -f -y
# Configure Fastfetch (logo selection remains interactive)
# The configuration is done through a series of jq commands
# The configuration is done through a series of jq commands.
# A custom "System optimised by ProxMenux" line is prepended
# to the modules array so it shows above the standard sections.
# Set Fastfetch to run at login
echo "clear && fastfetch" >> ~/.bashrc
# Wire Fastfetch into ~/.bashrc — inside a marker block so the
# same block can be replaced on re-run without polluting the file.
# The block is guarded so it only runs on interactive shells that
# actually have the fastfetch binary available.
cat >> ~/.bashrc <<'EOF'
# BEGIN FASTFETCH
if [[ $- == *i* ]] && command -v fastfetch >/dev/null 2>&1; then
clear
fastfetch
fi
# END FASTFETCH
EOF
`}
/>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={6} />
{t("figurine.title")}
</h3>
<StepHeading number={6} label={t("stepLabel")} title={t("figurine.title")} />
<p className="mb-4">{t("figurine.intro")}</p>
@@ -330,10 +334,7 @@ chmod +x "/etc/profile.d/figurine.sh"
<p className="mt-4">{t("figurine.outro")}</p>
<h3 className="text-xl font-semibold mt-16 mb-4 flex items-center">
<StepNumber number={7} />
{t("log2ram.title")}
</h3>
<StepHeading number={7} label={t("stepLabel")} title={t("log2ram.title")} />
<p className="mb-4">
{t.rich("log2ram.intro", { code, em })}
+1 -1
View File
@@ -163,7 +163,7 @@ export default async function PostInstallPage({
/>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("threeWays.heading")}</h2>
<p className="mb-6 text-gray-800 leading-relaxed">{t("threeWays.body")}</p>
<p className="mb-6 text-gray-800 leading-relaxed">{t.rich("threeWays.body", { em: (chunks) => <em>{chunks}</em> })}</p>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-8 not-prose">
{ROUTE_CONFIG.map(({ key, href, Icon, accent, iconBg }, idx) => {
@@ -52,6 +52,7 @@ export default async function PostInstallPerformancePage({
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={5}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -77,6 +78,10 @@ export default async function PostInstallPerformancePage({
sed -i "s/#pigz:.*/pigz: 1/" /etc/vzdump.conf
apt-get -y install pigz
# The wrapper pins PATH so gzip resolves inside the script even under sudo,
# and sets GZIP="-1" — fastest compression level, lowest ratio. The trade
# is intentional: on multi-core hosts wall-clock time drops far more than
# the archive grows.
cat > /bin/pigzwrapper <<'EOF'
#!/bin/sh
PATH=/bin:$PATH
@@ -95,8 +100,8 @@ chmod +x /bin/pigzwrapper
{t.rich("pigz.replacesBody", { code })}
</Callout>
<Callout variant="danger" title={t("pigz.revertTitle")}>
{t.rich("pigz.revertBody", { strong })}
<Callout variant="tip" title={t("pigz.revertTitle")}>
{t.rich("pigz.revertBody", { strong, link: (chunks) => <Link href="/docs/post-install/uninstall" className="text-blue-600 hover:underline">{chunks}</Link>, code: (chunks) => <code>{chunks}</code> })}
</Callout>
<CopyableCode
@@ -47,6 +47,7 @@ export default async function PostInstallSecurityPage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={5}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -72,9 +73,8 @@ export default async function PostInstallSecurityPage({
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("rpcbind.runsTitle")}</h3>
<CopyableCode
code={`# Stop and disable the rpcbind service
systemctl stop rpcbind
systemctl disable rpcbind`}
code={`# Stop and disable both activation paths
systemctl disable --now rpcbind.socket rpcbind.service`}
className="my-4"
/>
<p className="mb-4 text-gray-800 leading-relaxed">{t("rpcbind.runsOutro")}</p>
@@ -84,14 +84,14 @@ systemctl disable rpcbind`}
{t.rich("rpcbind.verifyBody", { code })}
</p>
<CopyableCode
code={`systemctl is-active rpcbind # should report: inactive
systemctl is-enabled rpcbind # should report: disabled
code={`systemctl is-active rpcbind.service rpcbind.socket
systemctl is-enabled rpcbind.service rpcbind.socket
ss -tulpn | grep ':111 ' # should return nothing`}
className="my-4"
/>
<Callout variant="tip" title={t("rpcbind.reversibleTitle")}>
{t.rich("rpcbind.reversibleBody", { em, link: uninstallLink })}
<Callout variant="warning" title={t("rpcbind.reversibleTitle")}>
{t.rich("rpcbind.reversibleBody", { em, strong, code, link: uninstallLink })}
</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("related.heading")}</h2>
@@ -61,6 +61,7 @@ export default async function PostInstallStoragePage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={12}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -75,8 +76,8 @@ export default async function PostInstallStoragePage({
})}
</Callout>
<Callout variant="warning" title={t("notTrackedTitle")}>
{t.rich("notTrackedBody", { strong })}
<Callout variant="tip" title={t("trackedTitle")}>
{t.rich("trackedBody", { strong, link: (chunks) => <Link href="/docs/post-install/uninstall" className="text-blue-600 hover:underline">{chunks}</Link>, code: (chunks) => <code>{chunks}</code> })}
</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("arc.heading")}</h2>
@@ -286,8 +287,8 @@ ionice: 5 # Lower I/O priority (5 = best-effort class, lowest priority in
className="my-4"
/>
<Callout variant="warning" title={t("vzdump.noBackupTitle")}>
{t.rich("vzdump.noBackupBody", { strong, code, em })}
<Callout variant="tip" title={t("vzdump.backupTitle")}>
{t.rich("vzdump.backupBody", { strong, code })}
</Callout>
<Callout variant="tip" title={t("vzdump.skipTitle")}>
@@ -55,6 +55,7 @@ export default async function PostInstallSystemPage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={10}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -89,7 +90,9 @@ export default async function PostInstallSystemPage({
daily
su root adm
rotate 7
create
create 0640 root adm
missingok
notifempty
compress
size 10M
delaycompress
@@ -141,9 +144,10 @@ include /etc/logrotate.d`}
vm.swappiness = 10 # Avoid swapping unless truly necessary
vm.dirty_ratio = 15 # Start writeback sooner (default 20)
vm.dirty_background_ratio = 5 # Start async writeback earlier (default 10)
vm.overcommit_memory = 1 # Allow overcommit (needed by many applications)
vm.max_map_count = 262144 # Enough for modern apps (ES, Docker, some games)
vm.compaction_proactiveness = 20 # Only on kernels that support it`}
vm.compaction_proactiveness = 20 # Only on kernels that support it
# Note: the kernel's memory-overcommit policy (vm.overcommit_memory) is
# NOT modified Proxmox's default is left in place.`}
className="my-4"
/>
@@ -60,6 +60,7 @@ export default async function UninstallOptimizationsPage({
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={8}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -151,6 +151,10 @@ export default async function PostInstallUpdatesPage({
))}
</Steps>
<Callout variant="warning" title={t("applying.jqTitle")}>
{t.rich("applying.jqBody", { code })}
</Callout>
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("differs.heading")}</h2>
<div className="overflow-x-auto mb-6">
@@ -61,6 +61,7 @@ export default async function PostInstallVirtualizationPage({
<DocHeader
title={t("header.title")}
section={t("header.section")}
estimatedMinutes={9}
/>
<Callout variant="info" title={t("intro.title")}>
@@ -146,6 +147,10 @@ pcie_acs_override=downstream,multifunction`}
className="my-4"
/>
<Callout variant="warning" title={t("vfio.acsTitle")}>
{t.rich("vfio.acsBody", { code, strong })}
</Callout>
<p className="mb-3 text-gray-800 leading-relaxed">
{t.rich("vfio.modulesIntro", { code })}
</p>
-2
View File
@@ -1,6 +1,5 @@
import { Suspense } from "react"
import Navbar from "@/components/navbar"
import MouseMoveEffect from "@/components/mouse-move-effect"
import { PagefindHighlighter } from "@/components/pagefind-highlighter"
import { LocaleHtmlSync } from "@/components/locale-html-sync"
import type React from "react"
@@ -175,7 +174,6 @@ export default async function LocaleLayout({
}}
/>
<Navbar />
<MouseMoveEffect />
<div className="pt-16 md:pt-16">{children}</div>
<script src="/pagefind/pagefind-highlight.js" type="module" defer />
<Suspense fallback={null}>