mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
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:
@@ -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 })}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user