1616 Commits

Author SHA1 Message Date
librelad
8adcb140e5 feat(overview): tasks-style app rows on the Updates tab
Redesign the Fleet Overview Updates list rows to match the Tasks page:
each app now shows its icon tile, a status pill ('✓ Up to date' /
'↑ Update available' / '• Unscanned'), and a dedicated 'Details' toggle
button (chevron) in the row actions instead of the bare leading chevron.
Resolve the app icon from the slug (/core/icons/apps/<slug>.svg, hidden on
error) and prefer window.getAppDisplayName for a prettier title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 20:18:26 +01:00
librelad
423f3163a9 Merge claude/1 2026-07-18 20:04:56 +01:00
librelad
6fd7b0bf61 fix(tasks): use canonical section loader for task-log loading state
The task-log "Loading logs..." state rendered as a bare left-aligned text
line (or a tiny ad-hoc 16px spinner overlay), which read as unfinished next
to the rest of the UI. Swap all three log-loading spots (initial placeholder,
toggle-open fetch, running-task stream placeholder) to the canonical
window.lpLoadingBox('Loading logs…'), and scope .lp-loading inside the log
terminal box to fill it and drop its own card chrome so the spinner sits dead
centre over the terminal surface instead of a box-in-a-box. Widen the stream
overlay-removal selector to also clear .lp-loading.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 20:04:56 +01:00
librelad
641b152a82 Merge claude/2 2026-07-18 19:59:58 +01:00
librelad
f4d34a0383 fix(updater): give the app Updates detail panel even inner padding
The per-app Updates tab's recessed panel used padding: 4px 16px, so the first
section (VERSION) sat 4px from the top edge — looking unpadded — while the last
section ended 20px from the bottom. Match the sibling .backup-snapshots-container
idiom (uniform 16px) and zero the first/last sections' outer padding so the
container's inset sets an even top/bottom margin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:59:58 +01:00
librelad
8ab791ccd3 Merge claude/2 2026-07-18 19:53:50 +01:00
librelad
b91ebba8b7 fix(install): confirm the file-permissions step on app install
For a generic app (no traefik files, not libreportal) fixPermissionsBeforeStart
prints nothing, so the "Updating file permissions before starting" step showed
its header followed by an empty line — looking like it did nothing. Add an
isSuccessful line after the call so the step reports like every other one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:53:49 +01:00
librelad
d442690324 Merge claude/2 2026-07-18 18:57:58 +01:00
librelad
b2db9984e6 fix(install): skip the premature step-11 WebUI update on a bootstrap install
A fresh install ran webuiLibrePortalUpdate twice back-to-back: once in
installLibrePortal step 11, then again in startScan at the end of preinstall.
The 30s time-debounce meant to collapse them is fragile (it never fired on a
recent install — >30s elapsed between the two), and debouncing is the wrong
lever anyway: startScan's pass runs AFTER scanConfigsForRandomPassword
finalises app passwords, so it — not the step-11 pass — is authoritative.

Defer the step-11 generation deterministically during a bootstrap install
(libreportal_bootstrap_install=true), leaving startScan's single pass to do the
work. Standalone reinstalls (no bootstrap flag) still generate in step 11. The
time-debounce stays as a general back-to-back backstop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 18:57:15 +01:00
librelad
52454ab461 Merge claude/1 2026-07-18 18:56:49 +01:00
librelad
5ed946209e fix(trivy): use official Aqua Security Trivy logo as icon source
The authoritative icon source containers/trivy/trivy.svg held a generic
hand-drawn shield/magnifier placeholder, while the frontend copy had the
official brand mark. The icon sync (webuiSyncAppIcons) treats the source as
canonical, so it was clobbering the official logo with the placeholder on
every full WebUI update/install — leaving the setup wizard's app tile
showing the generic icon. Replace the source with the official logo so it
sticks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 18:56:49 +01:00
librelad
0b223f6d9b Merge claude/2 2026-07-18 00:00:49 +01:00
librelad
c5df12201c docs(updater): mark P0–P3 shipped in the roadmap
P0 anchor, P1 config-first pin, P2 registry detection + display, and P3 honest
pinned apply/revert are all built and deployed. Records the detection nuance
(digest-compare fully covers rolling apps; versioned newer-version enumeration
is a P5 add) and the remaining deferred work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-18 00:00:49 +01:00
librelad
b04cae228a Merge claude/2 2026-07-17 23:54:46 +01:00
librelad
e4297fc77e feat(updater): P3 — honest pinned apply/revert
Make the Update / Roll-back buttons tell the truth, closing the "new code on
old data" hole a floating tag creates.

