@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 +2 -0
- package/USER_MANUAL.md +2 -2
- package/bin/lifedrive.js +78 -19
- package/dist/lifedrive-bundle.tar.gz +2 -2
- package/dist/lifedrive-bundle.tar.gz.sha256 +1 -1
- package/package.json +4 -1
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.
|
|
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 **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 44093522 bytes
|
|
4
|
+
sha256: 0e95b10584f5a20d11b7aee8fe3a1dc01310151d22864301865170a92ac71f30
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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.
|
|
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"
|