feat(oci): run official container images as native LXC containers

Adds the OCI manager: an engine that turns a Docker Compose file into an
LXC definition, a catalog of 365 applications drawn from LinuxServer.io
and other container image sources, and a per-instance registry recording
what each container was built from. Reachable from the main menu.

Catalog text is translated like every other string in the project: the
taglines go through translate() and land in lang/*.json, so the entries
read in all eight languages instead of only English.

Translation cache builder:
- a failed translation leaves the key absent rather than writing English,
  which previously made the string count as translated forever
- a result identical to a 3+ word source is rejected, catching a provider
  that silently returns the text it was given
- strings that are nothing but glossary terms keep their source spelling
  instead of being discarded as failures
- no backoff between attempts when the provider is deterministic
- application names are protected so "HAOS One" survives translation
- argos joins the provider list, and the workflow reads the OCI sources

Audit & Report:
- findings that moved in the wrong direction between runs are reported
  alongside the ones that improved
- an accepted risk can carry a review date and is flagged when it falls due
- backup checks explain in plain language what they looked at and what to
  do next

Monitor:
- disks can be excluded from periodic reads, and an idle disk says so
  instead of showing a stale temperature
- per-disk identity survives a controller or enclosure change
- scheduled Borg backups resolve their SSH key from the repository entry
- PVE upgrades log the package list and the resulting dpkg changes

The web build no longer copies scripts/ into public/: the documentation
links to GitHub, so nothing read that folder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-22 18:24:59 +02:00
co-authored by Claude Opus 5
parent b36498f215
commit bcabcb618c
670 changed files with 221410 additions and 215 deletions
+23 -4
View File
@@ -10,7 +10,7 @@ import { Badge } from "./ui/badge"
import { Progress } from "./ui/progress"
import { Button } from "./ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "./ui/dialog"
import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, ChevronRight, Terminal, Archive, Plus, PlusCircle, Loader2, Clock, Database, Shield, Bell, FileText, Settings2, Activity, Package, RefreshCw, EthernetPort, ArrowUpCircle, Info, CheckCircle2, EyeOff, Eye, Trash2, Check, X, AlertTriangle, AlertCircle, Search, Tag as TagIcon } from 'lucide-react'
import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, ChevronRight, Terminal, Archive, Plus, PlusCircle, Loader2, Clock, Database, Shield, Bell, FileText, Settings2, Activity, Package, RefreshCw, EthernetPort, ArrowUpCircle, Info, CheckCircle2, EyeOff, Eye, Trash2, Check, X, AlertTriangle, AlertCircle, Search, Pin, Tag as TagIcon } from 'lucide-react'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { Checkbox } from "./ui/checkbox"
import { Switch } from "./ui/switch"
@@ -100,6 +100,7 @@ interface LxcAppWatch {
// once, through the image.
docker_available_version?: string | null
docker_update_available?: boolean | null
docker_pinned?: boolean | null
docker_image_reference?: string | null
docker_binding_error?: string | null
ports?: LxcAppPort[]
@@ -179,6 +180,7 @@ interface LxcDockerImageUpdate {
update_targets?: LxcDockerComposeTarget[]
standalone_containers?: string[]
update_available: boolean | null
pinned?: boolean
error: string | null
}
@@ -5740,7 +5742,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
) : (
<span className={image.update_available === false ? "text-green-500" : undefined}>
{t("vmLxc.updates.imageInstalledTag")} {" "}
<code className={image.update_available === false ? "text-green-500" : "text-foreground/80"}>{image.tag}</code>
<code className={image.update_available === false ? "text-green-500" : "text-foreground/80"}>{image.tag || image.local_digest?.slice(0, 19)}</code>
</span>
)}
</div>
@@ -5758,11 +5760,17 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
{t("vmLxc.updates.imageUpToDate")}
</span>
)}
{image.update_available === null && (
{image.update_available === null && !image.pinned && (
<span className="sm:hidden mt-1 text-xs text-muted-foreground inline-flex" title={image.error || undefined}>
{t("vmLxc.updates.imageDigestUnknown")}
</span>
)}
{image.pinned && (
<span className="sm:hidden mt-1 text-xs text-muted-foreground inline-flex items-center gap-1.5" title={t("vmLxc.updates.imagePinnedTitle")}>
<Pin className="h-3.5 w-3.5 flex-shrink-0" />
{t("vmLxc.updates.imagePinned")}
</span>
)}
</div>
</div>
<div className="flex flex-wrap items-center justify-end gap-2">
@@ -5772,11 +5780,17 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
{t("vmLxc.updates.imageUpToDate")}
</span>
)}
{image.update_available === null && (
{image.update_available === null && !image.pinned && (
<span className="hidden sm:inline-flex text-xs text-muted-foreground flex-shrink-0" title={image.error || undefined}>
{t("vmLxc.updates.imageDigestUnknown")}
</span>
)}
{image.pinned && (
<span className="hidden sm:inline-flex items-center gap-1.5 text-xs text-muted-foreground flex-shrink-0" title={t("vmLxc.updates.imagePinnedTitle")}>
<Pin className="h-3.5 w-3.5 flex-shrink-0" />
{t("vmLxc.updates.imagePinned")}
</span>
)}
{image.update_available === true && (image.update_targets || []).map((target) => (
<Button
key={`${image.reference}-${target.project}`}
@@ -6066,6 +6080,11 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
<CheckCircle2 className="h-4 w-4 flex-shrink-0" />
<span>{t("vmLxc.updates.imageUpToDate")}</span>
</div>
) : aw.docker_pinned ? (
<div className="flex items-center gap-2" title={t("vmLxc.updates.imagePinnedTitle")}>
<Pin className="h-4 w-4 flex-shrink-0" />
<span>{t("vmLxc.updates.imagePinned")}</span>
</div>
) : null
) : (
<div>{t("vmLxc.updates.versionTrackingPendingShort")}</div>