@inoku/lepan 0.7.65 → 0.7.67
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/USER_MANUAL.md +3 -4
- package/bin/lifedrive.js +86 -18
- 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
|
|
89
|
+
After successful npm setup, the installer displays the QR code at the end of its output whenever the terminal is wide enough. If the whole code cannot fit on screen, it automatically opens the full-size QR image in the computer's image viewer. In the iPhone or Android app, open **Settings → Set up users → Scan QR code** and scan the displayed code. A private `lepan-pairing-<id>.png` is also saved, with its path printed as a fallback for remote sessions or computers without an image viewer. 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/USER_MANUAL.md
CHANGED
|
@@ -142,8 +142,7 @@ This keeps what step 2.1 installed and adds users and phones:
|
|
|
142
142
|
|
|
143
143
|
1. Creates LifeDrive's private configuration in `<Leither root>/.lifedrive-household/`.
|
|
144
144
|
2. Installs and starts the identity service: `lifedrive-identity` through systemd on Linux, `uk.inoku.lifedrive-identity` through launchd on macOS, or `LifeDrive Identity` through Task Scheduler on Windows. Leither itself is not restarted and must be running separately.
|
|
145
|
-
3.
|
|
146
|
-
*"Keep the invitation above private. It expires in ten minutes."*
|
|
145
|
+
3. Displays a **setup invitation** as a QR code. If the terminal is too small, the full-size QR image opens automatically on this computer. The invitation expires in ten minutes.
|
|
147
146
|
|
|
148
147
|
The setup invitation is what makes a phone the first user of this node. Treat it
|
|
149
148
|
like a key: do not post it, email it to others, or paste it anywhere public.
|
|
@@ -152,7 +151,7 @@ like a key: do not post it, email it to others, or paste it anywhere public.
|
|
|
152
151
|
|
|
153
152
|
Within ten minutes of the invitation being printed:
|
|
154
153
|
|
|
155
|
-
1.
|
|
154
|
+
1. Scan the installer’s final QR code or the QR image that opens automatically. The saved image path is a fallback for remote sessions or computers without an image viewer. If you used the standalone shell installer, copy the whole invitation block instead.
|
|
156
155
|
2. Open LifeDrive on the phone. The **Set up your LifeDrive** screen opens by itself (or open **Settings → Set up users**).
|
|
157
156
|
3. Enter **Your name** and a name for this device.
|
|
158
157
|
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**.
|
|
@@ -168,7 +167,7 @@ empty page, or **Settings → Set up users**, opens it again.
|
|
|
168
167
|
|
|
169
168
|
| Situation | What to do |
|
|
170
169
|
|---|---|
|
|
171
|
-
| The invitation expired before you pasted it | On the node,
|
|
170
|
+
| The invitation expired before you pasted it | On the node, run `npx --yes @inoku/lepan@latest --household` again for a fresh QR code. Setup renews an unclaimed invitation and preserves the existing identity; no manual service commands are needed. |
|
|
172
171
|
| You pasted it, but the app lost its connection or was closed | Open the app on **the same phone** and paste **the same invitation** again. A claim that has started can finish after the ten minutes have passed. Do not delete the app or ask for a new invitation: that would abandon the phone's pending key. |
|
|
173
172
|
| Setup says the node already has users | The node has been claimed. Add further devices from **My devices** (section 4), not with a new setup invitation. |
|
|
174
173
|
|
package/bin/lifedrive.js
CHANGED
|
@@ -12,6 +12,31 @@ const distributionDirectory = path.resolve(__dirname, "..", "dist");
|
|
|
12
12
|
const bundlePath = path.join(distributionDirectory, "lifedrive-bundle.tar.gz");
|
|
13
13
|
const checksumPath = `${bundlePath}.sha256`;
|
|
14
14
|
|
|
15
|
+
function openPairingImage(imagePath) {
|
|
16
|
+
// A remote server must not open a window in another user's desktop session.
|
|
17
|
+
if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return false;
|
|
18
|
+
let command;
|
|
19
|
+
let args;
|
|
20
|
+
if (process.platform === "darwin") {
|
|
21
|
+
command = "/usr/bin/open";
|
|
22
|
+
args = [imagePath];
|
|
23
|
+
} else if (process.platform === "win32") {
|
|
24
|
+
command = "powershell.exe";
|
|
25
|
+
args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
|
|
26
|
+
"Start-Process -FilePath $env:LEPAN_PAIRING_IMAGE -ErrorAction Stop"];
|
|
27
|
+
} else if (process.platform === "linux" && (process.env.DISPLAY || process.env.WAYLAND_DISPLAY)) {
|
|
28
|
+
command = "xdg-open";
|
|
29
|
+
args = [imagePath];
|
|
30
|
+
} else {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const result = spawnSync(command, args, {
|
|
34
|
+
stdio: "ignore", timeout: 5000,
|
|
35
|
+
env: { ...process.env, LEPAN_PAIRING_IMAGE: imagePath },
|
|
36
|
+
});
|
|
37
|
+
return !result.error && result.status === 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
async function showPairingCode(resultPath) {
|
|
16
41
|
if (!fs.existsSync(resultPath)) return;
|
|
17
42
|
// The child writes this only after setup succeeds. Never scrape its output:
|
|
@@ -22,24 +47,69 @@ async function showPairingCode(resultPath) {
|
|
|
22
47
|
}
|
|
23
48
|
const directory = fs.readFileSync(`${resultPath}.directory`, "utf8");
|
|
24
49
|
if (!path.isAbsolute(directory)) throw new Error("Invalid pairing image directory");
|
|
25
|
-
const QRCode = require("qrcode");
|
|
26
50
|
const payload = JSON.stringify(pairing);
|
|
51
|
+
// Only encode fields consumed by the existing Android and iOS setup parsers.
|
|
52
|
+
// The node enforces expiry and returns account details after a successful claim.
|
|
53
|
+
// Keep the full invitation in the import file, and leave legacy identities intact.
|
|
54
|
+
// iOS still requires certificate_sha256 even when Leither transport uses a key.
|
|
55
|
+
const qrPayload = pairing.type === "lifedrive-node-setup"
|
|
56
|
+
? JSON.stringify(Object.fromEntries([
|
|
57
|
+
"type", "version", "app_id", "node_id", "endpoint", "transport",
|
|
58
|
+
"transport_key", "certificate_sha256", "request_id", "secret",
|
|
59
|
+
].filter(key => Object.hasOwn(pairing, key)).map(key => [key, pairing[key]])))
|
|
60
|
+
: payload;
|
|
61
|
+
const basename = path.join(directory, `lepan-pairing-${randomUUID()}`);
|
|
62
|
+
const imagePath = `${basename}.png`;
|
|
63
|
+
const identityPath = `${basename}.json`;
|
|
64
|
+
// Older mobile releases support importing this file but have no setup scanner.
|
|
65
|
+
// Keep a private copy even when QR generation succeeds.
|
|
66
|
+
fs.writeFileSync(identityPath, payload, { flag: "wx", mode: 0o600 });
|
|
27
67
|
const options = { errorCorrectionLevel: "M", margin: 4, scale: 8 };
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
const privacyNote = pairing.type === "lifedrive-node-setup"
|
|
69
|
+
? "Keep it private. This invitation expires ten minutes after creation."
|
|
70
|
+
: "This contains your device private key. Keep it private and delete it after pairing.";
|
|
71
|
+
const renewalHint = pairing.type === "lifedrive-node-setup"
|
|
72
|
+
? "\nInvitation expired? Run this again on the node:\n npx --yes @inoku/lepan@latest --household\n"
|
|
73
|
+
: "";
|
|
74
|
+
let modules;
|
|
75
|
+
try {
|
|
76
|
+
const QRCode = require("qrcode");
|
|
77
|
+
const png = await QRCode.toBuffer(qrPayload, options);
|
|
78
|
+
modules = QRCode.create(qrPayload, options).modules;
|
|
79
|
+
// Unique names and exclusive creation avoid overwriting another device's
|
|
80
|
+
// code or following a pre-existing symlink. Keep this outside the app bundle.
|
|
81
|
+
fs.writeFileSync(imagePath, png, { flag: "wx", mode: 0o600 });
|
|
82
|
+
} catch {
|
|
83
|
+
// The child no longer prints secrets. Preserve an importable fallback
|
|
84
|
+
// before runInstaller removes the temporary handoff directory.
|
|
85
|
+
process.stderr.write("\nNext: pair your phone\n\nThe QR code could not be created. Use the saved pairing file instead.\n");
|
|
86
|
+
process.stderr.write(` ${identityPath}\n\n`);
|
|
87
|
+
process.stderr.write("Transfer this file to your phone, then select it in LePan > Settings > Set up users > Choose identity file.\n");
|
|
88
|
+
process.stderr.write(`${privacyNote}\n`);
|
|
89
|
+
process.stderr.write(renewalHint);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Never hide a terminal code just because the window is short. Open the PNG
|
|
93
|
+
// automatically when the whole code cannot be visible at once, and put the
|
|
94
|
+
// terminal code last so subsequent instructions cannot scroll it away.
|
|
41
95
|
const width = modules.size + options.margin * 2;
|
|
42
|
-
|
|
96
|
+
const height = Math.ceil(width / 2);
|
|
97
|
+
const fitsWidth = process.stdout.isTTY && process.stdout.columns > width;
|
|
98
|
+
const fitsScreen = fitsWidth && process.stdout.rows >= height + 3;
|
|
99
|
+
const imageOpened = !fitsScreen && openPairingImage(imagePath);
|
|
100
|
+
process.stdout.write("\nPair your phone\n\n");
|
|
101
|
+
process.stdout.write("Open LePan > Settings > Set up users > Scan QR code.\n");
|
|
102
|
+
process.stdout.write(imageOpened
|
|
103
|
+
? "The QR image has opened automatically. Scan it with your phone.\n"
|
|
104
|
+
: fitsWidth ? "Scan the QR code below with your phone.\n"
|
|
105
|
+
: "This terminal is too narrow for the QR code and no image viewer could be opened. Open the saved image below.\n");
|
|
106
|
+
process.stdout.write(`${privacyNote}\n`);
|
|
107
|
+
process.stdout.write(`\nSaved QR image: ${imagePath}\n`);
|
|
108
|
+
process.stdout.write(`Pairing file (Choose identity file): ${identityPath}\n`);
|
|
109
|
+
process.stdout.write("Keep this computer on and connected while using LePan.\n");
|
|
110
|
+
process.stdout.write(renewalHint);
|
|
111
|
+
if (fitsWidth) {
|
|
112
|
+
process.stdout.write("\n");
|
|
43
113
|
// Explicit colors and a four-module quiet zone work on light/dark themes.
|
|
44
114
|
const dark = (x, y) => x >= 0 && y >= 0 && x < modules.size && y < modules.size && modules.get(y, x);
|
|
45
115
|
let terminal = "";
|
|
@@ -51,8 +121,6 @@ async function showPairingCode(resultPath) {
|
|
|
51
121
|
terminal += "\x1b[0m\n";
|
|
52
122
|
}
|
|
53
123
|
process.stdout.write(terminal);
|
|
54
|
-
} else {
|
|
55
|
-
process.stdout.write("Open the saved PNG on this computer to scan it.\n");
|
|
56
124
|
}
|
|
57
125
|
}
|
|
58
126
|
|
|
@@ -73,7 +141,7 @@ async function runInstaller(command, args) {
|
|
|
73
141
|
try { await showPairingCode(resultPath); }
|
|
74
142
|
catch {
|
|
75
143
|
// Parser errors may contain identity text; never print them.
|
|
76
|
-
process.stderr.write("
|
|
144
|
+
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
145
|
}
|
|
78
146
|
}
|
|
79
147
|
} 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: 44139682 bytes
|
|
4
|
+
sha256: 2173717546eb881f72dce0fdd7a5a6727bfc5286b4c23f948528b9d8f1a2fde8
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2173717546eb881f72dce0fdd7a5a6727bfc5286b4c23f948528b9d8f1a2fde8 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
|