diff --git a/CHANGELOG.md b/CHANGELOG.md index fe985cd5..3a8a1a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.8.8-alpha (2026-09-01) + +- **SSH over the mesh is now a first-class setting.** Settings gains an "SSH over mesh" card: off by default, and when you allow it the node's mesh firewall opens port 22 — either to every mesh peer (behind an explicit "I understand" confirmation, because that's a real exposure) or only to the mesh addresses you list. The rule is owned by the node (the `90-ssh.nft` drop-in), so it survives upgrades and daemon reinstalls, and the card tells you up front whether sshd is running, whether it listens on IPv6 (the mesh is IPv6-only — this is what a broken attempt looks like before it happens), and whether password login is on (keys-only is the recommended pairing). From Termux on your phone, `fipssh @` connects once the toggle is on — the npub is the durable address, and the command is shown with a copy button on the card. + +- **The App Store now lists apps — not parts of apps.** The signed catalog carries every manifest because the node's update layer needs their pins, and the store briefly listed them all: Mempool API, LND UI, Bitcoin UI, the Pine voice engines, the IndeeHub and Immich backends, the mesh router and friends. Components are hidden from the store listing (they still appear where they belong — the Services tab of My Apps, once installed), and four entries that never earned a tile are gone outright: MorphOS server (old), the Web5 DID wallet, Lightning Stack (an untracked upstream bundle — LND covers the need), and CryptPad (never tested). + +- **App icons now persist everywhere, in the proper container style.** Two fixes: installed apps render the icon from their own manifest — Cuprate no longer falls back to the generic A-mark on its Services tile — and the store grids (the Discover page) apply the same icon container treatment (backdrop, border, shadow) as My Apps, the detail pages, and Home. Manifest-declared UI apps also classify correctly again: Alby Hub installs into My Apps with a working tile, not into Services, because a probe miss no longer buries an app the manifest itself says has a frontend. + +- **Installing from the store keeps you on the store page.** The install progress lives on the tile itself and the app appears in My Apps when it lands — no more being yanked to My Apps mid-browse. + ## v1.8.7-alpha (2026-08-31) - **What's New really does stop at v1.8.0 now.** The first correction removed old generated release blocks but missed six much older hand-written v1.2 sections at the bottom of the modal. Those sections are gone, and the release check now recognizes and rejects that legacy format too, so the history floor cannot falsely pass again. diff --git a/neode-ui/src/views/Marketplace.vue b/neode-ui/src/views/Marketplace.vue index d7c820cd..7ce2a00b 100644 --- a/neode-ui/src/views/Marketplace.vue +++ b/neode-ui/src/views/Marketplace.vue @@ -515,8 +515,10 @@ async function installApp(app: MarketplaceApp) { } queueInstall(app) - toast.info("Installing " + (app.title ?? app.id) + " - check My Apps") - router.push('/dashboard/apps').catch(() => {}) + // Stay on the store page: the tile itself shows install progress via the + // global watcher, and a forced jump to My Apps yanked the user out of the + // page they were deliberately browsing. + toast.info("Installing " + (app.title ?? app.id) + " — it will appear in My Apps") try { const installUrl = app.url || app.manifestUrl || app.s9pkUrl @@ -540,8 +542,8 @@ async function installCommunityApp(app: MarketplaceApp) { } queueInstall(app) - toast.info("Installing " + (app.title ?? app.id) + " - check My Apps") - router.push('/dashboard/apps').catch(() => {}) + // Stay on the store page (see installApp). + toast.info("Installing " + (app.title ?? app.id) + " — it will appear in My Apps") try { const installParams: Record = { id: app.id, dockerImage: app.dockerImage, version: app.version } diff --git a/neode-ui/src/views/apps/appsConfig.ts b/neode-ui/src/views/apps/appsConfig.ts index d0e1e385..ff8217da 100644 --- a/neode-ui/src/views/apps/appsConfig.ts +++ b/neode-ui/src/views/apps/appsConfig.ts @@ -6,33 +6,17 @@ import { PackageState, type PackageDataEntry } from '@/types/api' import { matchPageScheme, resolveAppUrl } from '../appSession/appSessionConfig' import { portIsGateFronted } from '../discover/curatedApps' import { isAutoTabApp } from '@/utils/autoTabApps' +import { + SERVICE_NAMES as SHARED_SERVICE_NAMES, + isServiceContainer as sharedIsServiceContainer, +} from './serviceNames' export type AppsTab = 'apps' | 'websites' | 'services' -// Service container name patterns (backend/infra, not user-facing) -export const SERVICE_NAMES = new Set([ - 'dwn', 'archy-mempool-db', 'archy-btcpay-db', 'archy-nbxplorer', 'archy-tor', - // Headless backends with no user-facing UI: the Fedimint ecash client daemon, - // the Nostr relay, and the Meshtastic LoRa daemon (its chat UI lives in the - // built-in Mesh tab) belong in Services, not My Apps. - 'fedimint-clientd', 'nostr-rs-relay', 'meshtastic', - 'immich_postgres', 'immich_redis', - // immich is now a manifest-driven stack (app_id-named, hyphen). The server is - // the launcher app; postgres/redis are backends → Services. - 'immich-postgres', 'immich-redis', - 'mysql-mempool', 'mempool-api', 'archy-mempool-web', - 'archy-bitcoin-ui', 'archy-lnd-ui', 'archy-electrs-ui', - 'bitcoin-ui', 'lnd-ui', 'electrs-ui', - 'indeedhub-postgres', 'indeedhub-redis', 'indeedhub-minio', - 'indeedhub-api', 'indeedhub-ffmpeg', - 'indeedhub-relay', 'indeedhub-build_api_1', 'indeedhub-build_ffmpeg-worker_1', - 'indeedhub-build_postgres_1', 'indeedhub-build_redis_1', 'indeedhub-build_minio_1', - 'indeedhub-build_minio-init_1', 'indeedhub-build_relay_1', - // Pine voice-assistant stack: the two Wyoming engines are backends (STT/TTS) - // reached by Home Assistant over host.containers.internal — the user-facing - // card is "pine" (the setup/status launcher), so the engines go to Services. - 'pine-whisper', 'pine-piper', 'pine-openwakeword', -]) +// Re-exported for every existing caller — the canon moved to serviceNames.ts +// so the App Store's catalog merge can share it without a circular import. +export const SERVICE_NAMES = SHARED_SERVICE_NAMES +export const isServiceContainer = sharedIsServiceContainer const INTERNAL_TOOLING_NAMES = new Set([ 'buildx_buildkit_default', @@ -43,16 +27,7 @@ export function isInternalToolingPackage(id: string, pkg?: PackageDataEntry): bo return INTERNAL_TOOLING_NAMES.has(id) || INTERNAL_TOOLING_NAMES.has(manifestId) || id.startsWith('buildx_buildkit') || manifestId.startsWith('buildx_buildkit') } -export function isServiceContainer(id: string): boolean { - if (SERVICE_NAMES.has(id)) return true - if (id.startsWith('indeedhub-build_')) return true - if (id.startsWith('archy-')) return true - // Backend naming patterns that never carry a user-facing UI: databases and - // caches. Safe to classify by suffix (a database is never a launcher). - if (/-(db|postgres|postgresql|redis|valkey|mariadb|mysql|cache)$/.test(id)) return true - if (id.endsWith('_db')) return true - return false -} +// isServiceContainer now re-exports the shared canon (serviceNames.ts). export function isServicePackage(id: string, pkg?: PackageDataEntry): boolean { if (isServiceContainer(id)) return true diff --git a/neode-ui/src/views/apps/serviceNames.ts b/neode-ui/src/views/apps/serviceNames.ts new file mode 100644 index 00000000..15c17922 --- /dev/null +++ b/neode-ui/src/views/apps/serviceNames.ts @@ -0,0 +1,62 @@ +/** + * Canonical internal/service app-id classification — the shared source for + * every surface that must decide "is this a user-facing app or a component + * of one": the My Apps/Services tabs (appsConfig) and the App Store listing + * (the signed-catalog merge in discover/curatedApps must not publish + * components as standalone store apps). + * + * Keep this module dependency-free: it is imported from both the apps view + * and the catalog module (which must not import view code that reads the + * store — circulars). + */ + +// Service container ids (backend/infra, not user-facing). Mirrors the +// SERVICE_NAMES set that used to live in appsConfig.ts verbatim. +export const SERVICE_NAMES = new Set([ + 'dwn', 'archy-mempool-db', 'archy-btcpay-db', 'archy-nbxplorer', 'archy-tor', + // Headless backends with no user-facing UI: the Fedimint ecash client daemon, + // the Nostr relay, and the Meshtastic LoRa daemon (its chat UI lives in the + // built-in Mesh tab) belong in Services, not My Apps. + 'fedimint-clientd', 'nostr-rs-relay', 'meshtastic', + 'immich_postgres', 'immich_redis', + // immich is now a manifest-driven stack (app_id-named, hyphen). The server is + // the launcher app; postgres/redis are backends → Services. + 'immich-postgres', 'immich-redis', + 'mysql-mempool', 'mempool-api', 'archy-mempool-web', + 'archy-bitcoin-ui', 'archy-lnd-ui', 'archy-electrs-ui', + 'bitcoin-ui', 'lnd-ui', 'electrs-ui', + 'indeedhub-postgres', 'indeedhub-redis', 'indeedhub-minio', + 'indeedhub-api', 'indeedhub-ffmpeg', + 'indeedhub-relay', 'indeedhub-build_api_1', 'indeedhub-build_ffmpeg-worker_1', + 'indeedhub-build_postgres_1', 'indeedhub-build_redis_1', 'indeedhub-build_minio_1', + 'indeedhub-build_minio-init_1', 'indeedhub-build_relay_1', + // Pine voice-assistant stack: the two Wyoming engines are backends (STT/TTS) + // reached by Home Assistant over host.containers.internal — the user-facing + // card is "pine" (the setup/status launcher), so the engines go to Services. + 'pine-whisper', 'pine-piper', 'pine-openwakeword', +]) + +// Node-bundled internals that are real manifests (so they surface in the +// signed catalog's manifest list) but are not store apps: the assistant and +// the bundled relay/UIs ship with the node itself, the DWN is internal +// plumbing, and the mesh router + CLN are components of other surfaces. +export const NODE_INTERNAL_IDS = new Set([ + 'aiui', 'fips-ui', 'strfry', 'web5-dwn', 'router', 'core-lightning', +]) + +/** Is this id a component/backend rather than a standalone user app? */ +export function isServiceContainer(id: string): boolean { + if (SERVICE_NAMES.has(id)) return true + if (id.startsWith('indeedhub-build_')) return true + if (id.startsWith('archy-')) return true + // Backend naming patterns that never carry a user-facing UI: databases and + // caches. Safe to classify by suffix (a database is never a launcher). + if (/-(db|postgres|postgresql|redis|valkey|mariadb|mysql|cache)$/.test(id)) return true + if (id.endsWith('_db')) return true + return false +} + +/** Should this app id appear in the App Store listing at all? */ +export function isStoreListedApp(id: string): boolean { + return !isServiceContainer(id) && !NODE_INTERNAL_IDS.has(id) +} diff --git a/neode-ui/src/views/discover/AppGrid.vue b/neode-ui/src/views/discover/AppGrid.vue index e2a9ea56..609a6eee 100644 --- a/neode-ui/src/views/discover/AppGrid.vue +++ b/neode-ui/src/views/discover/AppGrid.vue @@ -40,7 +40,7 @@ v-if="app.icon" :src="app.icon" :alt="app.title" - class="w-14 h-14 rounded-lg object-cover" + class="w-14 h-14 rounded-lg archy-app-icon" @error="handleImageError" />
diff --git a/neode-ui/src/views/discover/FeaturedApps.vue b/neode-ui/src/views/discover/FeaturedApps.vue index ea379975..4f4db194 100644 --- a/neode-ui/src/views/discover/FeaturedApps.vue +++ b/neode-ui/src/views/discover/FeaturedApps.vue @@ -24,7 +24,7 @@ v-if="app.icon" :src="app.icon" :alt="app.title" - class="w-20 h-20 rounded-xl object-cover flex-shrink-0" + class="w-20 h-20 rounded-xl archy-app-icon flex-shrink-0" @error="handleImageError" />
diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index df6be382..caa91003 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -1,4 +1,5 @@ import type { MarketplaceApp } from './types' +import { isStoreListedApp } from '../apps/serviceNames' const R = 'source.archipelago-foundation.org/lfg2025' @@ -48,10 +49,15 @@ export interface SignedAppEntry { } /** Convert the signed catalog's keyed entries into store-listing apps. - * Pure — unit-tested against the live catalog's shape (Cuprate). */ + * Pure — unit-tested against the live catalog's shape (Cuprate). + * Components and node internals never enter the listing: the signed catalog + * carries every manifest (including backends like mempool-api and the + * bundled UIs) because the node's update layer needs their pins, but the + * App Store publishes apps, not parts of apps. */ export function signedCatalogToApps(catalog: SignedAppCatalog): MarketplaceApp[] { const out: MarketplaceApp[] = [] for (const [id, entry] of Object.entries(catalog.apps || {})) { + if (!isStoreListedApp(id)) continue const app = entry.manifest?.app out.push({ id, @@ -200,7 +206,6 @@ export function getCuratedAppList(): MarketplaceApp[] { { id: 'grafana', title: 'Grafana', version: '10.2.0', description: 'Analytics and monitoring platform. Dashboards for your node metrics and system health.', icon: '/assets/img/app-icons/grafana.png', author: 'Grafana Labs', dockerImage: `${R}/grafana:10.2.0`, repoUrl: 'https://github.com/grafana/grafana' }, { id: 'searxng', title: 'SearXNG', version: '2024.1.0', description: 'Privacy-respecting metasearch engine. Search the internet without being tracked or profiled.', icon: '/assets/img/app-icons/searxng.png', author: 'SearXNG', dockerImage: `${R}/searxng:latest`, repoUrl: 'https://github.com/searxng/searxng' }, { id: 'ollama', title: 'Ollama', version: '0.5.4', description: 'Run AI models locally. Llama, Mistral, and more — on your hardware, completely private.', icon: '/assets/img/app-icons/ollama.png', author: 'Ollama', dockerImage: `${R}/ollama:latest`, repoUrl: 'https://github.com/ollama/ollama' }, - { id: 'cryptpad', title: 'CryptPad', version: '2024.12.0', description: 'End-to-end encrypted documents, spreadsheets, and presentations. Zero-knowledge collaboration.', icon: '/assets/icon/favico-black-v2.svg', author: 'XWiki SAS', dockerImage: `${R}/cryptpad:2024.12.0`, repoUrl: 'https://github.com/cryptpad/cryptpad' }, { id: 'nextcloud', title: 'Nextcloud', version: '29', description: 'Your own private cloud. File sync, calendars, contacts — all on your hardware.', icon: '/assets/img/app-icons/nextcloud.webp', author: 'Nextcloud', dockerImage: `${R}/nextcloud:29`, repoUrl: 'https://github.com/nextcloud/server' }, { id: 'vaultwarden', title: 'Vaultwarden', version: '1.30.0', description: 'Self-hosted password vault. Bitwarden-compatible with zero-knowledge encryption.', icon: '/assets/img/app-icons/vaultwarden.webp', author: 'Vaultwarden', dockerImage: `${R}/vaultwarden:1.30.0-alpine`, repoUrl: 'https://github.com/dani-garcia/vaultwarden' }, { id: 'jellyfin', title: 'Jellyfin', version: '10.8.13', description: 'Free media server. Stream your movies, music, and photos to any device.', icon: '/assets/img/app-icons/jellyfin.webp', author: 'Jellyfin', dockerImage: `${R}/jellyfin:10.8.13`, repoUrl: 'https://github.com/jellyfin/jellyfin' }, diff --git a/neode-ui/src/views/marketplace/marketplaceData.ts b/neode-ui/src/views/marketplace/marketplaceData.ts index 772a1f8f..be4ae0b7 100644 --- a/neode-ui/src/views/marketplace/marketplaceData.ts +++ b/neode-ui/src/views/marketplace/marketplaceData.ts @@ -247,17 +247,6 @@ export function getCuratedAppList(): MarketplaceApp[] { manifestUrl: undefined, repoUrl: 'https://github.com/ollama/ollama' }, - { - id: 'cryptpad', - title: 'CryptPad', - version: '2024.12.0', - description: 'End-to-end encrypted documents, spreadsheets, and presentations. Zero-knowledge collaboration.', - icon: '/assets/icon/favico-black-v2.svg', - author: 'XWiki SAS', - dockerImage: `${REGISTRY}/cryptpad:2024.12.0`, - manifestUrl: undefined, - repoUrl: 'https://github.com/cryptpad/cryptpad' - }, { id: 'nextcloud', title: 'Nextcloud', diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index f8bfe313..390995a8 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -362,6 +362,19 @@ init()
+ +
+
+ v1.8.8-alpha + September 1, 2026 +
+
+

SSH over the mesh is now a first-class setting. Settings gains an "SSH over mesh" card: off by default, and when you allow it the node's mesh firewall opens port 22 — either to every mesh peer (behind an explicit "I understand" confirmation, because that's a real exposure) or only to the mesh addresses you list. The rule is owned by the node (the 90-ssh.nft drop-in), so it survives upgrades and daemon reinstalls, and the card tells you up front whether sshd is running, whether it listens on IPv6 (the mesh is IPv6-only — this is what a broken attempt looks like before it happens), and whether password login is on (keys-only is the recommended pairing). From Termux on your phone, fipssh <user>@<node-npub> connects once the toggle is on — the npub is the durable address, and the command is shown with a copy button on the card.

+

The App Store now lists apps — not parts of apps. The signed catalog carries every manifest because the node's update layer needs their pins, and the store briefly listed them all: Mempool API, LND UI, Bitcoin UI, the Pine voice engines, the IndeeHub and Immich backends, the mesh router and friends. Components are hidden from the store listing (they still appear where they belong — the Services tab of My Apps, once installed), and four entries that never earned a tile are gone outright: MorphOS server (old), the Web5 DID wallet, Lightning Stack (an untracked upstream bundle — LND covers the need), and CryptPad (never tested).

+

App icons now persist everywhere, in the proper container style. Two fixes: installed apps render the icon from their own manifest — Cuprate no longer falls back to the generic A-mark on its Services tile — and the store grids (the Discover page) apply the same icon container treatment (backdrop, border, shadow) as My Apps, the detail pages, and Home. Manifest-declared UI apps also classify correctly again: Alby Hub installs into My Apps with a working tile, not into Services, because a probe miss no longer buries an app the manifest itself says has a frontend.

+

Installing from the store keeps you on the store page. The install progress lives on the tile itself and the app appears in My Apps when it lands — no more being yanked to My Apps mid-browse.

+
+
diff --git a/neode-ui/src/views/settings/SshOverMeshCard.vue b/neode-ui/src/views/settings/SshOverMeshCard.vue new file mode 100644 index 00000000..2817012a --- /dev/null +++ b/neode-ui/src/views/settings/SshOverMeshCard.vue @@ -0,0 +1,189 @@ + + +