@openmeet/snowglobe 0.3.0-rc.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apps.mjs +3 -3
- package/lib/render.mjs +7 -4
- package/package.json +1 -1
- package/snowglobe.default.yml +9 -5
- package/stack/https/nginx.conf.template +1 -1
- package/stack/page/index.html +16 -4
package/lib/apps.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
|
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { ROOT, globeFacts, paths } from './env.mjs';
|
|
17
17
|
|
|
18
|
-
const HEADER = '# Written by
|
|
18
|
+
const HEADER = '# Written by snowglobe app from https/apps; rerun it instead of editing.\n';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* The first saved route whose ending is `app`, or null. app.<domain> is where the apps are, so an
|
|
@@ -25,7 +25,7 @@ export const appEnding = (apps) => apps.find((a) => a.ending === 'app') ?? null;
|
|
|
25
25
|
|
|
26
26
|
export const appEndingMessage = (file, a) =>
|
|
27
27
|
`${file}: the line "${formatApps([a]).trim()}" claims the ending app, which is where the apps are (${a.name}.app.<domain>); ` +
|
|
28
|
-
`remove the route (
|
|
28
|
+
`remove the route (snowglobe app --remove ${a.name}) or give it another ending`;
|
|
29
29
|
|
|
30
30
|
/** The routes in an apps file's text: [{ name, port, ending }], ending null when none. */
|
|
31
31
|
export function parseApps(text) {
|
|
@@ -85,7 +85,7 @@ export const writeApps = (p, apps) => put(p.apps, formatApps(apps));
|
|
|
85
85
|
export function renderRoutes({ root = ROOT, state, env = process.env } = {}) {
|
|
86
86
|
const p = paths(root, state);
|
|
87
87
|
const pageJson = join(p.globe, 'page.json');
|
|
88
|
-
if (!existsSync(pageJson)) throw new Error(`No ${pageJson}: run
|
|
88
|
+
if (!existsSync(pageJson)) throw new Error(`No ${pageJson}: run snowglobe up, which renders it`);
|
|
89
89
|
const { domain } = globeFacts({ root, state, env });
|
|
90
90
|
const apps = readApps(p);
|
|
91
91
|
const bad = appEnding(apps);
|
package/lib/render.mjs
CHANGED
|
@@ -452,14 +452,14 @@ export function renderGlobe(globe, base, { stateDir, globeDir = join(stateDir ??
|
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
const header = (c) =>
|
|
455
|
-
`${c} Written by
|
|
455
|
+
`${c} Written by snowglobe render from ${source}; render again instead of editing.\n`;
|
|
456
456
|
|
|
457
457
|
const compose =
|
|
458
458
|
header('#') +
|
|
459
459
|
`# The https globe "${globe.globe}" at ${D}: ${globe.pds.map((p) => p.name).join(', ')}` +
|
|
460
460
|
(globe.grouphosts.length ? `; group hosts ${globe.grouphosts.map((g) => g.name).join(', ')}` : '') +
|
|
461
461
|
'.\n' +
|
|
462
|
-
'# Run it with
|
|
462
|
+
'# Run it with snowglobe up, which renders this file first.\n\n' +
|
|
463
463
|
// YAML 1.1 quoting, so a value such as restart's "no" stays a string to compose.
|
|
464
464
|
stringify({ services, volumes, networks: { [NETWORK]: null } }, { lineWidth: 0, nullStr: '', aliasDuplicateObjects: false, version: '1.1' });
|
|
465
465
|
|
|
@@ -514,15 +514,18 @@ export function renderGlobe(globe, base, { stateDir, globeDir = join(stateDir ??
|
|
|
514
514
|
const named = Object.entries(IMAGE_NAMES).find(([, ref]) => ref === image)?.[0];
|
|
515
515
|
return named ?? image.split('/').pop().replace(/@sha256:([0-9a-f]{12})[0-9a-f]+$/, '@sha256:$1…');
|
|
516
516
|
};
|
|
517
|
+
// What the globe is made of, as the page's first line names it: only what runs.
|
|
518
|
+
const parts = [globe.pds.length === 1 ? 'PDS' : 'PDSs', 'PLC directory', on.relay && 'relay', on.jetstream && 'Jetstream'].filter(Boolean);
|
|
517
519
|
const page = {
|
|
518
520
|
globe: globe.globe,
|
|
519
521
|
domain: D,
|
|
520
522
|
about:
|
|
521
|
-
|
|
523
|
+
`An atproto network on this machine: its own ${parts.slice(0, -1).join(', ')} and ${parts.at(-1)}. Its accounts live in its own directory, not the public one, so Bluesky cannot find anything here. An app is handed this network's addresses (snowglobe env), not Bluesky's.`,
|
|
522
524
|
accountsNote:
|
|
523
525
|
`Every account on the network, on each of its ${globe.pds.length} PDS${globe.pds.length === 1 ? '' : 's'}` +
|
|
524
526
|
(globe.grouphosts.length ? `, and every group on its group host${globe.grouphosts.length === 1 ? '' : 's'}.` : '.'),
|
|
525
|
-
|
|
527
|
+
// Without Jetstream the page has no feed to follow, and says so instead of reconnecting.
|
|
528
|
+
feed: on.jetstream ? '/subscribe' : false,
|
|
526
529
|
services: [
|
|
527
530
|
...globe.pds.map((p) => [
|
|
528
531
|
'PDS',
|
package/package.json
CHANGED
package/snowglobe.default.yml
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# The default snowglobe: one current PDS, a PLC directory and Jetstream, all at https names, with
|
|
2
2
|
# Slingshot and PDSls (the record browser) beside them.
|
|
3
|
-
# Copy this file to snowglobe.yml (here, or in your app's repo) and run
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Copy this file to snowglobe.yml (here, or in your app's repo) and run snowglobe up
|
|
4
|
+
# (bin/snowglobe.mjs up from a checkout of snowglobe). The commented sections below add more.
|
|
5
|
+
# What each one does:
|
|
6
|
+
# https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/snowglobe.md
|
|
7
|
+
# The whole thing, three PDSes, a relay and a lexicon authority:
|
|
8
|
+
# https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/examples/atmo.yml
|
|
6
9
|
|
|
7
10
|
globe: snowglobe
|
|
8
11
|
domain: local.snowglobe.at # every name below is under it
|
|
@@ -26,7 +29,7 @@ services: # these are the defaults, each true or false
|
|
|
26
29
|
jetstream: true
|
|
27
30
|
slingshot: true # answers a handle's DID and PDS inside the network; needs jetstream
|
|
28
31
|
pdsls: true # https://pdsls.local.snowglobe.at, a browser for the network's records
|
|
29
|
-
tap: false # sends an app the events of one collection (true, or a map; see
|
|
32
|
+
tap: false # sends an app the events of one collection (true, or a map; see snowglobe.md above)
|
|
30
33
|
# relay: auto # auto: on when there is more than one PDS
|
|
31
34
|
|
|
32
35
|
# Where every PDS resolves lexicons (NSIDs) from. Name a PDS that is in this file.
|
|
@@ -45,7 +48,8 @@ accounts: # <name>@<pds>; up makes each one
|
|
|
45
48
|
- bob@main
|
|
46
49
|
|
|
47
50
|
# Your app, reached at https://myapp.app.local.snowglobe.at, with settings from the snowglobe.
|
|
48
|
-
# `snowglobe env` hands them over
|
|
51
|
+
# `snowglobe env` hands them over. The names:
|
|
52
|
+
# https://github.com/OpenMeet-Team/snowglobe/blob/main/docs/your-app.md
|
|
49
53
|
# apps:
|
|
50
54
|
# - name: myapp
|
|
51
55
|
# local: { port: 5173 }
|
|
@@ -96,7 +96,7 @@ server {
|
|
|
96
96
|
listen 443 ssl;
|
|
97
97
|
server_name *.app.${SG_DOMAIN};
|
|
98
98
|
if ($snowglobe_app = "") {
|
|
99
|
-
return 404 "No app is registered at $host (
|
|
99
|
+
return 404 "No app is registered at $host (snowglobe app).\n";
|
|
100
100
|
}
|
|
101
101
|
location / {
|
|
102
102
|
proxy_pass http://$snowglobe_app;
|
package/stack/page/index.html
CHANGED
|
@@ -233,7 +233,8 @@
|
|
|
233
233
|
if (tally.people != null) parts.push(plural(tally.people, "person", "people"));
|
|
234
234
|
if (tally.groups) parts.push(plural(tally.groups, "group", "groups"));
|
|
235
235
|
var line = parts.length ? parts.join(" and ") + ". " : "";
|
|
236
|
-
|
|
236
|
+
// writes is null on a network with no feed to count them from.
|
|
237
|
+
document.getElementById("tally").textContent = line + (tally.writes == null ? "" : plural(tally.writes, "write", "writes") + " in the last 24 hours.");
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
function health(domain, services) {
|
|
@@ -380,6 +381,15 @@
|
|
|
380
381
|
connect();
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
// A network with no Jetstream has no feed: the panel says so instead of reconnecting forever.
|
|
385
|
+
function noActivity() {
|
|
386
|
+
document.getElementById("stream").style.display = "none";
|
|
387
|
+
document.getElementById("count").parentNode.style.display = "none";
|
|
388
|
+
var body = document.getElementById("events");
|
|
389
|
+
body.textContent = "";
|
|
390
|
+
add(body, el("p", { "class": "empty" }, "This network runs no Jetstream, so its writes are not followed here."));
|
|
391
|
+
}
|
|
392
|
+
|
|
383
393
|
getJSON("/snowglobe.json").then(function (g) {
|
|
384
394
|
document.title = g.globe + " · Snowglobe network";
|
|
385
395
|
document.getElementById("title").textContent = g.globe;
|
|
@@ -396,12 +406,14 @@
|
|
|
396
406
|
}
|
|
397
407
|
if (g.about) document.getElementById("about").textContent = g.about;
|
|
398
408
|
if (g.accountsNote) document.getElementById("accounts-note").textContent = g.accountsNote;
|
|
399
|
-
|
|
400
|
-
|
|
409
|
+
// feed false: the network runs no Jetstream. Unset, Jetstream is at its usual public name.
|
|
410
|
+
var feed = g.feed === false ? null : g.feed || "wss://jetstream." + g.domain + "/subscribe";
|
|
411
|
+
if (feed && feed.charAt(0) === "/") feed = "wss://" + location.host + feed;
|
|
412
|
+
if (!feed) { tally.writes = null; noActivity(); }
|
|
401
413
|
showTally();
|
|
402
414
|
health(g.domain, g.services || SERVICES);
|
|
403
415
|
// The feed names accounts by handle, so it waits for the account list.
|
|
404
|
-
accounts(g.accounts || ACCOUNTS).catch(function () {}).then(function () { activity(feed); });
|
|
416
|
+
accounts(g.accounts || ACCOUNTS).catch(function () {}).then(function () { if (feed) activity(feed); });
|
|
405
417
|
});
|
|
406
418
|
})();
|
|
407
419
|
</script>
|