@inoku/lepan 0.7.65 → 0.7.66
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 +25 -1
- package/bin/lifedrive.js +58 -16
- package/dist/lifedrive-bundle.tar.gz +2 -2
- package/dist/lifedrive-bundle.tar.gz.sha256 +1 -1
- package/dist/lifedrive-install.ps1 +3 -3
- package/dist/lifedrive-install.sh +11 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,7 +86,31 @@ The Linux/macOS legacy browser-owner setup uses the built-in AV1 registration en
|
|
|
86
86
|
|
|
87
87
|
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.
|
|
88
88
|
|
|
89
|
-
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
|
|
89
|
+
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 it fits the terminal's width and height. In the iPhone or Android app, open **Settings → Set up users → Scan QR code** and scan the terminal or the saved image. A private `.json` invitation is saved alongside it for apps without a setup scanner: transfer that file to the phone and use **Choose identity file**. These files are 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 transfer copies 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.
|
|
90
|
+
|
|
91
|
+
### Renew an expired first-phone invitation
|
|
92
|
+
|
|
93
|
+
Run this again **on the computer running Leither**:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx --yes @inoku/lepan@latest --household
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If more than one node is running, also pass `--leither-root` with the same node
|
|
100
|
+
directory used during installation. On Windows, use an administrator terminal.
|
|
101
|
+
The macOS setup package also supports renewal when run again.
|
|
102
|
+
|
|
103
|
+
Before the first owner has been created, setup replaces the previous invitation
|
|
104
|
+
and produces a fresh QR image and import file with a new ten-minute expiry. It
|
|
105
|
+
briefly stops only the managed account service while issuing the invitation,
|
|
106
|
+
then starts it and verifies readiness before displaying the new code. If issuance
|
|
107
|
+
fails, it attempts to restore the service. Leither keeps running; existing node
|
|
108
|
+
keys and drive data are preserved.
|
|
109
|
+
|
|
110
|
+
Setup never replaces an existing owner or an interrupted account claim. If users
|
|
111
|
+
already exist, use **My devices** on a paired phone to invite another device. An
|
|
112
|
+
interrupted first claim must be resumed on the original phone with its original
|
|
113
|
+
invitation. Ordinary `--upgrade` does not renew invitations.
|
|
90
114
|
|
|
91
115
|
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.
|
|
92
116
|
|
package/bin/lifedrive.js
CHANGED
|
@@ -22,24 +22,59 @@ async function showPairingCode(resultPath) {
|
|
|
22
22
|
}
|
|
23
23
|
const directory = fs.readFileSync(`${resultPath}.directory`, "utf8");
|
|
24
24
|
if (!path.isAbsolute(directory)) throw new Error("Invalid pairing image directory");
|
|
25
|
-
const QRCode = require("qrcode");
|
|
26
25
|
const payload = JSON.stringify(pairing);
|
|
26
|
+
// Only encode fields consumed by the existing Android and iOS setup parsers.
|
|
27
|
+
// The node enforces expiry and returns account details after a successful claim.
|
|
28
|
+
// Keep the full invitation in the import file, and leave legacy identities intact.
|
|
29
|
+
// iOS still requires certificate_sha256 even when Leither transport uses a key.
|
|
30
|
+
const qrPayload = pairing.type === "lifedrive-node-setup"
|
|
31
|
+
? JSON.stringify(Object.fromEntries([
|
|
32
|
+
"type", "version", "app_id", "node_id", "endpoint", "transport",
|
|
33
|
+
"transport_key", "certificate_sha256", "request_id", "secret",
|
|
34
|
+
].filter(key => Object.hasOwn(pairing, key)).map(key => [key, pairing[key]])))
|
|
35
|
+
: payload;
|
|
36
|
+
const basename = path.join(directory, `lepan-pairing-${randomUUID()}`);
|
|
37
|
+
const imagePath = `${basename}.png`;
|
|
38
|
+
const identityPath = `${basename}.json`;
|
|
39
|
+
// Older mobile releases support importing this file but have no setup scanner.
|
|
40
|
+
// Keep a private copy even when QR generation succeeds.
|
|
41
|
+
fs.writeFileSync(identityPath, payload, { flag: "wx", mode: 0o600 });
|
|
27
42
|
const options = { errorCorrectionLevel: "M", margin: 4, scale: 8 };
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
const privacyNote = pairing.type === "lifedrive-node-setup"
|
|
44
|
+
? "Keep it private. This invitation expires ten minutes after creation."
|
|
45
|
+
: "This contains your device private key. Keep it private and delete it after pairing.";
|
|
46
|
+
const renewalHint = pairing.type === "lifedrive-node-setup"
|
|
47
|
+
? "\nInvitation expired? Run this again on the node:\n npx --yes @inoku/lepan@latest --household\n"
|
|
48
|
+
: "";
|
|
49
|
+
let modules;
|
|
50
|
+
try {
|
|
51
|
+
const QRCode = require("qrcode");
|
|
52
|
+
const png = await QRCode.toBuffer(qrPayload, options);
|
|
53
|
+
modules = QRCode.create(qrPayload, options).modules;
|
|
54
|
+
// Unique names and exclusive creation avoid overwriting another device's
|
|
55
|
+
// code or following a pre-existing symlink. Keep this outside the app bundle.
|
|
56
|
+
fs.writeFileSync(imagePath, png, { flag: "wx", mode: 0o600 });
|
|
57
|
+
} catch {
|
|
58
|
+
// The child no longer prints secrets. Preserve an importable fallback
|
|
59
|
+
// before runInstaller removes the temporary handoff directory.
|
|
60
|
+
process.stderr.write("\nNext: pair your phone\n\nThe QR code could not be created. Use the saved pairing file instead.\n");
|
|
61
|
+
process.stderr.write(` ${identityPath}\n\n`);
|
|
62
|
+
process.stderr.write("Transfer this file to your phone, then select it in LePan > Settings > Set up users > Choose identity file.\n");
|
|
63
|
+
process.stderr.write(`${privacyNote}\n`);
|
|
64
|
+
process.stderr.write(renewalHint);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write("\nNext: pair your phone\n\n");
|
|
68
|
+
process.stdout.write(" 1. Open LePan on your iPhone or Android phone.\n");
|
|
69
|
+
process.stdout.write(" 2. Go to Settings > Set up users > Scan QR code.\n");
|
|
38
70
|
// Small terminal codes use half-height blocks. If the terminal would wrap,
|
|
39
|
-
//
|
|
40
|
-
const modules = QRCode.create(payload, options).modules;
|
|
71
|
+
// or scroll the instructions out of view, use the full-resolution PNG.
|
|
41
72
|
const width = modules.size + options.margin * 2;
|
|
42
|
-
|
|
73
|
+
const height = Math.ceil(width / 2);
|
|
74
|
+
const fitsTerminal = process.stdout.isTTY && process.stdout.columns > width &&
|
|
75
|
+
process.stdout.rows >= height + 26;
|
|
76
|
+
if (fitsTerminal) {
|
|
77
|
+
process.stdout.write(" 3. Scan the code below.\n\n");
|
|
43
78
|
// Explicit colors and a four-module quiet zone work on light/dark themes.
|
|
44
79
|
const dark = (x, y) => x >= 0 && y >= 0 && x < modules.size && y < modules.size && modules.get(y, x);
|
|
45
80
|
let terminal = "";
|
|
@@ -52,8 +87,15 @@ async function showPairingCode(resultPath) {
|
|
|
52
87
|
}
|
|
53
88
|
process.stdout.write(terminal);
|
|
54
89
|
} else {
|
|
55
|
-
process.stdout.write("Open the saved
|
|
90
|
+
process.stdout.write(" 3. Open the saved QR image on this computer and scan it.\n");
|
|
56
91
|
}
|
|
92
|
+
process.stdout.write(`\nQR image:\n ${imagePath}\n\n${privacyNote}\n`);
|
|
93
|
+
process.stdout.write("\nNo Scan QR code button? Transfer this pairing file to your phone:\n");
|
|
94
|
+
process.stdout.write(` ${identityPath}\n`);
|
|
95
|
+
process.stdout.write("Then open Settings > Set up users > Choose identity file.\n");
|
|
96
|
+
process.stdout.write("Keep the pairing file private; delete the transfer copy after import.\n");
|
|
97
|
+
process.stdout.write("Keep this computer on and connected while using LePan.\n");
|
|
98
|
+
process.stdout.write(renewalHint);
|
|
57
99
|
}
|
|
58
100
|
|
|
59
101
|
async function runInstaller(command, args) {
|
|
@@ -73,7 +115,7 @@ async function runInstaller(command, args) {
|
|
|
73
115
|
try { await showPairingCode(resultPath); }
|
|
74
116
|
catch {
|
|
75
117
|
// Parser errors may contain identity text; never print them.
|
|
76
|
-
process.stderr.write("
|
|
118
|
+
process.stderr.write("\nPhone pairing could not be prepared. Use your existing identity file, or create a new setup invitation from the node.\n");
|
|
77
119
|
}
|
|
78
120
|
}
|
|
79
121
|
} finally {
|
|
@@ -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: 44139710 bytes
|
|
4
|
+
sha256: 134de0bb85330eb3192e837e5054074969a80cbb4eaa1ee0b7870e55b9a3b310
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
134de0bb85330eb3192e837e5054074969a80cbb4eaa1ee0b7870e55b9a3b310 lifedrive-bundle.tar.gz
|
|
@@ -27,7 +27,7 @@ Windows options:
|
|
|
27
27
|
--no-install-leither Require an already running Leither node.
|
|
28
28
|
--leither-service Configure Leither startup only.
|
|
29
29
|
--upgrade Upgrade an existing Windows LePan installation.
|
|
30
|
-
--household
|
|
30
|
+
--household Set up household users or renew an unclaimed setup invitation.
|
|
31
31
|
|
|
32
32
|
Run Windows Terminal or PowerShell as Administrator. Windows setup supports
|
|
33
33
|
native x64 Windows 10/11 and Windows Server; WSL and Git Bash are not required.
|
|
@@ -382,8 +382,8 @@ if ($Upgrade) {
|
|
|
382
382
|
if (Test-Path -LiteralPath (Join-Path $root "lifeDrive.owner") -PathType Leaf) {
|
|
383
383
|
throw "This node uses the legacy browser-owner setup, which Windows does not migrate automatically. Its files were left unchanged."
|
|
384
384
|
}
|
|
385
|
-
if (Test-Path -LiteralPath $routes -PathType Leaf) {
|
|
386
|
-
throw "
|
|
385
|
+
if ((Test-Path -LiteralPath $routes -PathType Leaf) -and -not $Household) {
|
|
386
|
+
throw "A LePan household already exists. Use --household for phone setup, or --upgrade for application updates."
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
389
|
|
|
@@ -28,7 +28,7 @@ Installer options:
|
|
|
28
28
|
--storage-max-gb GB Maximum disk space for a newly installed Leither node (default: 100 GB).
|
|
29
29
|
--release-base URL Alternate GitHub Release asset base URL.
|
|
30
30
|
--upgrade Upgrade an existing LePan without changing device authorization.
|
|
31
|
-
--household
|
|
31
|
+
--household Set up household users or renew an unclaimed setup invitation.
|
|
32
32
|
--household-config FILE Install mobile-led household setup using a private service configuration.
|
|
33
33
|
|
|
34
34
|
Setup options are forwarded to lifeDrive-setup.sh. Common examples:
|
|
@@ -172,14 +172,14 @@ choose_new_node_storage() {
|
|
|
172
172
|
valid_storage_gb "$STORAGE_MAX_GB" || { echo "Invalid saved Leither storage choice." >&2; return 1; }
|
|
173
173
|
fi
|
|
174
174
|
available_gb=$(available_storage_gb)
|
|
175
|
-
|
|
176
|
-
echo "
|
|
175
|
+
printf '\nStorage\n'
|
|
176
|
+
echo "Set the disk space limit for this node (LePan and other Leither data)."
|
|
177
177
|
if [[ "$available_gb" =~ ^[0-9]+$ ]]; then
|
|
178
|
-
echo "Available
|
|
178
|
+
echo " Available: ${available_gb} GB"
|
|
179
179
|
fi
|
|
180
180
|
if [[ -n "$STORAGE_MAX_GB" ]]; then
|
|
181
181
|
selected_gb="$STORAGE_MAX_GB"
|
|
182
|
-
echo "
|
|
182
|
+
echo " Selected limit: ${selected_gb} GB"
|
|
183
183
|
elif [[ -t 0 ]]; then
|
|
184
184
|
while true; do
|
|
185
185
|
read -r -p "Maximum Leither storage in GB [$default_gb]: " selected_gb
|
|
@@ -252,7 +252,6 @@ try {
|
|
|
252
252
|
throw error;
|
|
253
253
|
}
|
|
254
254
|
NODE
|
|
255
|
-
echo "Leither storage limit saved as ${STORAGE_MAX_GB} GB."
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
install_leither_binary() (
|
|
@@ -475,7 +474,8 @@ configure_macos_leither_firewall() {
|
|
|
475
474
|
return 1
|
|
476
475
|
}
|
|
477
476
|
|
|
478
|
-
|
|
477
|
+
printf '\nNetwork access\n'
|
|
478
|
+
echo "Allowing connections through the macOS firewall..."
|
|
479
479
|
firewall_apps=$("$firewall" --listapps) || {
|
|
480
480
|
echo "LePan setup could not read the macOS Application Firewall rules." >&2
|
|
481
481
|
return 1
|
|
@@ -501,7 +501,7 @@ configure_macos_leither_firewall() {
|
|
|
501
501
|
echo "The macOS Application Firewall still reports Leither as blocked." >&2
|
|
502
502
|
return 1
|
|
503
503
|
}
|
|
504
|
-
echo "
|
|
504
|
+
echo " Firewall access ready."
|
|
505
505
|
|
|
506
506
|
block_all_state=$("$firewall" --getblockall 2>/dev/null || true)
|
|
507
507
|
if [[ "$block_all_state" == *"enabled"* ]]; then
|
|
@@ -566,7 +566,7 @@ finish_new_node_storage() {
|
|
|
566
566
|
version=$(leither_version "$LEITHER_WORKDIR") || return 1
|
|
567
567
|
port=$(node -e 'const fs=require("node:fs"),p=require("node:path"),r=process.argv[1];const f=["SystemVars.json","systemvars.json"].map(n=>p.join(r,n)).find(f=>fs.existsSync(f));const v=JSON.parse(fs.readFileSync(f)).ServicePort??4800;if(!Number.isInteger(v)||v<1||v>65535)process.exit(1);process.stdout.write(String(v));' "$LEITHER_WORKDIR") || return 1
|
|
568
568
|
previous_pid=$(leither_service_pid) || return 1
|
|
569
|
-
echo "
|
|
569
|
+
echo "Applying the storage limit (restarting the newly created node)..."
|
|
570
570
|
case "$(uname -s)" in
|
|
571
571
|
Linux) sudo systemctl restart lifedrive-leither.service || return 1 ;;
|
|
572
572
|
Darwin) sudo launchctl kickstart -k system/uk.inoku.leither || return 1 ;;
|
|
@@ -578,7 +578,7 @@ finish_new_node_storage() {
|
|
|
578
578
|
return 1
|
|
579
579
|
}
|
|
580
580
|
rm -f -- "$pending" || return 1
|
|
581
|
-
echo "
|
|
581
|
+
echo " Storage ready: ${STORAGE_MAX_GB} GB limit."
|
|
582
582
|
}
|
|
583
583
|
|
|
584
584
|
bootstrap_leither() {
|
|
@@ -801,6 +801,7 @@ cleanup() {
|
|
|
801
801
|
}
|
|
802
802
|
trap cleanup EXIT HUP INT TERM
|
|
803
803
|
|
|
804
|
+
printf '\nInstall LePan\n'
|
|
804
805
|
if [[ "$RELEASE_BASE" == file://* ]]; then
|
|
805
806
|
echo "Loading the LePan release included with the npm package..."
|
|
806
807
|
else
|