1187 Commits

Author SHA1 Message Date
librelad
f4ba7ffe51 Merge claude/2 2026-07-06 18:19:58 +01:00
librelad
10846b120e perf(backup): fetch snapshots once per location, filter in jq — kill the O(apps×locations) restic storm
The backup-data refresh in the WebUI update chain shelled out to
`restic snapshots` once PER APP (dashboard, app-status) and once per
(host, app) (migrate) — every call a `sudo`+`restic` spawn that re-opens
and decrypts the repo. With N apps and L locations that's O(N·L) (app-status)
plus O(hosts·apps·L) (migrate) spawns on every update — the silent stall
after "Refreshed backup dashboard data...".

One `restic snapshots --json` already returns the whole repo, so fetch it
ONCE per location and derive the per-app / per-host / system views in jq.
Validated against a live restic 0.19 repo: the jq filter reproduces
`restic snapshots --tag <t> --host <h>` exactly (incl. empty-host = all
hosts, and newest = last), and count/latest extraction matches the old grep.

- dashboard: one primary-location fetch → all app tiles + system status
  (was N snapshot calls + 1).
- app-status: one fetch per location → filter per app (was N×L calls);
  also de-recursed into a flat app loop.
- migrate: filter the already-fetched per-location list (was a spawn per
  host×app). Fixes a latent bug too — `grep -oc` over single-line JSON
  always reported a count of 1; jq `length` is now accurate.

restic snapshot spawns per refresh drop from ~O(N·L + hosts·apps·L) to ~O(L).
`restic stats` (still run per location in locations + dashboard) is a
separate, non-multiplicative cost left untouched to preserve its semantics.

Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:19:58 +01:00
librelad
c3315d9fbf Merge claude/2 2026-07-06 17:58:38 +01:00
librelad
e168496824 perf(gluetun): throttle the provider-list fetch off the WebUI-update hot path
webuiLibrePortalUpdate runs the per-app refresh hooks on every update (the
task processor fires it repeatedly), and gluetun's hook silently pulls a
~7MB servers.json from GitHub each time. The hook's stdout is captured by
`result=$($_hook)`, so on a slow link the update just sits with no output
right after "Generated apps-tools.json..." — it reads as a freeze.

- Throttle the fetch to once per CFG_GLUETUN_PROVIDERS_REFRESH_HOURS (24h
  default; 0 = manual-only). Direct callers (Tools refresh, install hook)
  pass GLUETUN_PROVIDERS_FORCE=1 to still get a guaranteed pull.
- curl --compressed (this JSON gzips ~7x) + --connect-timeout 15 so a slow
  or dead link shrinks/fails fast instead of stalling every update.
- Print "Refreshing <app> WebUI data..." from the updater loop (outside the
  output capture) so the step is visible instead of looking hung.

Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 17:58:38 +01:00
librelad
b93aa78d9c Merge claude/2 2026-07-05 20:33:21 +01:00
librelad
6dd729d0ac fix(webui/admin): paint page header immediately, confine spinner to content
The Admin Dashboard (OverviewPage) and Admin System (SystemPage) boards each
blanked the whole content pane to a bare headerless lpLoadingBox() on init(),
then rendered the .page-header + panel only once data arrived. So clicking
either tab flashed a full-pane spinner with no indication of which page you'd
landed on, then everything appeared at once.

Extract the page header into a _headerHtml() helper on each page and render it
up front in both the loading state and render(). Now navigating to a board
paints its header (breadcrumb + title + description) instantly and the spinner
is confined to the .admin-panel beneath it — the header stays put and only the
content swaps in when the fetch resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-05 20:33:21 +01:00
librelad
d96a9646bc Merge claude/1 2026-07-05 20:25:21 +01:00
librelad
f4913422fb refactor(catalog): rename the 'marketplace' app to 'libreportal_catalog'
'Marketplace' read as a place to buy things; this is a free, self-hosted app
catalog, so rename the app to LibrePortal Catalog. Slug is libreportal_catalog
(underscore — the slug becomes a CFG_<SLUG>_ prefix and a bash identifier via
declare "${app_name}=i"; a dash would break install). Docker-facing names use
dashes (libreportal-catalog-service / hostname libreportal-catalog), declared
explicitly in the PORT config + compose, not derived from the slug.

- containers/marketplace/ -> containers/libreportal_catalog/ (+ .config, .svg, hook file)
- CFG_MARKETPLACE_* -> CFG_LIBREPORTAL_CATALOG_*, APP_NAME + TITLE + PORT_1 updated
- install hook fn marketplace_install_post_setup -> libreportal_catalog_install_post_setup
- served browse site reworded Marketplace -> Catalog, icon refs updated
- regenerated function_manifest.sh (autoload stub now points at the new file)

