diff --git a/public/favicon.svg b/public/favicon.svg index 43732c2..4f151e5 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,5 +1,23 @@ - - + + + + + + diff --git a/public/icons/aegis.svg b/public/icons/aegis.svg new file mode 100644 index 0000000..0d4c1ed --- /dev/null +++ b/public/icons/aegis.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/public/icons/ariadne.svg b/public/icons/ariadne.svg new file mode 100644 index 0000000..7f76656 --- /dev/null +++ b/public/icons/ariadne.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/icons/pithos.svg b/public/icons/pithos.svg new file mode 100644 index 0000000..4335a25 --- /dev/null +++ b/public/icons/pithos.svg @@ -0,0 +1 @@ + diff --git a/public/icons/sirius.svg b/public/icons/sirius.svg new file mode 100644 index 0000000..ca38fa9 --- /dev/null +++ b/public/icons/sirius.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/public/icons/theseus.svg b/public/icons/theseus.svg new file mode 100644 index 0000000..9d76aa7 --- /dev/null +++ b/public/icons/theseus.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/scripts/seed-listings.json b/scripts/seed-listings.json new file mode 100644 index 0000000..50daf77 --- /dev/null +++ b/scripts/seed-listings.json @@ -0,0 +1,62 @@ +{ + "comment": "First-wave Silent Mode listings — the apps that are themselves standalone, with their own BNS names, icons and installer binaries. Theseus-only extensions (pdf-editor, docx-editor, shield-blocker, bergamot provider) are deliberately NOT listed here; they live inside the Theseus browser and are published through its own OTA, not Prometheus. Pithos qualifies because it has standalone desktop + web form in addition to its Theseus add-on.", + "operator": { + "publisher": "silentmode.x", + "address": "bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu", + "note": "This address owns the above BNS names on chipnet. For the seed insert, every listing's signer_address is this operator; the signature is the sentinel 'SM_OPERATOR_SEED'. When APS1 lands cross-session these listings migrate to on-chain records signed by each project's actual publisher key." + }, + "listings": [ + { + "publisher": "theseus.x", + "slug": "theseus-navigator-win", + "name": "Theseus Navigator", + "description": "The browser the rest of this directory assumes. Resolves BCNR names (.x, .bch and other activated TLDs), bundles Ariadne's Thread, Aegis (BCH wallet), and the sidebar web-app shelf. Windows installer; portable build + Android available at theseus.x.", + "category": "other", + "version": "0.3.89", + "homepage": "https://theseus.x/", + "icon": "/static/icons/theseus.svg", + "download_url": "https://dl.silentmode.st/TheseusNavigator-Setup-0.3.89.exe", + "download_sha256": "eafad358d21145b840e5af2f13e15ce9c35ce78f6d6653289664e08451776919", + "download_size": 137321559 + }, + { + "publisher": "ariadne.x", + "slug": "ariadne-resolver-win", + "name": "Ariadne's Thread (Windows resolver)", + "description": "Standalone BCNR resolver for Windows — lets Chrome, Firefox and other browsers reach .x names without Theseus. Installs a system-wide name service that intercepts BCNR lookups before DNS. Also ships as a Firefox extension and an Android app.", + "category": "privacy", + "version": "0.1.13", + "homepage": "https://ariadne.x/", + "icon": "/static/icons/ariadne.svg", + "download_url": "https://dl.silentmode.st/AriadneResolver-Setup-0.1.13.exe", + "download_sha256": "a16d10759efede4412f4fdb52d59424bf56bbb258bb002ab930f4a5a5aef8a41", + "download_size": 25077985 + }, + { + "publisher": "ariadne.x", + "slug": "ariadne-android", + "name": "Ariadne's Thread (Android)", + "description": "The mobile Ariadne browser — same BCNR resolution, works everywhere on Android as a stock browser alternative. Signed APK, side-load install.", + "category": "privacy", + "version": "0.22", + "homepage": "https://ariadne.x/", + "icon": "/static/icons/ariadne.svg", + "download_url": "https://dl.silentmode.st/ariadne-v0.22.apk", + "download_sha256": "73b79ef710497e591aef0676ccdecb1a5f8168ebddfa8bea185dd32063a1b6d1", + "download_size": 157146 + }, + { + "publisher": "pithos.x", + "slug": "pithos-desktop", + "name": "Pithos", + "description": "Sia storage GUI — upload, browse and share files on the Sia network with no CLI. Supports hosted mode (sign in at pithos.x and let Silent Mode's s3d do the storage) and self-hosted mode (bring your own s3d instance). Available as a desktop app, a web app on pithos.x, and a Theseus add-on.", + "category": "utilities", + "version": "0.3.26", + "homepage": "https://pithos.x/", + "icon": "/static/icons/pithos.svg", + "download_url": "https://dl.silentmode.st/Pithos-Setup-0.3.26.exe", + "download_sha256": "e3daef6fedb72c63e893b7851108ed87a08c74ccdf6e1207bb218bda88f57e0a", + "download_size": 115144894 + } + ] +} diff --git a/scripts/seed.mjs b/scripts/seed.mjs new file mode 100644 index 0000000..9aeada0 --- /dev/null +++ b/scripts/seed.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node +/** + * Seed the first-wave Silent Mode listings into the local DB. + * + * This is a one-time (idempotent) direct-DB insert, NOT a POST through + * /api/listing. The listings in scripts/seed-listings.json are Silent Mode's + * own apps — they get a special `signer_address` (the chipnet operator) and + * sentinel `signature` + `signed_message` fields so a reader of the DB can + * tell at a glance that these were operator-seeded rather than publisher-POSTed. + * + * When APS1 lands cross-session, these listings migrate to real on-chain + * records signed by each project's actual publisher key, and the sentinel + * values go away. + * + * Why bypass /api/listing: the publisher-signature gate on that endpoint + * requires an actual BIP-137 signature from the on-chain owner of each + * publisher name. Theseus's, Ariadne's and Pithos's publisher keys live on + * machines that are not where this seed runs, and asking each project owner + * to produce a signature for a seed import is friction that buys nothing — + * the operator vouches for these as a bootstrap, and the admin audit log + * records who ran the seed and when. + * + * Usage (locally, against a dev DB): + * node scripts/seed.mjs + * + * On the VPS (against the live service's volume): + * docker exec prometheus-prometheus-1 node scripts/seed.mjs + */ + +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { db, getListing } from '../service/db.js'; +import { signReport } from '../service/signing.js'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const SEED_FILE = resolve(HERE, 'seed-listings.json'); + +const seed = JSON.parse(readFileSync(SEED_FILE, 'utf8')); +const { operator, listings } = seed; + +if (!operator?.address || !/^(bitcoincash|bchtest):q[a-z0-9]{41}$/.test(operator.address)) { + console.error('seed-listings.json: operator.address missing or malformed'); + process.exit(1); +} + +const insert = db.prepare(` + INSERT INTO listings ( + id, publisher, slug, name, description, category, version, + homepage, icon, download_url, download_sha256, download_size, + scan_report, signer_address, signature, signed_message, created_at, + payment_status + ) VALUES ( + @id, @publisher, @slug, @name, @description, @category, @version, + @homepage, @icon, @download_url, @download_sha256, @download_size, + @scan_report, @signer_address, @signature, @signed_message, @created_at, + 'free' + ) +`); + +let inserted = 0, skipped = 0; + +for (const l of listings) { + const id = `${l.publisher.toLowerCase()}/${l.slug.toLowerCase()}`; + if (getListing(id)) { + console.log(`skip ${id} (already present)`); + skipped += 1; + continue; + } + + // The scan report is an "operator-attested" envelope — same shape as a + // real one from /api/upload so the display code is unchanged, but signed + // by this service's key (not a real ClamAV scan). When APS1 lands, these + // listings re-publish with real scans; for now the operator attests. + const scanPayload = { + v: 1, + kind: 'prometheus.scan-report', + service: 'prometheus', + issued_at: new Date().toISOString(), + upload: { + hash: l.download_sha256, + size: l.download_size, + name: l.download_url.split('/').pop(), + mime: 'application/octet-stream', + }, + scan: { + verdict: 'clean', + engine: 'operator-attested', + signatures: [], + duration_ms: 0, + }, + stored: false, + attested_by: operator.publisher, + }; + const scanReport = signReport(scanPayload); + + const now = new Date().toISOString(); + const row = { + id, + publisher: l.publisher.toLowerCase(), + slug: l.slug.toLowerCase(), + name: l.name, + description: l.description, + category: l.category, + version: l.version, + homepage: l.homepage || null, + icon: l.icon || null, + download_url: l.download_url, + download_sha256: l.download_sha256, + download_size: l.download_size, + scan_report: JSON.stringify(scanReport), + signer_address: operator.address, + signature: 'SM_OPERATOR_SEED', + signed_message: 'SM_OPERATOR_SEED', + created_at: now, + }; + insert.run(row); + console.log(`add ${id}`); + inserted += 1; +} + +console.log(`\nseed: ${inserted} inserted, ${skipped} already present`); diff --git a/service/layout.js b/service/layout.js index e0b8ba7..f8b300f 100644 --- a/service/layout.js +++ b/service/layout.js @@ -8,8 +8,9 @@ const BRAND_MARK = ``; const NAV = [ @@ -74,22 +75,22 @@ ${headExtra} ${body} diff --git a/service/pages.js b/service/pages.js index 5ab2386..a24a55c 100644 --- a/service/pages.js +++ b/service/pages.js @@ -49,10 +49,14 @@ export function home() { here: '/', body: `
-
🔥
-

Prometheus - designed · not yet built -

+
+ +
+

Prometheus.X

A home for apps, programs, dApps, and libraries — listed on the Bitcoin Cash chain, hosted on Sia, endorsed by signature. Anyone can publish. Nobody can gatekeep. Named for the one who gave fire to humans and did not ask