@inoku/lepan 0.7.59 → 0.7.62

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
@@ -84,6 +84,8 @@ The Linux/macOS legacy browser-owner setup uses the built-in AV1 registration en
84
84
 
85
85
  LifeDrive requires Leither V0.24.11 or newer. Linux/macOS browser-owner setup creates a separate Ed25519 `sodiumv2` identity for the primary browser. Windows prints a private ten-minute invitation for the first paired phone; later users and devices are managed from a paired device.
86
86
 
87
+ After successful npm setup, the installer saves a private `lepan-pairing-<id>.png` and prints its full path. It also displays the QR code when the terminal is wide enough. In the iPhone or Android app, open **Settings → Set up users → Scan QR code** and scan the terminal or the saved image. The PNG is saved in the Leither root on Linux/macOS and its private `.lifedrive-household` directory on Windows. Household invitations expire ten minutes after creation; legacy identity codes contain a device private key, so keep them private and delete the image after pairing. Legacy `--add-device` and `--upgrade` also produce a code when an identity is available; household upgrades preserve existing pairings without issuing new invitations.
88
+
87
89
  An existing installation is backed up before replacement. The explicit `--upgrade` path requires the saved application and key-auth owner state before changing files, synchronizes the same published application MID, and does not rerun device or domain setup. This release intentionally does not migrate the earlier password-owner prototype. Application files are active only after Leither advances the application from `cur` to `last`; a publication timeout leaves the prior `last` version serving users.
88
90
 
89
91
  Source code and design documentation are maintained separately. No device key, identity bundle, PPT, publisher key, or node-specific application MID is included in these release assets.
package/USER_MANUAL.md CHANGED
@@ -150,10 +150,10 @@ like a key: do not post it, email it to others, or paste it anywhere public.
150
150
 
151
151
  Within ten minutes of the invitation being printed:
152
152
 
153
- 1. Copy the whole invitation block from the terminal and send it to your phone, or copy it on a computer your phone shares a clipboard with.
153
+ 1. Keep the installer’s final QR code visible, or open the saved `lepan-pairing-<id>.png` using the path it prints. If you used the standalone shell installer, copy the whole invitation block instead.
154
154
  2. Open LifeDrive on the phone. The **Set up your LifeDrive** screen opens by itself (or open **Settings → Set up users**).
155
155
  3. Enter **Your name** and a name for this device.
156
- 4. Tap **Paste node identity** (or **Choose identity file** if you saved the invitation as a file).
156
+ 4. Tap **Scan QR code** and point the phone at the terminal or saved image. You can also use **Paste node identity** or **Choose identity file**.
157
157
 
158
158
  The phone creates its own device key, claims the node, and opens your new, empty
159
159
  personal drive. You are now the node's first user and its **administrator**, and
package/bin/lifedrive.js CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  const fs = require("node:fs");
5
5
  const path = require("node:path");
6
+ const os = require("node:os");
7
+ const { randomUUID } = require("node:crypto");
6
8
  const { spawnSync } = require("node:child_process");
7
9
  const { pathToFileURL } = require("node:url");
8
10
 
@@ -10,6 +12,75 @@ const distributionDirectory = path.resolve(__dirname, "..", "dist");
10
12
  const bundlePath = path.join(distributionDirectory, "lifedrive-bundle.tar.gz");
11
13
  const checksumPath = `${bundlePath}.sha256`;
12
14
 
