From 2fad10c8de58a29275cecac0e5bf98be898efcc8 Mon Sep 17 00:00:00 2001 From: yaya Date: Tue, 6 Oct 2026 08:00:08 +0100 Subject: [PATCH] Show DATUM login credentials and document complete app launch requirements --- apps/DEVELOPMENT.md | 2 + .../src/api/rpc/package/install.rs | 20 +++++++++ docs/app-developer-guide.md | 42 +++++++++++++++++++ docs/developer-guide.md | 2 + .../src/stores/__tests__/appLauncher.test.ts | 16 +++++++ neode-ui/src/stores/appLauncher.ts | 1 + 6 files changed, 83 insertions(+) diff --git a/apps/DEVELOPMENT.md b/apps/DEVELOPMENT.md index 53a364ed..0e37f8e7 100644 --- a/apps/DEVELOPMENT.md +++ b/apps/DEVELOPMENT.md @@ -91,3 +91,5 @@ Adding a new app requires updates in multiple places: ## Port Assignments See [PORTS.md](./PORTS.md) for complete mapping. Dev ports are offset by +10000. + +Before submitting an app, complete **Launch acceptance: credentials, signer, and HTTP nodes** in `docs/app-developer-guide.md`. A generated password needs an authenticated credential interstitial; native Nostr login needs a tested first-launch chooser. Container health alone is not launch acceptance. diff --git a/core/archipelago/src/api/rpc/package/install.rs b/core/archipelago/src/api/rpc/package/install.rs index ae2570e2..419c92c0 100644 --- a/core/archipelago/src/api/rpc/package/install.rs +++ b/core/archipelago/src/api/rpc/package/install.rs @@ -1892,6 +1892,26 @@ autopilot.active=false\n", })); } + if app_id == "datum" { + // This is the same platform-owned secret injected into DATUM and + // Gashboard. Never publish it in the manifest or a UI fallback. + let password = tokio::fs::read_to_string( + self.config.data_dir.join("secrets/datum-admin-password"), + ) + .await + .context("DATUM credentials are not available yet; wait for installation to finish")?; + let password = password.trim(); + anyhow::ensure!(!password.is_empty(), "DATUM administrator password is empty"); + return Ok(serde_json::json!({ + "title": "DATUM Gateway login", + "description": "Use this password when DATUM asks you to unlock configuration. In Config, set your Bitcoin payout address. Point miners at this node's IP address on Stratum port 23334 (stratum+tcp://NODE-IP:23334). Gashboard connects automatically.", + "credentials": [ + { "label": "Username", "value": "admin" }, + { "label": "Password", "value": password, "sensitive": true } + ] + })); + } + if app_id == "photoprism" { return Ok(serde_json::json!({ "title": "PhotoPrism credentials", diff --git a/docs/app-developer-guide.md b/docs/app-developer-guide.md index d53372f0..a8464413 100644 --- a/docs/app-developer-guide.md +++ b/docs/app-developer-guide.md @@ -789,3 +789,45 @@ adapter instead of reporting a successful installation with no usable backend. For example, Angor Indexer requires `mempool-api` (shown to users as its owning Mempool app), shares that index and declares only an `api` interface. API-only interfaces belong in Services and do not generate browser launch buttons. + + +## Launch acceptance: credentials, signer, and HTTP nodes + +An app is not ready just because its container is healthy. Before submission, +verify its first launch from My Apps, app details, a browser tab and Companion: + +- Declare a real UI interface and stage the app icon in the web UI assets. Check + the installing tile as well as the completed installation: a UI app belongs + in My Apps and must not appear as an iconless service. +- If the app needs a password or first-run token, provide the shared credential + interstitial **before** launch, with copy controls and setup instructions. + Generating a secret in the manifest does not register this screen. Implement + `package.credentials` in `core/archipelago/src/api/rpc/package/install.rs` + and register the app in `CREDENTIAL_INTERSTITIAL_APPS` in + `neode-ui/src/stores/appLauncher.ts`. Both changes require a platform update; + app-only sideloads cannot add this RPC integration. File Browser and DATUM + are examples. Read generated secrets from the node's configured data directory; + never put them in a manifest, static browser bundle, default-password fallback, + logs, screenshots, or test reports. Keep the RPC dashboard-authenticated. +- Explain initial configuration and client connection details. For DATUM this + includes its administrator password, Bitcoin payout address, and the node's + Stratum address on port 23334. App-to-app connections use container DNS + (`http://datum:7152`), never a container IP address. +- Native Nostr apps should open the host identity chooser once on an explicit + app launch when unauthenticated, then finish the app's ordinary NIP-07 login. + The app may call `archipelagoNostr.selectIdentity()` at initial mount for this + first-launch flow; this is the exception to the routine-signing rule above. + Do not assume the platform's eager-picker app list contains a new app ID. + Consume an already selected identity through `getSelectedIdentity()` or the + sticky `onIdentitySelected()` subscription to avoid a second chooser. + Preserve manual login/account switching, external extensions and remote + signers. Cancellation must leave a usable login screen without reopening a + prompt loop; signing still requires the platform's normal consent. +- Test the actual HTTP LAN/Tailscale address, not only localhost or HTTPS. + `crypto.subtle` and clipboard APIs may be unavailable on those addresses. + Keep authenticated encryption: use a vetted compatible implementation when + WebCrypto is absent, and secure randomness (`crypto.getRandomValues`). Test + existing-message decryption, tamper rejection and an HTTP round trip. +- Verify first launch, cancellation/retry, reload, owner/viewer authorization, + and persisted data after app recreation. Use the shared browser-check suite + outside the repository; record which nodes and browser engines were tested. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 184d5233..43313c58 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -1,5 +1,7 @@ # Archipelago Developer Guide +For new apps, start with `docs/app-developer-guide.md` and complete its **Launch acceptance: credentials, signer, and HTTP nodes** checklist. Packaging includes My Apps presentation, login/first-run credential handoff, native signer startup, and real HTTP-node testing—not only a working container. + ## Project Structure ``` diff --git a/neode-ui/src/stores/__tests__/appLauncher.test.ts b/neode-ui/src/stores/__tests__/appLauncher.test.ts index 68bf8ee7..5ced5414 100644 --- a/neode-ui/src/stores/__tests__/appLauncher.test.ts +++ b/neode-ui/src/stores/__tests__/appLauncher.test.ts @@ -143,6 +143,22 @@ describe('useAppLauncherStore', () => { expect(mockWindowOpen).not.toHaveBeenCalled() }) + it('shows DATUM generated credentials before opening its embedded UI', async () => { + mockRpcCall.mockResolvedValueOnce({ + title: 'DATUM Gateway login', + credentials: [{ label: 'Password', value: 'fixture-only-password', sensitive: true }], + }) + const store = useAppLauncherStore() + store.openSession('datum') + await vi.waitFor(() => expect(store.credentialPrompt.loading).toBe(false)) + expect(store.credentialPrompt.show).toBe(true) + expect(store.credentialPrompt.credentials[0]?.value).toBe('fixture-only-password') + expect(store.panelAppId).toBeNull() + expect(mockWindowOpen).not.toHaveBeenCalled() + store.continueCredentialLaunch() + expect(store.panelAppId).toBe('datum') + }) + it('gates a Home-style Portainer launch until its first-run token is shown', async () => { mockRpcCall.mockResolvedValueOnce({ title: 'Portainer first-run token', diff --git a/neode-ui/src/stores/appLauncher.ts b/neode-ui/src/stores/appLauncher.ts index bb8e94fd..27493fef 100644 --- a/neode-ui/src/stores/appLauncher.ts +++ b/neode-ui/src/stores/appLauncher.ts @@ -93,6 +93,7 @@ const NEW_TAB_APP_IDS = new Set([ * original synchronous user gesture. Portainer is dynamic (first-run only); * File Browser and PhotoPrism have stable fallback credentials. */ export const CREDENTIAL_INTERSTITIAL_APPS = new Set([ + 'datum', 'filebrowser', 'photoprism', 'portainer',