Not installed yet, so there is no live config/container/volume to migrate — the
cheapest moment to rename. The client-side registry ('View full page on the
marketplace') wording is a separate subsystem and left unchanged for now.

Signed-off-by: librelad <librelad@digitalangels.vip>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:25:21 +01:00
librelad
d0168ca43e Merge claude/2 2026-07-05 19:48:23 +01:00
librelad
f8e1072d1e fix(de-sudo): skip runtime dockerinstall-password re-sync (twin of 9050a8c)
start_scan.sh runs updateDockerInstallPassword every system scan, doing
`sudo passwd $CFG_DOCKER_INSTALL_USER` via runSystem. Model A's scoped
sudoers grants only LP_HELPERS/LP_SYSTEM + run-as-install-user — not passwd
— so at runtime (manager, non-root) it fails exit 1 every scan. This is the
exact sibling of the updateDockerSudoPassword failure fixed in 9050a8c; that
guard was added to the manager/sudo user but the dockerinstall user was
missed, so error_report.log kept logging "Updating the password for the
dockerinstall user" on every scan.

The password is set at install (root path, startPreInstall →
installDockerRootlessUser) and the rootless docker user is driven by tooling,
not a password login, so the runtime re-sync is legacy + impossible under
de-sudo. Guard it to skip unless EUID 0, mirroring the sudo-pass fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-05 19:48:22 +01:00
librelad
caf90bfdfa Merge claude/1 2026-07-05 12:43:12 +01:00
librelad
868b0d3c24 fix(webui/setup): darken the setup-progress banner so it reads as a solid card
The setup-in-progress banner floats over arbitrary page content (the aurora
gradient, apps grid, task logs) but filled at rgba(--bg-rgb, 0.45) with no
backdrop blur, so the page bled through and it looked washed-out. Mirror the
.notification treatment already used for floating toasts: a near-solid panel
(0.85) over a frosted backdrop, plus a lift shadow. Stays theme-aware via
--bg-rgb (near-black on dark themes, near-white on light).

Signed-off-by: librelad <librelad@digitalangels.vip>

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:43:12 +01:00
librelad
e6df7be1ca Merge claude/2 2026-07-04 23:14:35 +01:00
librelad
ae39bba93f feat(marketplace/site): hash-route the views (#view=submit) — shareable + bookmarkable
The pins now update the hash so How it works / Submit / Submissions are
deep-linkable; the #<slug> app-focus link still works. One parseHash()
distinguishes them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 23:14:35 +01:00
librelad
d1caee0b54 Merge claude/2 2026-07-04 23:10:21 +01:00
librelad
01cd6c3f9d feat(marketplace/site): How it works, Submit an app + Submissions views
Turns the site into a small multi-view app. Three pinned nav buttons above
the search (App Center pin style) switch the main pane:
- How it works: the former footer text, expanded into cards (add an app,
  point your box here, how trust works, run your own).
- Submit an app: the PR-based flow (fork → drop-in app folder → PR → reviewed
  + signed → published with a community badge + your name). No uploads/accounts.
- Submissions: renders a static submissions.json (operator-published, like the
  catalog) — author names, status badges (pending/review/merged), PR links,
  a pending-count badge on the pin, graceful empty/loading states.

Category/search return to browse; #<slug> deep-link still focuses one app.
REPO_URL is overridable via <meta name=lp-repo>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 23:10:21 +01:00
librelad
06164fedd4 Merge claude/2 2026-07-04 22:54:06 +01:00
librelad
a8ac2f6dce feat(release): make_app.sh re-emits meta.featured (dropped in the section revert)
The featured passthrough was removed when the dedicated-section commit was
reverted; the Featured sidebar row needs it. spec.json {"featured":true} now
sets meta.featured on the published app again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:54:06 +01:00
librelad
8746448cbc Merge claude/2 2026-07-04 22:50:05 +01:00
librelad
855d698486 feat(marketplace/site): Featured sidebar row + match App Center font/size/icon/bg
- Add a Featured sidebar row (gold star, publisher-curated meta.featured),
  filters to featured apps; only shown when the catalog has any.
- Exact App Center font stack + 16px category text (was system-ui/14px).
- 'All' (and Featured) render as inline currentColor glyphs so the All icon
  is white like the WebUI — all.svg uses currentColor and rendered dark as an
  <img>; the fixed-blue category icons stay <img>.
- Apply --sidebar-bg to the sidebar itself too (double layer, matching the App
  Center) so the sidebar tint matches instead of showing more aurora through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:50:05 +01:00
librelad
223dc1e225 Merge claude/2 2026-07-04 22:40:07 +01:00
librelad
26e099c25f fix(marketplace/site): define the sidebar tokens so hover + separators match
--surface-hover and --sidebar-border weren't defined in the site's :root (and
--sidebar-bg had a made-up value), so the category hover/active background and
the row separators resolved to nothing — the sidebar looked flat vs the App
Center. Set them to the exact nebula values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:40:07 +01:00
librelad
fdf9dd6879 Merge claude/2 2026-07-04 22:27:57 +01:00
librelad
3be0e340ea fix(marketplace/site): keep the empty focus bar hidden (class display vs [hidden])
.focusbar's display:flex was overriding the hidden attribute, leaving a thin
empty bar under the status strip when no app was focused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:27:57 +01:00
librelad
ace415d654 Merge claude/2 2026-07-04 22:23:28 +01:00
librelad
c0133cd437 feat(marketplace): match the App Center sidebar + simplify the app name/blurb
Website sidebar now mirrors the App Center (sidebar.css): a 220px full-height
column, full-width category rows with bottom-border separators and the real
per-category icons (bundled into the site by the install hook from the live
frontend), and the same search box — instead of the rounded pills + generic
circles + count badges it had. Marketplace app copy shortened to peer style:
description 'App Catalog & Registry', one-sentence long description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:23:28 +01:00
librelad
8e08e85db3 Merge claude/2 2026-07-04 22:04:46 +01:00
librelad
cc36d86e57 feat(webui/apps): registry Add opens a detail modal with a marketplace link
Replaces the extra on-card marketplace tags (the separate Official trust
badge) with a richer, more elegant flow: a registry card keeps a single
'Available' pill, and clicking Add (or the card) opens a modal outlining the
app — full description, publisher + trust, version, the add command, and a
'View full page on the marketplace ↗' link to the app's page on the source
it came from — with the actual add as the confirm. Gives a beat to review a
community app before pulling it in.

- webui_registry_scan.sh re-emits source{base,channel}; loadApps stashes it
  (window.registryCatalogSource) + carries per-app version.
- openRegistryDetails() builds the eo-modal; addRegistryApp() is now just the
  task dispatch (the modal's confirm / a fallback).
- The marketplace website supports a #<slug> deep-link (focus one app, with a
  'Show all' bar), so the modal's link lands on that app's page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 22:04:46 +01:00
librelad
88bdb4d63a Merge claude/2 2026-07-04 21:55:54 +01:00
librelad
8e7537d717 feat(marketplace/site): replicate the App Center design on the browse website
The standalone marketplace site now mirrors the App Center it feeds: nebula
aurora background, the same app-card grid (70px icon, title, description /
category / Available / Official tags, italic long description, indigo Add
button), a left category sidebar with search, and a top bar. Client-rendered
from the same signed index.json; the Add button copies
'libreportal app add <slug>'. The status strip probes for a detached
signature rather than asserting verification (boxes verify, not the site).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:55:54 +01:00
librelad
cac431ac21 Merge claude/2 2026-07-04 21:50:57 +01:00
librelad
cfb3a4f755 revert: drop the dedicated Marketplace section — keep catalog merged into the grid
Reverts a4e65df. The maintainer prefers the more fluid model where registry
apps flow into the App Center grid as 'Available — Add' cards alongside
installed/local apps, rather than a separate /apps/marketplace destination.
Restores the Stage-4 merged-grid behaviour (loadApps merges
registry_catalog.json; createAppCard renders registry cards; addRegistryApp
dispatches app_add). The make_app.sh featured passthrough + the generator's
featured/source fields go with it (unused by the merged grid).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:50:57 +01:00
librelad
81b11e5675 Merge claude/2 2026-07-04 21:38:12 +01:00
librelad
a4e65df77f feat(webui/marketplace): dedicated Marketplace section in the App Center
Splits the catalog out of the main grid into its own destination, the
WordPress 'Add Plugins' vs 'Installed Plugins' model: the grid is now
purely 'your apps' (local definitions), and the new Marketplace section is
'get more apps' (the remote signed catalog).

- New MarketplacePage (components/apps/marketplace/) mounts at
  /apps/marketplace inside the apps feature (same sub-dispatch pattern as
  /apps/overview — no new top-level component). Pinned sidebar entry with a
  live 'available to add' count badge.
- Status strip: signed/unsigned, available + catalog counts, serial, source
  base+channel, freshness, and a Refresh that re-runs the host-side registry
  scan via updater_check.
- Publisher-curated Featured shelf (meta.featured, set at publish time — no
  tracking/popularity), category chips + search, per-app detail modal
  (long description, publisher/trust/version, add command), and the chained
  Add & set-up flow: dispatch app_add, and when the definition lands, hand
  off to the app's config/install page.
- State-aware cards: Available (Add) / Added (Set up →) / Installed (Open).
- Backend: make_app.sh passes through meta.featured; webui_registry_scan.sh
  emits featured + source{base,channel} in registry_catalog.json.
- Removed the grid's registry-merge + registry card path + its CSS (moved to
  the namespaced marketplace surface); app_add task wiring + completion
  handler retained and reused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:38:12 +01:00
librelad
d79a999c4f Merge claude/1 2026-07-04 21:34:05 +01:00
librelad
b17ecdc192 fix(webui/overview): restore inner padding on the recessed tab body
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:34:05 +01:00
librelad
6a1e4e4795 Merge claude/1 2026-07-04 21:30:22 +01:00
librelad
31ce56c022 fix(webui/overview): keep the recessed tab-body surface, only drop its inset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:30:22 +01:00
librelad
06d938e58a Merge claude/1 2026-07-04 21:24:42 +01:00
librelad
baa1ed4d8d fix(webui/overview): uniform tab rhythm + rounded sub-tab strip corners
- .tabs-list now rounds its own top corners (12px, matching .tab-navigation);
  the first/last .tab-button radii were invisible against the bar's square
  background on the Backups/Migrate/Config sub-tab strips.
- All five fleet tabs share one rhythm under the header divider: a single
  16px gap, no extra inset — .ov-tab-body drops its recessed margin/padding
  box, and the Backups/Migrate sub-tab strips gain the matching gap.
- The Peers list keeps its recessed panel via .peers-body (in-card recipe),
  no longer riding the flattened .ov-tab-body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:24:42 +01:00
librelad
a8f7f53f4e Merge claude/1 2026-07-04 21:08:03 +01:00
librelad
aef0c15726 refactor(webui/backups): backup center sub-tabs use canonical tabs-wrapper
The embedded backup center's fragment kept its standalone-page skeleton
(.container + .sidebar restyled by a pile of overview.css overrides).
.container's fixed viewport height inflated the Backups tab pane to
~100vh, stretching the pane surface far past the content and under the
footer buttons, and the restyled strip never matched the app-detail
Config sub-tabs.

Rebuild backup-content.html on the canonical sub-tab idiom instead —
.tabs-wrapper > .tabs-list (emoji tab-buttons) + .tabs-content card,
with a .backup-actions footer below the card mirroring .config-actions.
The bespoke overview.css restyle block, the nebula special-cases, the
embed's id-stripping and BackupPage's dead page-header updater all fall
away; the export menu now opens upward from the footer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-04 21:08:03 +01:00
librelad
9830382a4a Merge claude/2 2026-07-03 21:46:29 +01:00
librelad
7fd4a1c29e fix(marketplace/site): hide the light scrollbar strip on the add-command snippets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 21:46:29 +01:00
librelad
0c8b13d2f1 Merge claude/2 2026-07-03 21:43:59 +01:00
librelad
c44e17967f fix(marketplace): converge the nginx.conf bind-mount source in the install hook
The generic installer copies only config+compose into the live dir, so the
compose's nginx.conf file mount had no source — and a premature container
start leaves a directory placeholder at the mount path. The post_setup hook
now removes the placeholder and copies the file from the definition, so
(re)install converges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 21:43:59 +01:00
librelad
58541b1fe3 Merge claude/2 2026-07-03 21:40:33 +01:00
librelad
897f514735 feat(marketplace): the marketplace server ships as a dev-mode LibrePortal app
containers/marketplace — nginx:alpine app (standard drop-in contract:
config + tagged compose + icon + install hook) whose docroot serves BOTH
halves of the marketplace: the signed catalog channel tree (index.json /
payloads, published into data/<channel>/ by the release tools) and a
self-contained client-rendered browse site over the same file (search,
category chips, trust badges, copyable 'libreportal app add <slug>' —
no third-party assets, no backend, no build step). The official
marketplace is an instance of this app; self-hosting one = installing it
and pointing CFG_RELEASE_BASE_URL at it. Boxes only ever trust the
minisign signature on the catalog, never the website.

New generic gating convention: CFG_<APP>_DEV_ONLY=true keeps an app out
of the App Center grid unless Developer Mode is on (CFG_DEV_MODE, the
same flag the **DEV** config-field filter uses); an installed dev-only
app always stays visible. The marketplace app is the first user.

Cache policy: catalog/channel manifests no-cache; payloads short
revalidating cache (same-id re-publish); version-pinned release
artifacts immutable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 21:40:33 +01:00
librelad
9b5a4d276d Merge claude/2 2026-07-03 21:35:30 +01:00
librelad
52be5968e9 docs: registry slice shipped — §8.4b bundle spec addendum + §8.7 status + publish guide
Roadmap: Phase 6 entry (app bundles + Browse-&-Add, built 2026-07-03),
the app-bundle format addendum (meta object, tarball contract, collision
policy, trust-then-quarantine ordering), deferred bullet trimmed to what
actually remains (community quarantine, taps, theme/component, canary
countersign) with a pointer to marketplace-website.md for the website
layer. development.md gains the 'Publish an app to the marketplace
catalog' section (make_app.sh flow + the app add loop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-07-03 21:35:30 +01:00