Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
906fde6
pc2-node: service-type app support (needed for Elastos Node Manager)
May 28, 2026
fc88245
Elastos Node Manager — service-type app (requires #18)
May 29, 2026
094c6e0
Merge PR #18 (pc2-service-apps) into ENM integration branch
SashaMIT Jun 24, 2026
de5890a
Merge PR #19 (enm-app): Elastos Node Manager service + operator UI
SashaMIT Jun 24, 2026
0554949
harden(service-apps): fail-closed Ed25519 verify for type:service ins…
SashaMIT Jun 24, 2026
d12197a
test(service-apps): AppProcessManager lifecycle integration test
SashaMIT Jun 24, 2026
7f48dd3
feat(dapp-centre): device-compatibility gating (requirements.platform)
SashaMIT Jun 24, 2026
0fa024e
feat(dapp-centre): multi-arch ENM packaging + per-arch distribution v…
SashaMIT Jun 25, 2026
d081f7e
ci(enm): temporarily trigger build on feature branch (revert before m…
SashaMIT Jun 25, 2026
00498e0
chore(enm): revert temp branch CI trigger; fix packager assembly hint
SashaMIT Jun 25, 2026
0d9715a
fix(service-apps): same-origin backend proxy + correct session-DB path
SashaMIT Jun 25, 2026
c0d2eea
fix(auth): resolve doubled Authorization header to the live session
SashaMIT Jun 25, 2026
303722a
feat(dapp-centre): pre-install icons + real download progress
SashaMIT Jun 25, 2026
e469a2d
perf(server): gzip text responses with a stream-safe filter
SashaMIT Jun 26, 2026
8ef2fff
ci(enm): temp feature-branch trigger for release-prep build (revert b…
SashaMIT Jun 26, 2026
84dc08e
chore(enm): revert temp feature-branch CI trigger
SashaMIT Jun 26, 2026
958071d
docs(enm): stage production registry entry + flip procedure (not live)
SashaMIT Jun 26, 2026
5c9f4a5
chore(release): bump to 1.4.0 (ENM, multi-arch packaging, app-layer c…
SashaMIT Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .cursor/tasks/DAPP-CENTRE-INSTALL-UX/DAPP-CENTRE-INSTALL-UX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Task: dApp Centre install UX — pre-install icons + real download progress

**Task ID**: DAPP-CENTRE-INSTALL-UX
**Created**: 2026-06-25
**Status**: Review
**Priority**: Medium

## Description
Two platform-wide dApp Centre UX gaps surfaced while testing the ENM install on
the Jetson:

1. **No real app icon before install.** Store cards render a `gradient + category
glyph` placeholder; the real bundle logo only appears after install. Every
installable app is affected.
2. **Install progress feels stuck.** The progress bar jumps 5% → (stuck for the
whole download) → 100%, with no sense of where it's at.

## Background / root cause (from code reading)
- **Icon**: `registryAppToCatalog` (`src/backend/apps/app-center/index.html`)
only sets `icon` (a glyph key) + `gradient`. The renderer (`getCardIconHtml`,
`getStaffPickIconHtml`) already renders a real image via `app.builtInIcon`
(a `data:` / `https:` / `/…` URL) with the `.has-img` treatment — that's how
INSTALLED apps show their logo. Registry entries simply never carry an image.
- **Progress**: the whole pipeline already exists and is deployed —
`AppInstallService.install(manifest, cid, onProgress)` emits stage events,
the install route bridges them to Socket.io `install:progress`, `UIDesktop.js`
forwards them to iframes as `apps:installProgress`, and app-center already
consumes them via `updateInstallProgress`. THE GAP: `install()` calls
`fetchFromIPFS(effectiveCid)` **without** an `onProgress`, so during the
download (the long part) nothing is emitted — the bar holds at 5% until the
fetch completes and it jumps to `verifying, 55`. `IpfsService.pinRemoteCID`
already supports `{ onProgress }` (used by ContentSeedingService).

## Requirements
- Store cards + staff-pick + install modal show the real app logo BEFORE install
when the catalog entry provides one (`iconDataUrl`).
- The install bar moves during the download, showing a real % and MB counter.
- No regression for apps without an icon (fall back to gradient+glyph).
- Backend change limited to threading progress; no change to the security gates
(signature verify, platform gate, owner gate) — out of scope per security.mdc.

## Implementation Plan
- [ ] Backend: `AppInstallService.fetchFromIPFS(cid, onProgress?)` → pass
`onProgress` to `pinRemoteCID`. In `install()`, compute `totalBytes` from
the resolved variant/`manifest.distribution.size` and emit
`('fetching', 5..55 scaled, { bytesReceived, totalBytes })`.
- [ ] Frontend: `registryAppToCatalog` sets `builtInIcon` from
`app.iconDataUrl` (or `app.icon` when it's already a URL). Verify
`mergeApps` preserves it for not-installed apps.
- [ ] ENM private catalog (Jetson test): embed `iconDataUrl` (data-URI of
`assets/icon.svg`) on the ENM entry.
- [ ] Deploy app-center + rebuilt backend to Jetson; user verifies icon shows
pre-install and the bar fills during download.

## Files to Modify
- `pc2-node/src/services/AppInstallService.ts` (thread onProgress)
- `src/backend/apps/app-center/index.html` (builtInIcon from iconDataUrl)
- Jetson private `jetson-catalog.json` (ENM iconDataUrl) — test-only

## Out of scope / follow-up
- Adding `iconDataUrl` to every app in the PRODUCTION `_index.json` (content
task at release time so all apps show real icons in the live catalog).
- Byte progress only streams on the gateway fetch path; pure bitswap discovery
latency stays coarse (acceptable — small bundles, and the stage text is
honest).

## Notes
- All changes deploy to the Jetson via the established loop (app-center copy +
`npm run build:backend` + `pm2 restart pc2`). app-center is NOT part of the
heavy GUI webpack bundle, so it deploys cleanly.
- Nothing pushed to `main` or the production registry.
64 changes: 64 additions & 0 deletions .cursor/tasks/ENM-RELEASE-PREP/ENM-RELEASE-PREP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Task: ENM release prep (staged — NOT live)

**Task ID**: ENM-RELEASE-PREP
**Created**: 2026-06-26
**Status**: Review (prep complete; flip deferred to version launch)
**Priority**: High

## What this is
Everything needed to make Elastos Node Manager installable in the PRODUCTION
dApp Centre, prepared so launch is a copy-paste — WITHOUT putting a broken app
in front of users before the runtime ships.

## Why ENM can't go live ahead of the version (the coupling)
The catalog entry is just a pointer. ENM's install + run depends on pc2-node
changes that exist ONLY on `feature/elastos-node-manager`, NOT in production:
- same-origin app-backend proxy (`static.ts`)
- service-app Ed25519 install gate + per-arch variant resolver
- `AppProcessManager` absolute DB/config path injection
- doubled-Authorization-header fix
- dApp Centre icon (`iconDataUrl` → `builtInIcon`) + real download progress

Flip the catalog live before deploying this pc2-node and EVERY user gets the
"ENM backend unavailable" error + placeholder icon. So: deploy runtime first,
then flip.

## Done now (safe — nothing served to users)
- [x] Official bundles built FROM CI (run 28216945441, both arches), signed with
the stable Elacity Labs key via `PC2_ENM_SIGNING_SEED`. Includes the
api.js proxy fix (postdated the prior main-built artifacts).
- [x] Pinned both tarballs to the supernode IPFS (recursive). Content available.
- [x] Assembled the complete production registry entry → `enm-registry-entry.staged.json`.

## Hard data (verified)
| Field | Value |
|---|---|
| version | 0.5.249 |
| publisher (signedBy) | `1ab060ba7578261355504300c1193c484ed8a46a30499c3fa3cb9065930367eb` |
| x64 CID | `bafybeifxo36g4oylmkv7lq4sppq6vsalyg3honwe367owibktqy6iy22mi` (size 5,817,936) |
| arm64 CID | `bafybeida6gpbbhaq2yd2nektksr6h7grfn3tztbex5mbtqbmbelfrmbbeq` (size 5,793,217) |
| platform gate | os linux; arch x64+arm64; minMemoryMB 4096 (macOS/Windows excluded) |
| trusted-publisher env | host must set `PC2_TRUSTED_SERVICE_PUBLISHERS` to include the publisher hex |

## Flip-to-live procedure (run AT launch, in order)
1. Deploy the new pc2-node (this branch) + new app-center + GUI bundle to the
supernode and any production hosts. Confirm `version` and that
`PC2_TRUSTED_SERVICE_PUBLISHERS` includes the publisher hex above.
2. Insert `enm-registry-entry.staged.json` into the production registry source
served by the `:4500` registry service (primary 69.164.241.210, secondary
38.242.211.112 — update BOTH).
3. Verify: `curl http://127.0.0.1:4500/api/registry/apps` lists ENM; a PC2 node
shows it in the dApp Centre with the real purple icon; install on an x64 and
an arm64 host both succeed and the backend comes up.

## Optional pre-launch teaser (safe)
Add the entry now with `registry.status: "coming_soon"` instead of "available"
— visible but NOT installable. Only do this once you're comfortable showing it.

## Notes
- The staged entry's signatures bind the BUNDLE BYTES (CI manifest `cid` is empty
by design); the CID is whatever the content pins to. Verified the pinned CIDs
match the bytes signed by CI.
- Per-app `iconDataUrl` for the OTHER 6 live apps is still a follow-up content
task so they show real logos once the new app-center is live.
- Temp CI trigger was added and reverted (commits on branch); do not re-add.
77 changes: 77 additions & 0 deletions .cursor/tasks/ENM-RELEASE-PREP/enm-registry-entry.staged.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"name": "elastos-node-manager",
"title": "Elastos Node Manager",
"version": "0.5.249",
"description": "Run and self-heal an Elastos mainchain node for BPoS supernode operators.",
"author": "Elacity",
"type": "service",
"category": "blockchain",
"role": "dapp",
"icon": "assets/icon.svg",
"iconDataUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTQwIDE0MCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5zdDAgewogICAgICAgIGZpbGwtb3BhY2l0eTogLjY7CiAgICAgIH0KICAgICAgLnN0MSB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQpOwogICAgICB9CiAgICAgIC5zdDIgewogICAgICAgIGZpbGw6ICMxNDE0MTQ7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjcwIiB5MT0iMTA2LjkiIHgyPSI3MCIgeTI9IjMzLjYiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxMzkuOSkgc2NhbGUoMSAtMSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZlZWRjIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmYzM4MiIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHJlY3QgY2xhc3M9InN0MiIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxNDAiIHJ4PSIyMCIgcnk9IjIwIi8+CiAgPHBhdGggY2xhc3M9InN0MSIgZD0iTTk1LjcsMzMuOGMxLjktMS4xLDQuMi0xLjEsNi4xLDBsMTYuOSw5LjhjNC4yLDIuNCw0LjIsOC4yLjMsMTAuNWwtMTEuOSw2LjktLjQuMiwxMiw3YzQuMiwyLjQsNC4yLDguMi4zLDEwLjVsLTExLjksNi45LTguMSw0LjctMjYsMTVjLTEuOSwxLjEtNC4yLDEuMS02LjEsMGwtMjYtMTUtOC4xLTQuNy0xMS45LTYuOWMtNC0yLjMtNC04LjEsMC0xMC41bDEyLjEtNy4yaC0uMWMwLDAtMTEuOS03LTExLjktNy00LTIuMy00LTguMSwwLTEwLjVsMTYuNi05LjhjMS45LTEuMSw0LjItMS4xLDYuMSwwbDIyLjksMTMuMmMxLjksMS4xLDQuMiwxLjEsNi4xLDBsMjIuOS0xMy4yWiIvPgogIDxnPgogICAgPHBhdGggY2xhc3M9InN0MiIgZD0iTTg3LjIsNzMuOGguM2MzLjEuMiw1LjUsMi43LDUuNSw1LjhzLTIuNCw1LjYtNS41LDUuOGgtLjNzLTE0LjIsMC0xNC4yLDBjLTMuMiwwLTUuOC0yLjYtNS44LTUuOHMyLjYtNS44LDUuOC01LjhoMTQuMloiLz4KICAgIDxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik01Mi4xLDUzLjZsLjIuMiw5LjYsNy41YzQsMy4xLDQsOS4xLDAsMTIuMmwtOS42LDcuNWMtMi41LDItNi4yLDEuNS04LjItMS0yLTIuNS0xLjUtNi4yLDEtOC4ybDUuNy00LjQtNS43LTQuNC0uMi0uMmMtMi4zLTItMi43LTUuNS0uOC04LDEuOS0yLjUsNS40LTMsNy45LTEuMloiLz4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik01Mi4xLDUzLjZsLjIuMiwxMS42LDksLjIuMmMyLjMsMiwyLjcsNS41LjgsOC0xLjksMi41LTUuNCwzLTcuOSwxLjJsLS4yLS4yLTExLjYtOS0uMi0uMmMtMi4zLTItMi43LTUuNS0uOC04LDEuOS0yLjUsNS40LTMsNy45LTEuMloiLz4KICAgIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik01Ni42LDc3LjdjMS0xLjksMS41LTQuOC0xLTYuOGw4LjUuOS03LjUsNS44WiIvPgogIDwvZz4KPC9zdmc+Cg==",
"entry": "index.html",
"display": {
"width": 1200,
"height": 800,
"resizable": true
},
"backend": {
"entry": "backend/src/server.js",
"port": 4180,
"healthCheck": "/api/enm/health",
"teardown": {
"endpoint": "/api/enm/teardown",
"timeoutMs": 30000
}
},
"capabilities": {
"network": true
},
"requirements": {
"minVersion": "1.1.0",
"platform": {
"os": [
"linux"
],
"arch": [
"x64",
"arm64"
],
"minMemoryMB": 4096,
"reason": "Elastos Node Manager runs native Linux node binaries (x86_64 / arm64) and needs at least 50 GB of free disk for chain data. It is not available on macOS or Windows."
}
},
"externalDataDirs": [
"${PC2_DATA_DIR}/extensions/elastos-node-manager"
],
"distribution": {
"cid": "bafybeifxo36g4oylmkv7lq4sppq6vsalyg3honwe367owibktqy6iy22mi",
"signedBy": "1ab060ba7578261355504300c1193c484ed8a46a30499c3fa3cb9065930367eb",
"channel": "beta",
"size": 5817936,
"variants": {
"linux-x64": {
"cid": "bafybeifxo36g4oylmkv7lq4sppq6vsalyg3honwe367owibktqy6iy22mi",
"signature": "f8a7998ba6bb8aff93e01cf57e8940892391ca12b99b113c4bc06297f0c0937aa321fe323508c787ec5b14666efddf380289e802694663663a5a815fa034a80a",
"size": 5817936
},
"linux-arm64": {
"cid": "bafybeida6gpbbhaq2yd2nektksr6h7grfn3tztbex5mbtqbmbelfrmbbeq",
"signature": "4431bef0e8ba1e4cc350082e1fc5cff35bf71cafb45422dd44b40855d1eb2480afc19689f564add559c92a4d0c27d7c652a02a5b5595d51da933652036b86d0a",
"size": 5793217
}
}
},
"registry": {
"category": "blockchain",
"gradient": "gradient-purple",
"badges": [
"beta",
"bpos"
],
"status": "available",
"shortDesc": "Run & self-heal an Elastos BPoS mainchain node.",
"staffPick": false,
"popular": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Task: Same-origin reverse-proxy for service-app backends (ENM remote access fix)

**Task ID**: ENM-SAME-ORIGIN-BACKEND-PROXY
**Created**: 2026-06-25
**Status**: InProgress
**Priority**: High

## Description
Make PC2 service-app backends (starting with Elastos Node Manager / `enm-server`) reachable
from a browser that loads the GUI through a supernode domain (e.g. `https://zzz.ela.city`),
not only when the browser is on the same host as the node.

## Background
ENM ships a sidecar backend (`enm-server`) that listens on port `4180`. The ENM frontend
builds its API base as `<window.location.host>:4180/api/enm` (see
`src/backend/apps/elastos-node-manager/js/services/api.js` → `deriveBackendBase()`).

This works only when the browser shares a host with the node (`localhost:4180` reachable).
When the GUI is reached through the supernode domain, PC2's relay forwards **only the main
PC2 GUI port** — port `4180` is not forwarded and `zzz.ela.city:4180` is refused. Every ENM
request then hits the frontend's 10s timeout → "ENM backend unavailable / Request timed out".

Verified on the Jetson (2026-06-25):
- `enm-server` healthy on loopback: `GET 127.0.0.1:4180/api/enm/health` → `{"ok":true}`.
- `zzz.ela.city:4180` → connection refused from outside.
- GUI on `4200`, backend on `4180`; no Caddy/nginx block and no cloudflared tunnel — the
domain reaches the Jetson via PC2's own relay, which forwards only the main port.

This affects every dApp Store user who reaches their node via a supernode domain, and the
same `host:port` pattern is shared with other service apps (e.g. dao-dashboard). It is a
release blocker for remote service-app access.

## Requirements
1. Add a same-origin reverse-proxy in pc2-node that forwards
`/api/app-backend/:appName/*` → `http://127.0.0.1:<backendPort>/*`, where `<backendPort>`
is the running service app's port (from `AppProcessManager.getStatus`).
2. Proxy must be transparent: forward method, `Authorization`/`Content-Type`/`Accept`,
request body, and **stream** the response (must not buffer — ENM uses SSE for live data).
3. Only proxy installed service apps that are currently running; validate `appName`.
4. Target is loopback only (`127.0.0.1`); never an arbitrary host.
5. Change ENM `deriveBackendBase()` to use the same-origin proxy path so it rides the relay.
6. No new npm dependency (use Node's built-in `http`).

## Implementation Plan
- [ ] pc2-node: import `http`; add `handleAppBackendProxy` + register `app.all('/api/app-backend/:appName/*', …)` in `static.ts` before the static wrapper.
- [ ] ENM frontend: `deriveBackendBase()` → `location.origin + '/api/app-backend/elastos-node-manager/api/enm'` (with `root.ENM_BACKEND_BASE` override hook).
- [ ] Build pc2-node (tsc) and deploy to Jetson; patch the installed bundle's `api.js`; restart pc2.
- [ ] Verify ENM startup diagnostic passes and live data loads via `zzz.ela.city`.
- [ ] Document the repackage + re-sign + re-pin of the ENM bundle (frontend fix) as the final pre-release step.

## Acceptance Criteria
- Opening ENM from the dApp Centre via `https://zzz.ela.city` completes the startup
diagnostic (no "backend unavailable") and shows live node data.
- `GET https://zzz.ela.city/api/app-backend/elastos-node-manager/api/enm/health` → `{"ok":true}`.
- SSE/live updates still stream (response not buffered).
- No direct dependency on port `4180` reachability from the browser.

## Files to Modify
- `pc2-node/src/static.ts` — add same-origin app-backend proxy.
- `src/backend/apps/elastos-node-manager/js/services/api.js` — same-origin base.

## Files to Create
- This task doc.

## Testing Strategy
- Manual: reload ENM via `zzz.ela.city` on the Jetson, confirm diag passes + data loads.
- curl the proxied health endpoint through the public domain.
- Confirm an SSE endpoint stays open (live peer/height updates tick).

## Second bug found during testing — ENM session DB path (fixed)
After the proxy fix, authenticated ENM calls 401'd ("Authentication required"). Root cause:
`AppProcessManager` injected `PC2_NODE_DB_PATH`/`PC2_NODE_CONFIG_PATH` using a hard-coded
`/data` default and the wrong filename (`pc2-node.sqlite`), and even when derived from
config the path was **relative** (`./data/pc2.db`) — spawned services run with
`cwd=bundleDir`, so the relative path resolved against the wrong dir and ENM's
`OwnerCheckMiddleware.getDb()` threw "DB not found", silently returning null → 401.

Fix:
- `DatabaseManager.getDbPath()` getter added.
- `AppProcessManager` now injects `PC2_NODE_DB_PATH = resolvePath(db.getDbPath())` (absolute)
and `PC2_NODE_CONFIG_PATH = <dbDir>/node-config.json`. Verified: `whoami` → 200,
`isOwner: true` for the node owner through the proxy.

Files: `pc2-node/src/storage/database.ts`, `pc2-node/src/services/AppProcessManager.ts`.

## Notes
- Scope: branch only. Do NOT push to `main`.
- The proxy is generic (`:appName`) so it also fixes other same-pattern service apps.
- Security: app routing only (not in the dDRM/session security checklist). Loopback target,
appName-validated, running-service-only. ENM enforces its own owner auth behind the proxy.
Loading
Loading