clavarium 0.1.0-next.8 → 0.1.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/README.md CHANGED
@@ -216,11 +216,13 @@ The host needs `tmux` and `bindfs` (the read-only views).
216
216
  # the stable version from npm, in local mode, asking nothing (Node.js pinned and
217
217
  # checked first); it prints the next command and the public keys (text and QR)
218
218
  curl -fsSL https://clavarium.org/install.sh | sudo bash
219
- # online in the same line: it installs, shows the setup key (text and QR), waits for
220
- # the relay, sets it up over SSH, tests from the internet, shows the password once
221
- curl -fsSL https://clavarium.org/install.sh | sudo bash -s -- --relay relay.example.net \
222
- --relay-user ubuntu --domain clav.example.com --admin you@example.com
223
- curl -fsSL https://clavarium.org/install.sh | sudo bash -s -- --direct --domain clav.example.com
219
+ # online in the same line: it installs, shows the setup key (text and QR), asks for the
220
+ # relay's address once you made it with that key, waits for your DNS, sets the relay up
221
+ # over SSH, tests from the internet, shows the password once
222
+ DOMAIN=<your-domain>
223
+ EMAIL=<your-email>
224
+ curl -fsSL https://clavarium.org/install.sh | sudo bash -s -- --domain "$DOMAIN" --admin "$EMAIL" --relay-user ubuntu
225
+ curl -fsSL https://clavarium.org/install.sh | sudo bash -s -- --direct --domain "$DOMAIN"
224
226
  # or later, from local mode
225
227
  sudo clavarium online --relay relay.example.net --domain clav.example.com
226
228
  sudo clavarium online --port --domain clav.example.com
package/deploy/install.sh CHANGED
@@ -701,7 +701,7 @@ elif [ -n "$RELAY" ]; then
701
701
  # It is a PUBLIC key: showing it is safe; it lets the relay know this machine.
702
702
  if command -v qrencode >/dev/null 2>&1 || apt-get install -y -qq qrencode >/dev/null 2>&1; then
703
703
  echo "The same key, to read with a phone or a tablet:"
704
- qrencode -t ANSIUTF8 -m 2 -- "$TUNNEL_KEY" || true
704
+ qrencode -t ANSI -m 1 -l L -- "$TUNNEL_KEY" || true
705
705
  fi
706
706
  echo "It will print this, and it must be the same: $RELAY_PRINT"
707
707
  echo "The name $DOMAIN must lead to the relay."
package/deploy/online.mjs CHANGED
@@ -63,7 +63,7 @@ export const USAGE = [
63
63
  ].join('\n');
64
64
 