15
+ async function showPairingCode(resultPath) {
16
+ if (!fs.existsSync(resultPath)) return;
17
+ // The child writes this only after setup succeeds. Never scrape its output:
18
+ // it can contain node keys as well as the mobile device's pairing identity.
19
+ const pairing = JSON.parse(fs.readFileSync(resultPath, "utf8"));
20
+ if (pairing.version !== 1 || !["lifedrive-device-identity", "lifedrive-node-setup"].includes(pairing.type)) {
21
+ throw new Error("Unsupported pairing data");
22
+ }
23
+ const directory = fs.readFileSync(`${resultPath}.directory`, "utf8");
24
+ if (!path.isAbsolute(directory)) throw new Error("Invalid pairing image directory");
25
+ const QRCode = require("qrcode");
26
+ const payload = JSON.stringify(pairing);
27
+ const options = { errorCorrectionLevel: "M", margin: 4, scale: 8 };
28
+ const png = await QRCode.toBuffer(payload, options);
29
+ // Unique names and exclusive creation avoid overwriting another device's
30
+ // code or following a pre-existing symlink. Keep this outside the app bundle.
31
+ const imagePath = path.join(directory, `lepan-pairing-${randomUUID()}.png`);
32
+ fs.writeFileSync(imagePath, png, { flag: "wx", mode: 0o600 });
33
+ process.stdout.write(`\nPair your phone: open LePan → Settings → Set up users → Scan QR code.\n`);
34
+ process.stdout.write(`Pairing image saved: ${imagePath}\n`);
35
+ process.stdout.write(pairing.type === "lifedrive-node-setup"
36
+ ? "Keep this code private. The invitation expires ten minutes after creation.\n"
37
+ : "This code contains your device private key. Keep it private and delete the image after pairing.\n");
38
+ // Small terminal codes use half-height blocks. If the terminal would wrap,
39
+ // direct the user to the full-resolution PNG instead of a broken code.
40
+ const modules = QRCode.create(payload, options).modules;
41
+ const width = modules.size + options.margin * 2;
42
+ if (process.stdout.isTTY && (!process.stdout.columns || process.stdout.columns > width)) {
43
+ // Explicit colors and a four-module quiet zone work on light/dark themes.
44
+ const dark = (x, y) => x >= 0 && y >= 0 && x < modules.size && y < modules.size && modules.get(y, x);
45
+ let terminal = "";
46
+ for (let y = -options.margin; y < modules.size + options.margin; y += 2) {
47
+ terminal += "\x1b[30;47m";
48
+ for (let x = -options.margin; x < modules.size + options.margin; x += 1) {
49
+ terminal += dark(x, y) ? (dark(x, y + 1) ? "█" : "▀") : (dark(x, y + 1) ? "▄" : " ");
50
+ }
51
+ terminal += "\x1b[0m\n";
52
+ }
53
+ process.stdout.write(terminal);
54
+ } else {
55
+ process.stdout.write("Open the saved PNG on this computer to scan it.\n");
56
+ }
57
+ }
58
+
59
+ async function runInstaller(command, args) {
60
+ const work = fs.mkdtempSync(path.join(os.tmpdir(), "lepan-pairing-"));
61
+ const resultPath = path.join(work, "pairing.json");
62
+ try {
63
+ const result = spawnSync(command, args, {
64
+ stdio: "inherit",
65
+ env: { ...process.env, LIFEDRIVE_PAIRING_RESULT: resultPath },
66
+ });
67
+ if (result.error) throw result.error;
68
+ if (typeof result.status === "number") process.exitCode = result.status;
69
+ else if (result.signal === "SIGINT") process.exitCode = 130;
70
+ else if (result.signal === "SIGTERM") process.exitCode = 143;
71
+ else process.exitCode = 1;
72
+ if (process.exitCode === 0) {
73
+ try { await showPairingCode(resultPath); }
74
+ catch {
75
+ // Parser errors may contain identity text; never print them.
76
+ process.stderr.write("LePan is installed, but its pairing QR code could not be created. Use the identity file or invitation shown above.\n");
77
+ }
78
+ }
79
+ } finally {
80
+ fs.rmSync(work, { recursive: true, force: true });
81
+ }
82
+ }
83
+
13
84
  function windowsArguments(values) {
14
85
  const valueOptions = new Map([
15
86
  ["--leither-root", "-LeitherRoot"],
@@ -41,21 +112,17 @@ function windowsArguments(values) {
41
112
  return translated;
42
113
  }
43
114
 
44
- function main() {
115
+ async function main() {
45
116
  if (process.platform === "win32") {
46
117
  const installerPath = path.join(distributionDirectory, "lifedrive-install.ps1");
47
118
  if (![installerPath, bundlePath, checksumPath].every(candidate => fs.existsSync(candidate))) {
48
119
  throw new Error("The npm package is incomplete. Reinstall @inoku/lepan and try again.");
49
120
  }
50
- const result = spawnSync(
121
+ await runInstaller(
51
122
  "powershell.exe",
52
123
  ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", installerPath,
53
- "-PackageDirectory", distributionDirectory, ...windowsArguments(process.argv.slice(2))],
54
- { stdio: "inherit", env: process.env }
124
+ "-PackageDirectory", distributionDirectory, ...windowsArguments(process.argv.slice(2))]
55
125
  );
56
- if (result.error) throw result.error;
57
- if (typeof result.status === "number") process.exitCode = result.status;
58
- else process.exitCode = 1;
59
126
  return;
60
127
  }
61
128
 
@@ -71,21 +138,13 @@ function main() {
71
138
  }
72
139
 
73
140
  const localReleaseBase = pathToFileURL(distributionDirectory).href.replace(/\/$/, "");
74
- const result = spawnSync(
141
+ await runInstaller(
75
142
  "/bin/bash",
76
- [installerPath, "--release-base", localReleaseBase, ...process.argv.slice(2)],
77
- { stdio: "inherit", env: process.env }
143
+ [installerPath, "--release-base", localReleaseBase, ...process.argv.slice(2)]
78
144
  );
79
- if (result.error) throw result.error;
80
- if (typeof result.status === "number") process.exitCode = result.status;
81
- else if (result.signal === "SIGINT") process.exitCode = 130;
82
- else if (result.signal === "SIGTERM") process.exitCode = 143;
83
- else process.exitCode = 1;
84
145
  }
85
146
 
86
- try {
87
- main();
88
- } catch (error) {
147
+ main().catch(error => {
89
148
  process.stderr.write(`LePan installer stopped: ${error.message}\n`);
90
149
  process.exitCode = 1;
91
- }
150
+ });
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/lifedrive-bundle.tar.gz
3
- size: 40572222 bytes
4
- sha256: 05320977b19562ac0e8f1419597dd92def37f0f13ec85767e23514cfc8235e53
3
+ size: 44093522 bytes
4
+ sha256: 0e95b10584f5a20d11b7aee8fe3a1dc01310151d22864301865170a92ac71f30
@@ -1 +1 @@
1
- 05320977b19562ac0e8f1419597dd92def37f0f13ec85767e23514cfc8235e53 lifedrive-bundle.tar.gz
1
+ 0e95b10584f5a20d11b7aee8fe3a1dc01310151d22864301865170a92ac71f30 lifedrive-bundle.tar.gz
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inoku/lepan",
3
- "version": "0.7.59",
3
+ "version": "0.7.62",
4
4
  "description": "Install LePan and its Leither runtime on Linux, macOS, or Windows",
5
5
  "os": [
6
6
  "linux",
@@ -22,6 +22,9 @@
22
22
  "engines": {
23
23
  "node": ">=18"
24
24
  },
25
+ "dependencies": {
26
+ "qrcode": "1.5.4"
27
+ },
25
28
  "repository": {
26
29
  "type": "git",
27
30
  "url": "git+https://github.com/cfa532/lifedrive-installer.git"