@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 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 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.
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 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");
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
- // direct the user to the full-resolution PNG instead of a broken code.
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
- if (process.stdout.isTTY && (!process.stdout.columns || process.stdout.columns > width)) {
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 PNG on this computer to scan it.\n");
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("LePan is installed, but its pairing QR code could not be created. Use the identity file or invitation shown above.\n");
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: 44150306 bytes
4
- sha256: ef901f174a43f1a7288516db7834821dbdde61a2eb46d4916c82f6ec880972cd
3
+ size: 44139710 bytes
4
+ sha256: 134de0bb85330eb3192e837e5054074969a80cbb4eaa1ee0b7870e55b9a3b310
@@ -1 +1 @@
1
- ef901f174a43f1a7288516db7834821dbdde61a2eb46d4916c82f6ec880972cd lifedrive-bundle.tar.gz
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 Accepted for parity; Windows uses household mode by default.
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 "An initialized LePan household already exists. Rerun with --upgrade."
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 Prepare mobile household setup using the existing node address.
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
- echo
176
- echo "Choose the maximum hard-drive space Leither may use for LePan and any other data on this node."
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 now on the selected drive: ${available_gb} GB."
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 "Leither storage limit: ${selected_gb} GB."
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
- echo "Allowing Leither through the macOS Application Firewall..."
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 "macOS Application Firewall allows incoming connections to Leither."
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 "Restarting the newly created Leither node to activate its storage limit..."
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 "Leither restarted with the ${STORAGE_MAX_GB} GB storage limit."
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inoku/lepan",
3
- "version": "0.7.65",
3
+ "version": "0.7.66",
4
4
  "description": "Install LePan and its Leither runtime on Linux, macOS, or Windows",
5
5
  "os": [
6
6
  "linux",