parrot-blackbox 1.0.4 → 1.0.5
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/package.json +1 -1
- package/src/backup/snapshot.js +21 -6
- package/src/commands/manage.js +21 -6
- package/src/commands/service.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parrot-blackbox",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "parrot-blackbox — crash-proof, multi-cloud backup & recovery automation for Parrot OS. Daily/weekly off-disk backups with automatic catch-up, smart storage across many MEGA + Google Drive accounts, Timeshift snapshot backups and one-command restore.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.js",
|
package/src/backup/snapshot.js
CHANGED
|
@@ -17,7 +17,7 @@ import { refreshAccounts } from '../storage/accounts.js';
|
|
|
17
17
|
import { planAndPlace } from '../storage/allocator.js';
|
|
18
18
|
import { listArtifacts, removeArtifact } from '../storage/archive.js';
|
|
19
19
|
import { planPrune } from './retention.js';
|
|
20
|
-
import { sudoInteractive, sudoNonInteractive } from '../util/sudo.js';
|
|
20
|
+
import { sudoInteractive, sudoNonInteractive, sudoInteractiveCapture } from '../util/sudo.js';
|
|
21
21
|
|
|
22
22
|
export class SudoDeferredError extends Error {
|
|
23
23
|
constructor() {
|
|
@@ -50,18 +50,33 @@ function findPathInLine(line) {
|
|
|
50
50
|
null;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* `timeshift --list` needs admin on most installs — run it with sudo.
|
|
55
|
+
* Interactive: capture stdout while keeping stdin (so the sudo password
|
|
56
|
+
* prompt works). Non-interactive: `sudo -n`, falling back to a direct call
|
|
57
|
+
* for the few builds that allow unprivileged listing.
|
|
58
|
+
*/
|
|
59
|
+
async function runTimeshiftList({ privileged = 'noninteractive' } = {}) {
|
|
60
|
+
if (privileged === 'interactive') {
|
|
61
|
+
try {
|
|
62
|
+
const res = await sudoInteractiveCapture(['timeshift', '--list']);
|
|
63
|
+
return (res.exitCode === 0 ? res.stdout : res.stdout || res.stderr) || '';
|
|
64
|
+
} catch {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
55
68
|
try {
|
|
56
|
-
const res = await
|
|
57
|
-
|
|
69
|
+
const res = await sudoNonInteractive(['timeshift', '--list']);
|
|
70
|
+
if (res.exitCode === 0) return res.stdout || '';
|
|
71
|
+
const direct = await execa('timeshift', ['--list'], { reject: false });
|
|
72
|
+
return (direct.exitCode === 0 ? direct.stdout : direct.stdout || direct.stderr) || '';
|
|
58
73
|
} catch {
|
|
59
74
|
return '';
|
|
60
75
|
}
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
export async function listLocalSnapshots({ privileged = 'noninteractive' } = {}) {
|
|
64
|
-
const text = await runTimeshiftList();
|
|
79
|
+
const text = await runTimeshiftList({ privileged });
|
|
65
80
|
return parseTimeshiftList(text);
|
|
66
81
|
}
|
|
67
82
|
|
package/src/commands/manage.js
CHANGED
|
@@ -185,14 +185,29 @@ export async function runRepair({ auto = false } = {}) {
|
|
|
185
185
|
// 3. Service
|
|
186
186
|
try {
|
|
187
187
|
const { serviceFile, daemonLogFile } = await import('../core/paths.js');
|
|
188
|
-
const { serviceBackend } = await import('./service.js');
|
|
188
|
+
const { serviceBackend, installService } = await import('./service.js');
|
|
189
189
|
const fsMod = await import('node:fs');
|
|
190
|
-
if (serviceBackend() === 'systemd'
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
if (serviceBackend() === 'systemd') {
|
|
191
|
+
// The ExecStart line must reference a REAL absolute path; the v1.0.4 unit
|
|
192
|
+
// wrote `node parrot-blackbox daemon foreground` (bare name) which crashed
|
|
193
|
+
// with "Cannot find module '/home/artkins/parrot-blackbox'".
|
|
194
|
+
let unitOk = false;
|
|
195
|
+
try {
|
|
196
|
+
const unit = fsMod.readFileSync(serviceFile(), 'utf8');
|
|
197
|
+
const execLine = unit.split('\n').find((l) => l.startsWith('ExecStart=')) || '';
|
|
198
|
+
unitOk = fsMod.existsSync(serviceFile()) && /ExecStart=\S+/.test(execLine) && /\//.test(execLine);
|
|
199
|
+
} catch {
|
|
200
|
+
unitOk = false;
|
|
201
|
+
}
|
|
202
|
+
if (!unitOk) {
|
|
203
|
+
const backend = await installService();
|
|
204
|
+
p.log.success(`Always-on service re-written via ${backend}.`);
|
|
205
|
+
fixed.push('service');
|
|
206
|
+
} else {
|
|
207
|
+
p.log.success('Service OK.');
|
|
208
|
+
}
|
|
194
209
|
} else {
|
|
195
|
-
p.log.success('Service OK.');
|
|
210
|
+
p.log.success('Service backend OK.');
|
|
196
211
|
}
|
|
197
212
|
} catch (e) {
|
|
198
213
|
p.log.warn(`Service check failed: ${e.message}`);
|
package/src/commands/service.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { execa } from 'execa';
|
|
11
12
|
import { serviceFile, daemonLogFile } from '../core/paths.js';
|
|
12
13
|
import { loadConfig, journal, hasCommandSync } from '../core/store.js';
|
|
@@ -15,6 +16,8 @@ function findCliBin() {
|
|
|
15
16
|
// npm-installed global binary (preferred) or our own bin entry.
|
|
16
17
|
const candidates = [
|
|
17
18
|
process.env.PBB_BIN,
|
|
19
|
+
// This module lives in src/commands/ → ../../bin/parrot-blackbox.js
|
|
20
|
+
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'bin', 'parrot-blackbox.js'),
|
|
18
21
|
path.join(path.dirname(process.argv[1] || ''), 'parrot-blackbox.js'),
|
|
19
22
|
];
|
|
20
23
|
return candidates.find((c) => c && fs.existsSync(c)) || 'parrot-blackbox';
|