65
65
  export function parseOnline(args) {
66
- const o = { relay: '', user: 'root', relayPort: '22', domain: '', admin: '', fingerprint: '', keepSetupKey: false, yes: false, port: false };
66
+ const o = { askRelay: false, relay: '', user: 'root', relayPort: '22', domain: '', admin: '', fingerprint: '', keepSetupKey: false, yes: false, port: false };
67
67
  const value = (i, name) => { const v = args[i + 1]; if (v === undefined || v.startsWith('--')) throw new Bad(`${name} needs a value`); return v; };
68
68
  for (let i = 0; i < args.length; i++) {
69
69
  const a = args[i];
@@ -80,12 +80,15 @@ export function parseOnline(args) {
80
80
  default: throw new Bad(`unknown parameter: ${a}`);
81
81
  }
82
82
  }
83
- if (!o.relay === !o.port) throw new Bad('say how it will be reached: --relay HOST or --port');
83
+ if (o.relay && o.port) throw new Bad('say how it will be reached: --relay HOST or --port, not both');
84
+ /* Neither: through a relay that does not exist yet -- its key is shown first,
85
+ and its address asked once the operator has made it with that key. */
86
+ if (!o.relay && !o.port) o.askRelay = true;
84
87
  if (!DOMAIN.test(o.domain)) throw new Bad('--domain is a host name, lower case, like clav.example.com');
85
88
  o.admin ||= `admin@${o.domain}`;
86
89
  if (!EMAIL.test(o.admin)) throw new Bad('--admin is an e-mail address, lower case');
87
- if (o.relay) {
88
- if (!HOST.test(o.relay)) throw new Bad('--relay is the name or the address of the relay machine');
90
+ if (o.relay || o.askRelay) {
91
+ if (o.relay && !HOST.test(o.relay)) throw new Bad('--relay is the name or the address of the relay machine');
89
92
  if (!USER.test(o.user)) throw new Bad('--relay-user is an account name');
90
93
  if (!/^\d{1,5}$/.test(o.relayPort) || +o.relayPort < 1 || +o.relayPort > 65535) throw new Bad('--relay-port is a port number');
91
94
  if (o.fingerprint && !PRINT.test(o.fingerprint)) throw new Bad('--relay-fingerprint looks like SHA256:... (ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on the relay)');
@@ -121,7 +124,7 @@ export async function ensureKeys(sys, P = PATHS) {
121
124
  }
122
125
 
123
126
  async function qr(sys, text) {
124
- const r = await sys.run(['qrencode', '-t', 'ANSIUTF8', '-m', '2', '--', text]);
127
+ const r = await sys.run(['qrencode', '-t', 'ANSI', '-m', '1', '-l', 'L', '--', text]);
125
128
  if (r.code === 0 && r.out) sys.print(r.out.replace(/\n$/, ''));
126
129
  }
127
130
 
@@ -216,6 +219,19 @@ export async function checkDns(sys, o) {
216
219
  return got;
217
220
  }
218
221
 
222
+ /* The domain made to point to the relay while the operator is at it: waited for
223
+ (every 30 s, Enter to look now), the record to make said once. */
224
+ export async function waitDns(sys, o) {
225
+ let said = false;
226
+ for (;;) {
227
+ const relay = await addresses(sys, o.relay);
228
+ const got = await addresses(sys, o.domain);
229
+ if (relay.length && got.some((a) => relay.includes(a))) return;
230
+ if (!said && relay.length) { said = true; sys.print(`At your DNS provider, point the domain to the relay:\n ${o.domain} A ${relay[0]}\nWaiting for it (it takes a few minutes) -- or press Enter to look now.`); }
231
+ await (sys.sleepOrEnter ? sys.sleepOrEnter(30_000) : sys.sleep(30_000));
232
+ }
233
+ }
234
+
219
235
  /* ------------------------ the test from the internet ------------------------ */
220
236
  /* What Let's Encrypt (or Caddy) said, as the operator needs to read it. */
221
237
  export function explain(error, { domain, port, relay }) {
@@ -437,6 +453,17 @@ export async function online(o, sys, P = PATHS) {
437
453
  if (was) throw new Refused(`this machine is in server mode already: https://${was}`);
438
454
  if (!existsSync(P.caddy)) throw new Failed('the HTTPS programs are not on this machine: run the installer again (curl -fsSL https://clavarium.org/install.sh | sudo bash)');
439
455
  const keys = await ensureKeys(sys, P);
456
+ if (o.askRelay) {
457
+ await showKeys(sys, P, { which: 'setup' });
458
+ sys.print('Create the relay at your host now (Ubuntu), with the key above as its SSH key.');
459
+ for (;;) {
460
+ const a = String(await sys.askLine('Its address, once it exists (IP or name): ') ?? '').trim();
461
+ if (HOST.test(a)) { o.relay = a; break; }
462
+ if (!a && !sys.hasKeyboard?.()) throw new Failed('no keyboard to ask on: give --relay HOST');
463
+ sys.print(' That is not an address: an IP address, or a name like relay.example.com');
464
+ }
465
+ await waitDns(sys, o);
466
+ }
440
467
  await checkDns(sys, o);
441
468
  await portsFree(sys);
442
469
  if (o.relay) await setUpRelay(sys, P, o, keys);
@@ -515,6 +542,12 @@ export function realSys() {
515
542
  timer = setTimeout(done, ms);
516
543
  try { tty = createReadStream('/dev/tty'); tty.on('data', (d) => { if (d.includes(10) || d.includes(13)) done(); }); tty.on('error', () => {}); } catch { /* no keyboard: the timer alone */ }
517
544
  }),
545
+ hasKeyboard: () => { try { closeSync(openSync('/dev/tty', 'r')); return true; } catch { return false; } },
546
+ askLine: async (question) => {
547
+ let fd;
548
+ try { fd = openSync('/dev/tty', 'r+'); } catch { return ''; }
549
+ try { writeSync(fd, question); const buf = Buffer.alloc(512); let s = ''; while (!s.includes('\n')) { const n = readSync(fd, buf, 0, buf.length, null); if (!n) break; s += buf.toString('utf8', 0, n); } return s.split('\n')[0]; } finally { closeSync(fd); }
550
+ },
518
551
  waitEnter: async () => {
519
552
  let fd;
520
553
  try { fd = openSync('/dev/tty', 'r'); } catch { return false; }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "clavarium",
3
- "version": "0.1.0-next.8",
3
+ "version": "0.1.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "clavarium",
9
- "version": "0.1.0-next.8",
9
+ "version": "0.1.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clavarium",
3
- "version": "0.1.0-next.8",
3
+ "version": "0.1.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A conversation is a VM with a terminal in it. Everything else is a folder.",