updaterApplyApp:
- Anchor-correct: capture before/after from updaterPrimaryImage (the
  <slug>-service image), not `grep -m1 image:` — fixes ollama et al.
- Records EXACT build refs in history from->to: repo:tag@sha256:<digest>
  (via updaterRefDigest), so history is meaningful even when the tag doesn't
  move (a rebuilt `latest`).
- Un-pins any digest a prior rollback pinned before pulling, so Update tracks
  the channel again instead of freezing on the rolled-back build.

updaterRollbackApp:
- Before recreating, re-pins the anchor image to the pre-update build's digest
  (from history's last update/ok `from`) via updaterSetAnchorRef, so `up` runs
  the OLD code — not the current channel head. This is the fix for restoring a
  data snapshot but recreating on a newer image.

New helpers (cli_updater_commands.sh): updaterRefDigest (local RepoDigest),
updaterSetAnchorRef (rewrite the anchor image line by service name, preserving
indent + the version sentinel; correct for companion-first apps like ollama),
updaterLastUpdateFrom (roll-back target from history).

Verified the helpers on nextcloud + ollama: pin adds @sha256 to the right
anchor only, sidecars untouched, sentinel preserved, unpin restores, YAML valid.
Caught and fixed a `local a=$1 b=...$a...` same-statement expansion bug that
would have silently no-op'd the rollback pin. End-to-end apply/revert not
exercised live here (no installed app has a pending update on this box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:54:46 +01:00
librelad
67e807c617 Merge claude/1 2026-07-17 23:49:40 +01:00
librelad
eea9cb4006 feat(icons): add official Trivy app icon
Trivy had no /core/icons/apps/trivy.svg (the canonical app-icon path used by
task rows, tiles, tools, services, routing), so it fell back to the default.
Add the official Trivy mark, white on its #1904DA brand tile so it stays legible
on the dark, near-transparent icon tiles across every theme.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:49:40 +01:00
librelad
84c3154203 Merge claude/2 2026-07-17 23:42:32 +01:00
librelad
d44a052a3e fix(updater): don't drop up-to-date apps from updates.json
The per-app object build used `available_version:($available_version|select(.!=""))`
to omit the field when empty. But in jq, a `{key: (empty)}` makes the WHOLE
object construction emit nothing — so every app with no available update (empty
available_version) produced no object and was silently dropped from updates.json.
Only apps WITH a pending update survived; an all-up-to-date fleet showed an empty
list. (Missed in P2's sandbox test because both fixture apps had updates.)

Emit an explicit null instead: `(if $available_version=="" then null else … end)`.
Verified: an up-to-date app (trivy, local==registry digest) now emits with
available_version:null; apps with updates still carry the string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:42:32 +01:00
librelad
68e8fcccb1 Merge claude/1 2026-07-17 23:35:43 +01:00
librelad
4f06674b61 ux(tasks): frame the task-type icon in a tile to match the app icon
The type glyph (🩺/🔄/…) sat bare next to the framed .task-app-icon tile. Give
it the same 32px rounded tile (border + surface-elevated bg) so the two read as
a matched pair; spacing now comes from .task-info's gap like the app icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:35:43 +01:00
librelad
06bd49d768 Merge claude/2 2026-07-17 23:30:28 +01:00
librelad
5a53a1b4c6 feat(updater): P2 — real registry detection + per-type version display
webuiUpdaterScan now tells the truth instead of hardcoding update_available=false.

Per app it resolves three facts (see roadmap §2): the running RepoDigest (one
`docker inspect` of the anchor image), the display version (OCI label →
versioned tag → channel·shortdigest), and — throttled — the registry's current
index digest for the channel tag (`docker buildx imagetools inspect`, the same
identity as RepoDigest, verified exact). update_available = the two digests
differ. Emits type (versioned|rolling), channel, current/available digests +
versions, and a services[] array (every image line, anchor flagged).

- Registry lookups throttled separately from the scan: CFG_UPDATER_REGISTRY_
  INTERVAL (min, default 360; 0 = local-only), own /tmp stamp, reuse of the
  prior available_digest between windows so the app list still refreshes every
  scan. UPDATER_REGISTRY_FORCE=1 forces a live pull (the Check-now button).
  Registry failure (offline/rate-limited) = "unknown", never a false "changed".
- Digest-compare fully detects rolling apps' new builds; for versioned apps it
  catches rebuilds of the pinned tag (newer-version enumeration is a later
  step) — honest per type, and versioned apps are user-picked via P1b anyway.
- Fixes a P1b regression: updaterPrimaryImage now strips the trailing version
  sentinel comment (`s/ #.*//`) via _updaterCleanImageRef — without this the
  anchor ref (and the live CVE scanner's image arg) carried the comment.
- JSON built with jq for safe escaping; jq-less fallback keeps output valid.

Verified via a simulated 2-app install: navidrome → "0.62.0" (OCI label) +
detected update; rolling app → channel·shortdigest + update; throttled re-run
reuses the prior digest; all output valid JSON.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:30:28 +01:00
librelad
da645d2591 Merge claude/1 2026-07-17 23:23:48 +01:00
librelad
2005f2c742 feat(tasks): retry keeps the failed task (marked "Retried") + jumps to new run
Previously retry silently created a new task, left the failed one in place with
no link, and didn't take you to the retry. Now:

- The new task is linked to the failed one via `retryOf`; the server stamps the
  original `retriedBy` and KEEPS it (its log is the failure record — never
  deleted), so history survives and there's no confusing bare duplicate.
- The failed row shows a muted "↻ Retried" pill, hides its now-stale Retry
  button, and offers "View retry" to jump to the new run. The new run shows a
  "Retry of:" backlink to the original.
- After retrying, the UI selects the new task and opens its live log so you
  follow the retry instead of hunting for it.

retryOf rides through the existing POST /api/tasks (no new endpoint); createTask
gained an optional extra-body arg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:23:48 +01:00
librelad
692e4a408a Merge claude/2 2026-07-17 23:13:58 +01:00
librelad
56102ac5c0 feat(updater): P1b — CFG_<APP>_VERSION drives the anchor image tag
Add a `#LIBREPORTAL|<APP>_VERSION_TAG|<current-tag>` sentinel to each app's
anchor (`<slug>-service`) image line, so a `CFG_<APP>_VERSION` config drives
that tag through the existing tagging system — the config becomes the source,
the compose tag is derived (like ports/IPs/domains already are). This is the
"install/pin a specific version" knob.

- Placeholder is the literal current tag (e.g. `31-fpm-alpine`), NOT a `*_DATA`
  token: an unset var leaves the line untouched (inert comment, keeps the real
  tag) and never trips the up_app stale-tag scanner.
- Only the anchor line is tagged (sidecars mariadb/redis/nginx stay
  tracked-by-digest, not user-version-picked). ollama correctly targets
  `ollama/ollama`, not the companion open-webui.
- Untagged anchors normalized to `:latest` (semantic no-op) so they're
  templatable too. The manager (libreportal) is skipped — it updates via its
  release channel, not a docker tag.

32 apps wired. Verified: CFG_NEXTCLOUD_VERSION=32 → `nextcloud:32` (prefix
preserved, idempotent, sidecars untouched); all compose files still valid YAML.
Inert until a version config is set, so no behaviour change on existing installs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:13:57 +01:00
librelad
0f53fb0b47 Merge claude/1 2026-07-17 23:11:34 +01:00
librelad
25b496df06 ux(health): rename "Repair Control Plane" → "Fix System Issues" (plainer copy)
"Control plane" is jargon. Rename the self-heal task and de-jargon all the
user-facing copy: task titles ("LibrePortal - Fix System Issues" / "System
Health Check"), the action label, the badge/banner/panel text and "Fix now"
button, the status summaries, and the heal task-log messages. Behaviour
unchanged; code comments keep the technical term where accurate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:11:34 +01:00
librelad
120e69de20 Merge claude/1 2026-07-17 23:04:12 +01:00
librelad
2da888f92d fix(tasks): show LibrePortal logo on system health/network heal task rows
The task-list row only paints the LibrePortal app-logo for commands matched by
isLibrePortalSystemTask's whitelist (appless tasks otherwise get just the type
icon, so arbitrary custom commands don't get a spurious logo). `libreportal
system health/network heal|check` weren't in it — only `system reclaim|image` —
so the heal rows showed a bare function icon instead of the logo like "Check for
Updates". Add health|network to the whitelist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 23:04:12 +01:00
librelad
fb4ad9a555 Merge claude/2 2026-07-17 22:57:12 +01:00
librelad
22d7bcf505 fix(updater): anchor app version to <slug>-service, not first image; revise design
The scan read the app's "current image" via `grep -m1 image:` — the first
image line. For apps that declare a companion service first this mislabels the
app: ollama lists `ollama-webui` (open-webui) before `ollama-service`
(ollama/ollama), so an app named ollama reported open-webui's version.

Add `updaterPrimaryImage`: resolve the version anchor from the app's
`<slug>-service` image (the universal primary-service naming convention,
33/33 apps; underscores→hyphens for slugs like libreportal_catalog). Falls back
to the first image line off-convention. Used by both the version and CVE loops.
Verified: only ollama changes (→ ollama/ollama:latest); nextcloud, mastodon,
jitsi, gitea, vaultwarden anchors unchanged.

Also revise docs/roadmap/app-version-updater-and-cve.md to the config-first
direction agreed this session:
- CFG_<APP>_VERSION ADOPTED (was rejected): the #LIBREPORTAL tagging system
  makes the config the source (compose tag derived from it), not a second one.
- Two version TYPES — versioned (real tag, version picker) vs rolling (floating
  channel, digest-freshness); digest is the uniform detection engine for both,
  version numbers are display enrichment. Answers "why not just compare numbers"
  (most upstreams publish none; no universal latest-version API).
- Multi-service anchor = <slug>-service, not first line; lock-step sets (jitsi)
  = one channel → several image lines; sidecars tracked-by-digest, not headline.
- Phases updated: P0 anchor (done) → P1 config-first pin → P2 detection+display.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 22:57:12 +01:00
librelad
e66a5759da Merge claude/1 2026-07-17 22:53:55 +01:00
librelad
24fbc36518 fix(health): robust crash-loop detection (RestartCount delta) + task display
Crash-loop detection keyed on `docker ps --filter status=restarting`, but a
backed-off loop sits "exited" between restarts once docker's backoff grows to
tens of seconds — so a slowed loop is missed. Detect via RestartCount CLIMBING
between scans (what a crash loop actually is), unioned with the instantaneous
restarting signal for fast loops. Baseline counts persist in
.health_restart_counts, written only by the throttled check so the heal's
re-scans don't disturb the delta.

Also give the system_health_heal / system_network_heal tasks proper display
(they fell through to the raw command + generic ⚙️): friendly titles in
formatCommandForUser, type icons (🩺 / 🌐) in getTaskTypeIcon, and action
labels in formatActionTitle — so they read as "LibrePortal - Repair Control
Plane" etc. with an icon, like install tasks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 22:53:54 +01:00
librelad
57f800ca1f Merge claude/1 2026-07-17 22:03:02 +01:00
librelad
2d1e4aa98f feat(health): self-healing control-plane watchdog + crash-loop failure cap
An offline trivy install crash-looped (server FATALs when it can't fetch the
vuln DB), and on rootless docker the restart storm churned the shared network's
port-forwarder until the WebUI's own published host port was torn down — the
WebUI stayed healthy INSIDE its container but was unreachable from the host, with
nothing detecting or healing it.

Three fixes, in the house self-healing style (mirrors the network-drift trio):

1. Control-plane health checker wired into the existing task-processor idle poll
   (maybeRegenPoll), no new daemon. dockerHealthScan (read-only) detects daemon
   down, a WebUI running-but-host-port-unreachable (the port-forward corruption),
   and crash-looping containers. webuiSystemHealthCheck writes
   frontend/data/system/health_status.json + self-dispatches a heal — the user
   can't click a button on a dead WebUI, so the poll drives the fix. Frontend
   health-notifier surfaces a topbar badge + dashboard banner + details panel.

2. Failure cap, enforced centrally by dockerHealthHeal (task-gated): stops
   crash-loopers (removing the churn), restarts the WebUI to re-publish a lost
   port forward, and — only if that fails — recycles the rootless daemon and
   restarts the core container. Caps every app immediately, no template churn.

3. Trivy no longer crash-loops offline: the server runs in a shell retry-loop so
   the container stays Up and quietly retries on a backoff instead of exiting
   FATAL. Verified: container stays Up across repeated DB-download failures.
   Core WebUI compose gains restart: unless-stopped so it self-recovers after a
   reboot / daemon recycle instead of staying down.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 22:03:02 +01:00
librelad
178a5487f2 Merge claude/2 2026-07-17 21:56:55 +01:00
librelad
71a02374d1 perf(backup): raw-data stats, dedupe repo stats, SSH connection reuse
Follow-up to the backup-refresh throttle/dedupe, cutting the cost of the
remote pulls that do still happen.

* restic stats now runs in --mode raw-data (restic_check.sh). The default
  restore-size mode walks every snapshot's tree to sum logical file sizes —
  the slowest restic op — just to fill a size readout. raw-data reads the
  index only and reports the repository's actual deduplicated on-disk size,
  which is exactly what the dashboard already labels "deduplicated,
  encrypted". raw-data omits total_file_count, so the per-location card now
  shows that location's snapshot count (already loaded client-side, and more
  useful for a backup repo) instead of a file count.

* engineLocationStats now shares the same per-refresh memoiser as
  engineSnapshotsJson (engine_dispatch.sh). Both the locations and dashboard
  generators call it per location, so repo stats went from two restic calls
  per location per refresh to one. Factored the cache into _engineCachedPull.

* SSH connection reuse for SFTP locations (backup_ssh.sh): ControlMaster=auto
  with a self-reaping ControlPersist master, so the several restic
  subprocesses a refresh/backup spawns against one location share a single
  authenticated connection instead of a fresh handshake each — the dominant
  per-call cost on a high-latency link. Toggle via CFG_BACKUP_SSH_MULTIPLEX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 21:56:55 +01:00
librelad
f81bcbbd21 Merge claude/2 2026-07-17 21:35:24 +01:00
librelad
a5cd8d625b perf(backup): throttle + dedupe the WebUI backup dashboard refresh
The "Refreshing backup data..." step on every WebUI update fired one
`restic stats` (restore-size mode — the slowest restic op) plus ~4
identical unfiltered `restic snapshots --json` pulls per enabled
location (dashboard, snapshots, app-status, migrate each pulled their
own), all over a fresh SSH connection for remote repos, on every pass
with no throttle — the slow, "frozen"-looking line users hit on poor
links.

Two fixes:

1. Dedupe. engineSnapshotsJson transparently memoises the first
   unfiltered whole-repo pull per location to a shared cache dir
   (LP_SNAP_CACHE_DIR, set by webui_updater around the chain), so the
   four generators reuse one restic call instead of four. Filtered and
   failed/empty pulls always fall through to a live call.

2. Throttle. Backups and location changes already regenerate this data
   live when they happen, so the routine pass is only a drift catch-up.
   Split the cheap local-only generators (engines/schema/passwords —
   no remote I/O) out to always run, and gate the remote pull behind
   CFG_BACKUP_DASHBOARD_REFRESH_INTERVAL (minutes, default 30, 0 =
   every update). A completed backup touches a dirty marker that forces
   the next pass to pull; WEBUI_UPDATER_FORCE still forces a full
   refresh.

Net: N locations x 5 remote restic calls every update -> 1 call per
location, only when something actually changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-17 21:35:24 +01:00
librelad
f6a7415a52 Merge claude/1 2026-07-16 17:39:40 +01:00
librelad
d794d4031a fix(overview): raise specificity so full-width detail actually applies
The prior .ov-row override tied .updater-row on specificity, and updater.css
loads later, so `display:grid` won and the detail stayed boxed. Use the
two-class selector .updater-row.ov-row to win regardless of load order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 17:39:40 +01:00
librelad
ea5d2b4357 Merge claude/1 2026-07-16 17:35:28 +01:00
librelad
97af9474cc fix(overview): expanded app detail spans full row width
The fleet Updates expander row inherited .updater-row's 3-column grid, so an
open row's detail body (CVEs, recovery, history) landed in a narrow right-hand
`auto` column with dead space to its left. Make .ov-row a block: the head stacks
on top and the detail body uses the full width.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 17:35:28 +01:00
librelad
b94cda0ffe Merge claude/1 2026-07-16 17:28:07 +01:00
librelad
7ab60a801d feat(updater): scrollable, worst-first CVE lists so long lists stay compact
An app with many CVEs (e.g. Trivy's 28) rendered every row full-height, pushing
the page down. Extract one renderCveList() shared by the standalone Security tab
and the per-app expander: sort worst-severity-first, and once past ~6 rows cap
the height (260px) with an internal scroll + themed scrollbar and a bottom fade
hint. Add a count pill to the expander's "Security" heading. Per-app sections
stay stacked and independently collapsible as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 17:28:07 +01:00
librelad
37e531ebbc Merge claude/1 2026-07-16 16:59:59 +01:00
librelad
a079050108 fix(trivy): scan local images via the mounted socket, not a registry pull
The client scan resolved images by pulling from Docker Hub, which fails on an
offline/privacy box ("index.docker.io ... network is unreachable") — yet every
installed app's image is already present locally. Point Trivy at the docker
socket that's already bind-mounted into the container (--image-src docker, plus
DOCKER_HOST=unix:///run/user/<uid>/docker.sock for rootless, derived from the
install user — rooted's default path is found automatically). Scans local
images with zero network. Verified: aquasec/trivy:latest -> 28 CVEs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 16:59:59 +01:00
librelad
6bb857f5a1 Merge claude/1 2026-07-16 16:51:49 +01:00