routstrd 0.4.1 → 0.4.2
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/bun.lock +316 -2
- package/dist/daemon/index.js +69744 -54589
- package/dist/index.js +183 -85
- package/package.json +2 -2
- package/src/cli.ts +98 -12
- package/src/daemon/config-store.ts +79 -4
- package/src/daemon/models.test.ts +81 -0
- package/src/daemon/models.ts +18 -2
- package/src/integrations/pi.ts +17 -1
- package/src/tui/usage/data.ts +13 -2
- package/src/tui/usage/render.ts +8 -0
- package/src/utils/config.ts +3 -1
- package/tests/daemon/config-store.perms.test.ts +55 -0
- package/tests/daemon/config-store.scenario.ts +113 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Scenario runner for config-store.perms.test.ts — executed as a standalone
|
|
2
|
+
// bun process with ROUTSTRD_DIR set before module evaluation. Not a test file.
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "fs";
|
|
10
|
+
|
|
11
|
+
const { CONFIG_DIR, CONFIG_FILE } = await import("../../src/utils/config");
|
|
12
|
+
const {
|
|
13
|
+
ensureDirs,
|
|
14
|
+
ensureDirsSync,
|
|
15
|
+
loadDaemonConfig,
|
|
16
|
+
loadDaemonConfigSync,
|
|
17
|
+
saveDaemonConfig,
|
|
18
|
+
REQUESTS_DIR,
|
|
19
|
+
} = await import("../../src/daemon/config-store");
|
|
20
|
+
|
|
21
|
+
const modeOf = (p: string): number => statSync(p).mode & 0o777;
|
|
22
|
+
|
|
23
|
+
const baseConfig = {
|
|
24
|
+
port: 8008,
|
|
25
|
+
host: "127.0.0.1",
|
|
26
|
+
provider: null,
|
|
27
|
+
cocodPath: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function assert(cond: unknown, msg: string): void {
|
|
31
|
+
if (!cond) {
|
|
32
|
+
console.error(`ASSERT-FAIL: ${msg}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const scenario = process.argv[2];
|
|
38
|
+
|
|
39
|
+
switch (scenario) {
|
|
40
|
+
case "fresh-install": {
|
|
41
|
+
saveDaemonConfig({
|
|
42
|
+
...baseConfig,
|
|
43
|
+
nsec: "nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
44
|
+
});
|
|
45
|
+
assert(modeOf(CONFIG_DIR) === 0o700, `dir mode ${modeOf(CONFIG_DIR).toString(8)} != 700`);
|
|
46
|
+
assert(modeOf(CONFIG_FILE) === 0o600, `file mode ${modeOf(CONFIG_FILE).toString(8)} != 600`);
|
|
47
|
+
assert(
|
|
48
|
+
readdirSync(CONFIG_DIR).filter((f) => f.endsWith(".tmp")).length === 0,
|
|
49
|
+
"temp file left behind",
|
|
50
|
+
);
|
|
51
|
+
const loaded = await loadDaemonConfig();
|
|
52
|
+
assert(
|
|
53
|
+
loaded.nsec === "nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
54
|
+
"nsec round-trip failed",
|
|
55
|
+
);
|
|
56
|
+
assert(loaded.port === 8008, "port round-trip failed");
|
|
57
|
+
|
|
58
|
+
await ensureDirs();
|
|
59
|
+
assert(modeOf(CONFIG_DIR) === 0o700, "ensureDirs dir mode");
|
|
60
|
+
assert(modeOf(REQUESTS_DIR) === 0o700, "ensureDirs requests dir mode");
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case "repair-perms": {
|
|
65
|
+
// Simulate an install written by the vulnerable version.
|
|
66
|
+
ensureDirsSync();
|
|
67
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(baseConfig), { mode: 0o644 });
|
|
68
|
+
chmodSync(CONFIG_DIR, 0o755);
|
|
69
|
+
chmodSync(CONFIG_FILE, 0o644);
|
|
70
|
+
assert(modeOf(CONFIG_FILE) === 0o644, "setup: file not 0644");
|
|
71
|
+
|
|
72
|
+
// Load repairs the file mode...
|
|
73
|
+
loadDaemonConfigSync();
|
|
74
|
+
assert(modeOf(CONFIG_FILE) === 0o600, "load did not repair file mode");
|
|
75
|
+
|
|
76
|
+
// ...and the next save repairs the directory mode too.
|
|
77
|
+
chmodSync(CONFIG_DIR, 0o755);
|
|
78
|
+
saveDaemonConfig({ ...baseConfig, port: 9009 });
|
|
79
|
+
assert(modeOf(CONFIG_DIR) === 0o700, "save did not repair dir mode");
|
|
80
|
+
assert(modeOf(CONFIG_FILE) === 0o600, "save did not keep file mode");
|
|
81
|
+
assert(loadDaemonConfigSync().port === 9009, "save did not persist port");
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "write-failure": {
|
|
86
|
+
// Turn the config dir into a regular file: mkdir fails and no temp write
|
|
87
|
+
// can succeed, so saveDaemonConfig must surface the error synchronously.
|
|
88
|
+
writeFileSync(process.env.ROUTSTRD_DIR!, "blocked");
|
|
89
|
+
let threw = false;
|
|
90
|
+
try {
|
|
91
|
+
saveDaemonConfig(baseConfig);
|
|
92
|
+
} catch {
|
|
93
|
+
threw = true;
|
|
94
|
+
}
|
|
95
|
+
assert(threw, "saveDaemonConfig did not throw on unwritable target");
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case "corrupt-json": {
|
|
100
|
+
ensureDirsSync();
|
|
101
|
+
writeFileSync(CONFIG_FILE, "{ not json");
|
|
102
|
+
const loaded = await loadDaemonConfig();
|
|
103
|
+
assert(loaded.port === 8008, "did not fall back to defaults");
|
|
104
|
+
assert(existsSync(CONFIG_FILE), "config file vanished");
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
default:
|
|
109
|
+
console.error(`unknown scenario: ${scenario}`);
|
|
110
|
+
process.exit(2);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log("SCENARIO-OK");
|