kitowall 7.5.0 → 7.7.0
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 +60 -6
- package/dist/cli.js +55 -4
- package/dist/core/setup.js +407 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,25 @@ Dependencias del stack principal, según la función usada:
|
|
|
76
76
|
|
|
77
77
|
La distribución recomendada de la UI es la `AppImage`.
|
|
78
78
|
|
|
79
|
+
### Paso previo obligatorio
|
|
80
|
+
Antes de abrir `Kitowall`, el host debe tener:
|
|
81
|
+
- `nodejs`
|
|
82
|
+
- `npm`
|
|
83
|
+
- `kitowall` CLI instalado para el usuario actual
|
|
84
|
+
|
|
85
|
+
Comando recomendado:
|
|
86
|
+
```bash
|
|
87
|
+
npm i -g --prefix ~/.local kitowall@latest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Verificación rápida:
|
|
91
|
+
```bash
|
|
92
|
+
command -v node
|
|
93
|
+
command -v npm
|
|
94
|
+
command -v kitowall
|
|
95
|
+
kitowall --version
|
|
96
|
+
```
|
|
97
|
+
|
|
79
98
|
### Importante
|
|
80
99
|
No ejecutes la AppImage con `sudo`.
|
|
81
100
|
|
|
@@ -96,9 +115,10 @@ Razón:
|
|
|
96
115
|
|
|
97
116
|
### Flujo recomendado
|
|
98
117
|
1. Descarga la última `AppImage` desde GitHub Releases.
|
|
99
|
-
2.
|
|
100
|
-
3.
|
|
101
|
-
4.
|
|
118
|
+
2. Asegúrate de tener `nodejs`, `npm` y `kitowall` CLI instalados.
|
|
119
|
+
3. Dale permisos de ejecución.
|
|
120
|
+
4. Ábrela como usuario normal.
|
|
121
|
+
5. Usa el instalador de dependencias desde la UI.
|
|
102
122
|
|
|
103
123
|
## Instalación de dependencias del host
|
|
104
124
|
|
|
@@ -148,6 +168,13 @@ Eso ejecuta:
|
|
|
148
168
|
### Nota
|
|
149
169
|
El script puede necesitar privilegios para instalar paquetes del sistema. La forma recomendada para usuarios finales sigue siendo la UI con `pkexec`.
|
|
150
170
|
|
|
171
|
+
### Si el CLI del host está desactualizado
|
|
172
|
+
Si la UI indica que el `kitowall` del host no soporta un comando nuevo, actualízalo con:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm i -g --prefix ~/.local kitowall@latest
|
|
176
|
+
```
|
|
177
|
+
|
|
151
178
|
## Inicialización
|
|
152
179
|
|
|
153
180
|
Después del bootstrap, inicializa el stack:
|
|
@@ -361,6 +388,25 @@ Common dependencies depending on enabled features:
|
|
|
361
388
|
|
|
362
389
|
The recommended desktop distribution for the UI is the `AppImage`.
|
|
363
390
|
|
|
391
|
+
### Required First Step
|
|
392
|
+
Before opening `Kitowall`, the host must already have:
|
|
393
|
+
- `nodejs`
|
|
394
|
+
- `npm`
|
|
395
|
+
- the `kitowall` CLI installed for the current user
|
|
396
|
+
|
|
397
|
+
Recommended command:
|
|
398
|
+
```bash
|
|
399
|
+
npm i -g --prefix ~/.local kitowall@latest
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Quick verification:
|
|
403
|
+
```bash
|
|
404
|
+
command -v node
|
|
405
|
+
command -v npm
|
|
406
|
+
command -v kitowall
|
|
407
|
+
kitowall --version
|
|
408
|
+
```
|
|
409
|
+
|
|
364
410
|
### Important
|
|
365
411
|
Do not run the AppImage with `sudo`.
|
|
366
412
|
|
|
@@ -381,9 +427,10 @@ Reason:
|
|
|
381
427
|
|
|
382
428
|
### Recommended Flow
|
|
383
429
|
1. Download the latest `AppImage` from GitHub Releases.
|
|
384
|
-
2. Make
|
|
385
|
-
3.
|
|
386
|
-
4.
|
|
430
|
+
2. Make sure `nodejs`, `npm`, and the `kitowall` CLI are installed.
|
|
431
|
+
3. Make it executable.
|
|
432
|
+
4. Launch it as a normal user.
|
|
433
|
+
5. Use the dependency installer from the UI.
|
|
387
434
|
|
|
388
435
|
## Host Dependency Installation
|
|
389
436
|
|
|
@@ -437,6 +484,13 @@ That handles:
|
|
|
437
484
|
### Note
|
|
438
485
|
The script may need privileges for system package installation. For end users, the recommended path remains the UI flow using `pkexec`.
|
|
439
486
|
|
|
487
|
+
### If the host CLI is outdated
|
|
488
|
+
If the UI reports that the host `kitowall` does not support a newer command, update it with:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
npm i -g --prefix ~/.local kitowall@latest
|
|
492
|
+
```
|
|
493
|
+
|
|
440
494
|
## Initialization
|
|
441
495
|
|
|
442
496
|
After bootstrap, initialize the stack:
|
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,7 @@ const systemd_1 = require("./core/systemd");
|
|
|
43
43
|
const init_1 = require("./core/init");
|
|
44
44
|
const watch_1 = require("./core/watch");
|
|
45
45
|
const doctor_1 = require("./core/doctor");
|
|
46
|
+
const setup_1 = require("./core/setup");
|
|
46
47
|
const cache_1 = require("./core/cache");
|
|
47
48
|
const favorites_1 = require("./core/favorites");
|
|
48
49
|
const history_1 = require("./core/history");
|
|
@@ -73,6 +74,9 @@ function printUsage() {
|
|
|
73
74
|
console.log(`kitowall <command> [options]
|
|
74
75
|
|
|
75
76
|
Commands:
|
|
77
|
+
version | --version | -v Show CLI version
|
|
78
|
+
|
|
79
|
+
Core:
|
|
76
80
|
outputs List outputs (Wayland monitors)
|
|
77
81
|
next [--pack <name>] [--namespace <ns>] Apply next wallpapers (respects mode unless --force)
|
|
78
82
|
status Show current state in JSON
|
|
@@ -119,6 +123,8 @@ Commands:
|
|
|
119
123
|
logs [--limit <n>] [--source <name>] [--pack <name>] [--level <info|warn|error>] [--q <text>]
|
|
120
124
|
Show system logs (requests/downloads/errors)
|
|
121
125
|
logs clear Clear system logs
|
|
126
|
+
|
|
127
|
+
Wallpaper Engine:
|
|
122
128
|
we config set-api-key <key> Save Steam Web API key (~/.config/kitowall/we.json)
|
|
123
129
|
we config get-steam-roots Show configured manual Steam roots
|
|
124
130
|
we config set-steam-roots <a,b,c> Save manual Steam roots (steam root or workshop/content/431960)
|
|
@@ -141,6 +147,8 @@ Commands:
|
|
|
141
147
|
Apply wallpapers in batch by monitor map
|
|
142
148
|
we stop [--monitor <name> | --all] Stop livewallpaper instances and restore previous services
|
|
143
149
|
we coexist enter|exit|status Temporarily stop/restore wallpaper rotation services
|
|
150
|
+
|
|
151
|
+
Live Wallpapers:
|
|
144
152
|
live init Initialize LiveWallpapers storage/index
|
|
145
153
|
live list [--favorites] [--json] List downloaded LiveWallpapers from local index
|
|
146
154
|
live browse [--page <n>] [--quality 4k|all] [--provider moewalls|motionbgs|all]
|
|
@@ -165,19 +173,32 @@ Commands:
|
|
|
165
173
|
live config runner [--bin-name <name>]
|
|
166
174
|
live config apply-defaults [--video-fps <n>] [--video-speed <n>] [--hwaccel auto|nvdec|vaapi|none]
|
|
167
175
|
[--quality low|medium|high|ultra] [--pause-on-steam-game true|false] [--steam-poll-ms <n>]
|
|
176
|
+
live view Show consolidated livewallpaper runtime/config data
|
|
168
177
|
live doctor [--fix]
|
|
169
|
-
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
170
178
|
|
|
179
|
+
System / Host:
|
|
180
|
+
check [--namespace <ns>] [--json] Quick system check (no changes)
|
|
171
181
|
init [--namespace <ns>] [--apply] [--force] Setup kitowall (install daemon + watcher + next.service), validate deps
|
|
172
182
|
watch [--debounce <ms>] Watch Hyprland monitor hotplug events and apply wallpapers
|
|
173
183
|
doctor [--namespace <ns>]
|
|
174
184
|
health [--namespace <ns>]
|
|
175
|
-
|
|
176
185
|
install-systemd [--every <dur>] Install + enable systemd user timer (timer only)
|
|
177
186
|
uninstall-systemd Disable systemd timer
|
|
178
187
|
systemd-status Show systemd timer status
|
|
179
188
|
rotate-now [--pack <name>] Apply next wallpapers ignoring manual mode (for timers)
|
|
180
189
|
mode <manual|rotate> Set mode persistently in state.json
|
|
190
|
+
|
|
191
|
+
Host Setup:
|
|
192
|
+
host-setup check dependency <id> [--json]
|
|
193
|
+
Check one host dependency
|
|
194
|
+
host-setup check service <id> [--namespace <ns>] [--json]
|
|
195
|
+
Check one host service/config item
|
|
196
|
+
host-setup list [--namespace <ns>] [--json]
|
|
197
|
+
List all host dependencies and services with status
|
|
198
|
+
host-setup versions [--json]
|
|
199
|
+
Show local/latest versions for kitowall, kitsune, and rendercore
|
|
200
|
+
host-setup install <id> [--namespace <ns>]
|
|
201
|
+
Install, reinstall, or repair one host item
|
|
181
202
|
Examples:
|
|
182
203
|
kitowall init --namespace kitowall --apply
|
|
183
204
|
kitowall install-systemd --every 5m
|
|
@@ -349,6 +370,36 @@ async function main() {
|
|
|
349
370
|
process.exitCode = report.ok ? 0 : 2;
|
|
350
371
|
return;
|
|
351
372
|
}
|
|
373
|
+
if (cmd === 'host-setup') {
|
|
374
|
+
const action = cleanOpt(args[1] ?? null);
|
|
375
|
+
const target = cleanOpt(args[2] ?? null);
|
|
376
|
+
const id = cleanOpt(args[3] ?? null);
|
|
377
|
+
const namespace = cleanOpt(getOptionValue(args, '--namespace')) ?? 'kitowall';
|
|
378
|
+
if (action === 'check' && target === 'dependency' && id) {
|
|
379
|
+
console.log(JSON.stringify(await (0, setup_1.checkSetupDependency)(id), null, 2));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (action === 'check' && target === 'service' && id) {
|
|
383
|
+
console.log(JSON.stringify(await (0, setup_1.checkSetupService)(id, namespace), null, 2));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (action === 'list') {
|
|
387
|
+
console.log(JSON.stringify(await (0, setup_1.listSetupStatus)(namespace), null, 2));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (action === 'versions') {
|
|
391
|
+
console.log(JSON.stringify(await (0, setup_1.listSetupVersions)(), null, 2));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (action === 'install' && target) {
|
|
395
|
+
const result = await (0, setup_1.installSetupItem)(target, namespace);
|
|
396
|
+
if (result.logs.trim())
|
|
397
|
+
process.stdout.write(result.logs);
|
|
398
|
+
process.exitCode = result.ok ? 0 : (result.code || 1);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
throw new Error('Usage: host-setup <check dependency <id>|check service <id>|list|versions|install <id>> [--namespace <ns>] [--json]');
|
|
402
|
+
}
|
|
352
403
|
if (cmd === 'we') {
|
|
353
404
|
const action = cleanOpt(args[1] ?? null);
|
|
354
405
|
if (!action) {
|
|
@@ -521,7 +572,7 @@ async function main() {
|
|
|
521
572
|
if (cmd === 'live') {
|
|
522
573
|
const action = cleanOpt(args[1] ?? null);
|
|
523
574
|
if (!action)
|
|
524
|
-
throw new Error('Usage: live <init|list|browse|search|resolve|preview|preview-clear|fetch|apply|auto-apply|favorite|remove|thumb|open|service-autostart|config|doctor> ...');
|
|
575
|
+
throw new Error('Usage: live <init|list|browse|search|resolve|preview|preview-clear|fetch|apply|auto-apply|favorite|remove|thumb|open|service-autostart|config|view|doctor> ...');
|
|
525
576
|
if (action === 'init') {
|
|
526
577
|
console.log(JSON.stringify((0, live_1.liveInit)(), null, 2));
|
|
527
578
|
return;
|
|
@@ -713,7 +764,7 @@ async function main() {
|
|
|
713
764
|
console.log(JSON.stringify((0, live_1.liveViewData)(), null, 2));
|
|
714
765
|
return;
|
|
715
766
|
}
|
|
716
|
-
throw new Error('Usage: live <init|list|browse|search|resolve|preview|preview-clear|fetch|apply|auto-apply|favorite|remove|thumb|open|service-autostart|config|doctor> ...');
|
|
767
|
+
throw new Error('Usage: live <init|list|browse|search|resolve|preview|preview-clear|fetch|apply|auto-apply|favorite|remove|thumb|open|service-autostart|config|view|doctor> ...');
|
|
717
768
|
}
|
|
718
769
|
// Regular commands (need config/state)
|
|
719
770
|
const config = (0, config_1.loadConfig)();
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOST_SERVICE_DEFS = exports.HOST_DEPENDENCY_DEFS = void 0;
|
|
4
|
+
exports.resolveHostBinPath = resolveHostBinPath;
|
|
5
|
+
exports.checkSetupDependency = checkSetupDependency;
|
|
6
|
+
exports.checkSetupService = checkSetupService;
|
|
7
|
+
exports.listSetupStatus = listSetupStatus;
|
|
8
|
+
exports.listSetupVersions = listSetupVersions;
|
|
9
|
+
exports.installSetupItem = installSetupItem;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const exec_1 = require("../utils/exec");
|
|
14
|
+
const init_1 = require("./init");
|
|
15
|
+
const systemd_1 = require("./systemd");
|
|
16
|
+
const ROOT_DIR = (0, node_path_1.resolve)(__dirname, '..', '..');
|
|
17
|
+
const RELEASE_CACHE = new Map();
|
|
18
|
+
exports.HOST_DEPENDENCY_DEFS = [
|
|
19
|
+
{ id: 'kitowall', bin: 'kitowall', label: 'Kitowall CLI', installer: 'kitowall-only' },
|
|
20
|
+
{ id: 'kitsune', bin: 'kitsune', label: 'Kitsune', installer: 'kitsune-only' },
|
|
21
|
+
{ id: 'kitsune-rendercore', bin: 'kitsune-rendercore', label: 'Kitsune RenderCore', installer: 'kitsune-only' },
|
|
22
|
+
{ id: 'swww', bin: 'swww', label: 'swww', installer: 'swww', system: true },
|
|
23
|
+
{ id: 'swww-daemon', bin: 'swww-daemon', label: 'swww-daemon', installer: 'swww-daemon', system: true },
|
|
24
|
+
{ id: 'hyprctl', bin: 'hyprctl', label: 'hyprctl', installer: 'hyprctl', system: true },
|
|
25
|
+
{ id: 'cava', bin: 'cava', label: 'cava', installer: 'cava', system: true }
|
|
26
|
+
];
|
|
27
|
+
exports.HOST_SERVICE_DEFS = [
|
|
28
|
+
{ id: 'kitowall-config', label: 'Kitowall config', installer: 'kitowall-config' },
|
|
29
|
+
{ id: 'kitowall-next.timer', label: 'kitowall-next.timer', installer: 'kitowall-next.timer' },
|
|
30
|
+
{ id: 'kitsune-rendercore.service', label: 'kitsune-rendercore.service', installer: 'kitsune-rendercore.service' }
|
|
31
|
+
];
|
|
32
|
+
function homeDir() {
|
|
33
|
+
return process.env.HOME || (0, node_os_1.homedir)();
|
|
34
|
+
}
|
|
35
|
+
async function fileExists(targetPath) {
|
|
36
|
+
try {
|
|
37
|
+
await node_fs_1.promises.access(targetPath);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function normalizeReleaseVersion(input) {
|
|
45
|
+
return String(input ?? '').trim().replace(/^v/, '');
|
|
46
|
+
}
|
|
47
|
+
function uniqueEntries(values) {
|
|
48
|
+
return [...new Set(values.map(value => String(value).trim()).filter(Boolean))];
|
|
49
|
+
}
|
|
50
|
+
function hostPathEntries() {
|
|
51
|
+
const home = homeDir();
|
|
52
|
+
return uniqueEntries([
|
|
53
|
+
(0, node_path_1.join)(home, '.local', 'bin'),
|
|
54
|
+
(0, node_path_1.join)(home, '.npm-global', 'bin'),
|
|
55
|
+
(0, node_path_1.join)(home, '.cargo', 'bin'),
|
|
56
|
+
...(process.env.PATH ? process.env.PATH.split(':') : []),
|
|
57
|
+
'/usr/local/bin',
|
|
58
|
+
'/usr/bin',
|
|
59
|
+
'/bin'
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
async function resolveBinFromPathEntries(bin) {
|
|
63
|
+
for (const dir of hostPathEntries()) {
|
|
64
|
+
const candidate = (0, node_path_1.join)(dir, bin);
|
|
65
|
+
if (await fileExists(candidate))
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
async function collectNpmPrefixBinDirs() {
|
|
71
|
+
const dirs = [];
|
|
72
|
+
const home = homeDir();
|
|
73
|
+
for (const args of [
|
|
74
|
+
['config', 'get', 'prefix'],
|
|
75
|
+
['prefix', '-g']
|
|
76
|
+
]) {
|
|
77
|
+
try {
|
|
78
|
+
const out = await (0, exec_1.run)('npm', args, {
|
|
79
|
+
env: {
|
|
80
|
+
...process.env,
|
|
81
|
+
HOME: home,
|
|
82
|
+
PATH: hostPathEntries().join(':')
|
|
83
|
+
},
|
|
84
|
+
timeoutMs: 1500
|
|
85
|
+
});
|
|
86
|
+
const prefix = out.stdout.trim();
|
|
87
|
+
if (!prefix || prefix === 'undefined' || prefix === 'null')
|
|
88
|
+
continue;
|
|
89
|
+
const candidate = (0, node_path_1.join)(prefix, 'bin');
|
|
90
|
+
if (await fileExists(candidate)) {
|
|
91
|
+
dirs.push(candidate);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (await fileExists(prefix))
|
|
95
|
+
dirs.push(prefix);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return uniqueEntries(dirs);
|
|
102
|
+
}
|
|
103
|
+
async function resolveHostBinPath(bin) {
|
|
104
|
+
const home = homeDir();
|
|
105
|
+
const fallback = {
|
|
106
|
+
kitowall: [
|
|
107
|
+
(0, node_path_1.join)(home, '.local', 'bin', 'kitowall'),
|
|
108
|
+
(0, node_path_1.join)(home, '.npm-global', 'bin', 'kitowall'),
|
|
109
|
+
(0, node_path_1.join)(home, 'node_modules', '.bin', 'kitowall')
|
|
110
|
+
],
|
|
111
|
+
kitsune: [
|
|
112
|
+
(0, node_path_1.join)(home, '.cargo', 'bin', 'kitsune'),
|
|
113
|
+
(0, node_path_1.join)(home, '.local', 'bin', 'kitsune'),
|
|
114
|
+
(0, node_path_1.join)(home, '.local', 'share', 'kitsune', 'bin', 'kitsune')
|
|
115
|
+
],
|
|
116
|
+
'kitsune-rendercore': [
|
|
117
|
+
(0, node_path_1.join)(home, '.cargo', 'bin', 'kitsune-rendercore'),
|
|
118
|
+
(0, node_path_1.join)(home, '.local', 'bin', 'kitsune-rendercore')
|
|
119
|
+
]
|
|
120
|
+
}[bin] ?? [(0, node_path_1.join)(home, '.local', 'bin', bin)];
|
|
121
|
+
for (const candidate of fallback) {
|
|
122
|
+
if (await fileExists(candidate))
|
|
123
|
+
return candidate;
|
|
124
|
+
}
|
|
125
|
+
const byPath = await resolveBinFromPathEntries(bin);
|
|
126
|
+
if (byPath)
|
|
127
|
+
return byPath;
|
|
128
|
+
if (bin === 'kitowall') {
|
|
129
|
+
for (const dir of await collectNpmPrefixBinDirs()) {
|
|
130
|
+
const candidate = (0, node_path_1.join)(dir, bin);
|
|
131
|
+
if (await fileExists(candidate))
|
|
132
|
+
return candidate;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
function bootstrapHostPath() {
|
|
138
|
+
return (0, node_path_1.join)(ROOT_DIR, 'scripts', 'bootstrap-host.sh');
|
|
139
|
+
}
|
|
140
|
+
function bootstrapSystemPath() {
|
|
141
|
+
return (0, node_path_1.join)(ROOT_DIR, 'scripts', 'bootstrap-system.sh');
|
|
142
|
+
}
|
|
143
|
+
async function bootstrapVersionsPath() {
|
|
144
|
+
return (0, node_path_1.join)(homeDir(), '.local', 'share', 'kitowall', 'bootstrap-versions.json');
|
|
145
|
+
}
|
|
146
|
+
async function readBootstrapVersion(component) {
|
|
147
|
+
try {
|
|
148
|
+
const raw = await node_fs_1.promises.readFile(await bootstrapVersionsPath(), 'utf8');
|
|
149
|
+
const data = JSON.parse(raw);
|
|
150
|
+
const version = String(data?.[component]?.version ?? '').trim();
|
|
151
|
+
return version || null;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function readCliVersion(binPath) {
|
|
158
|
+
try {
|
|
159
|
+
const realPath = await node_fs_1.promises.realpath(binPath);
|
|
160
|
+
const pkgPath = (0, node_path_1.join)((0, node_path_1.dirname)(realPath), '..', 'package.json');
|
|
161
|
+
const raw = await node_fs_1.promises.readFile(pkgPath, 'utf8');
|
|
162
|
+
const parsed = JSON.parse(raw);
|
|
163
|
+
const value = normalizeReleaseVersion(parsed.version ?? '');
|
|
164
|
+
if (value)
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// fallback to executing the bin
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const out = await (0, exec_1.run)(binPath, ['--version'], {
|
|
172
|
+
env: {
|
|
173
|
+
...process.env,
|
|
174
|
+
HOME: homeDir(),
|
|
175
|
+
PATH: hostPathEntries().join(':')
|
|
176
|
+
},
|
|
177
|
+
timeoutMs: 1500
|
|
178
|
+
});
|
|
179
|
+
const value = normalizeReleaseVersion(out.stdout.trim());
|
|
180
|
+
return value || null;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function githubLatestReleaseTag(repo) {
|
|
187
|
+
const cached = RELEASE_CACHE.get(repo);
|
|
188
|
+
if (cached && Date.now() - cached.at < 300000)
|
|
189
|
+
return cached.value;
|
|
190
|
+
try {
|
|
191
|
+
const out = await (0, exec_1.run)('curl', ['-fsSL', '--connect-timeout', '1', '--max-time', '2', '-H', 'User-Agent: kitowall-cli', `https://api.github.com/repos/${repo}/releases/latest`], {
|
|
192
|
+
env: {
|
|
193
|
+
...process.env,
|
|
194
|
+
HOME: homeDir(),
|
|
195
|
+
PATH: hostPathEntries().join(':')
|
|
196
|
+
},
|
|
197
|
+
timeoutMs: 2500
|
|
198
|
+
});
|
|
199
|
+
const data = JSON.parse(out.stdout);
|
|
200
|
+
const value = normalizeReleaseVersion(data?.tag_name ?? '') || null;
|
|
201
|
+
RELEASE_CACHE.set(repo, { at: Date.now(), value });
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
RELEASE_CACHE.set(repo, { at: Date.now(), value: null });
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function componentUpdateInfo(id, binPath = '') {
|
|
210
|
+
const repo = {
|
|
211
|
+
kitowall: 'KitotsuMolina/Kitowall',
|
|
212
|
+
kitsune: 'KitotsuMolina/Kitsune',
|
|
213
|
+
'kitsune-rendercore': 'KitotsuMolina/Kitsune-RenderCore'
|
|
214
|
+
}[id];
|
|
215
|
+
let localVersion = null;
|
|
216
|
+
if (id === 'kitowall' && binPath) {
|
|
217
|
+
localVersion = await readCliVersion(binPath);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
localVersion = await readBootstrapVersion(id);
|
|
221
|
+
}
|
|
222
|
+
const latestVersion = repo ? await githubLatestReleaseTag(repo) : null;
|
|
223
|
+
return {
|
|
224
|
+
local_version: localVersion,
|
|
225
|
+
latest_version: latestVersion,
|
|
226
|
+
update_available: !!(localVersion && latestVersion && localVersion !== latestVersion)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async function maybeSystemctlShow(unit, props) {
|
|
230
|
+
try {
|
|
231
|
+
const args = ['--user', 'show', unit, '--no-pager'];
|
|
232
|
+
for (const prop of props)
|
|
233
|
+
args.push('-p', prop);
|
|
234
|
+
const out = await (0, exec_1.run)('systemctl', args, {
|
|
235
|
+
env: {
|
|
236
|
+
...process.env,
|
|
237
|
+
HOME: homeDir(),
|
|
238
|
+
PATH: hostPathEntries().join(':')
|
|
239
|
+
},
|
|
240
|
+
timeoutMs: 1500
|
|
241
|
+
});
|
|
242
|
+
const result = {};
|
|
243
|
+
for (const line of out.stdout.split('\n')) {
|
|
244
|
+
const idx = line.indexOf('=');
|
|
245
|
+
if (idx > -1)
|
|
246
|
+
result[line.slice(0, idx)] = line.slice(idx + 1);
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function checkSetupDependency(id) {
|
|
255
|
+
const def = exports.HOST_DEPENDENCY_DEFS.find(item => item.id === id);
|
|
256
|
+
if (!def)
|
|
257
|
+
throw new Error(`Unknown dependency id: ${id}`);
|
|
258
|
+
let binPath = '';
|
|
259
|
+
let error = '';
|
|
260
|
+
try {
|
|
261
|
+
binPath = await resolveHostBinPath(def.bin);
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
error = err instanceof Error ? err.message : String(err);
|
|
265
|
+
}
|
|
266
|
+
let update = { local_version: null, latest_version: null, update_available: false };
|
|
267
|
+
try {
|
|
268
|
+
if (def.id === 'kitowall' || def.id === 'kitsune' || def.id === 'kitsune-rendercore') {
|
|
269
|
+
update = await componentUpdateInfo(def.id, binPath);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
if (!error)
|
|
274
|
+
error = err instanceof Error ? err.message : String(err);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
id: def.id,
|
|
278
|
+
label: def.label,
|
|
279
|
+
installed: !!binPath,
|
|
280
|
+
state: binPath ? 'ok' : error ? 'error' : 'missing',
|
|
281
|
+
path: binPath,
|
|
282
|
+
detail: binPath ? `path=${binPath}` : 'binary not found',
|
|
283
|
+
error,
|
|
284
|
+
update
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function checkSetupService(id, namespace = 'kitowall') {
|
|
288
|
+
const def = exports.HOST_SERVICE_DEFS.find(item => item.id === id);
|
|
289
|
+
if (!def)
|
|
290
|
+
throw new Error(`Unknown service id: ${id}`);
|
|
291
|
+
const home = homeDir();
|
|
292
|
+
const configPath = (0, node_path_1.join)(home, '.config', 'kitowall', 'config.json');
|
|
293
|
+
const timerUnitPath = (0, node_path_1.join)(home, '.config', 'systemd', 'user', 'kitowall-next.timer');
|
|
294
|
+
const rendercoreUnitPath = (0, node_path_1.join)(home, '.config', 'systemd', 'user', 'kitsune-rendercore.service');
|
|
295
|
+
if (id === 'kitowall-config') {
|
|
296
|
+
const installed = await fileExists(configPath);
|
|
297
|
+
return {
|
|
298
|
+
id,
|
|
299
|
+
label: def.label,
|
|
300
|
+
installed,
|
|
301
|
+
state: installed ? 'ok' : 'missing',
|
|
302
|
+
path: configPath,
|
|
303
|
+
detail: installed ? `namespace=${namespace}` : 'config.json missing',
|
|
304
|
+
error: ''
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (id === 'kitowall-next.timer') {
|
|
308
|
+
const installed = await fileExists(timerUnitPath);
|
|
309
|
+
const status = installed ? await maybeSystemctlShow('kitowall-next.timer', ['ActiveState', 'UnitFileState']) : null;
|
|
310
|
+
return {
|
|
311
|
+
id,
|
|
312
|
+
label: def.label,
|
|
313
|
+
installed,
|
|
314
|
+
state: installed ? 'ok' : 'missing',
|
|
315
|
+
path: timerUnitPath,
|
|
316
|
+
detail: installed
|
|
317
|
+
? (status
|
|
318
|
+
? `file=${timerUnitPath} active=${String(status.ActiveState ?? 'n/a')} unit=${String(status.UnitFileState ?? 'n/a')}`
|
|
319
|
+
: `file=${timerUnitPath} status=unavailable`)
|
|
320
|
+
: 'timer unit file missing',
|
|
321
|
+
error: ''
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (id === 'kitsune-rendercore.service') {
|
|
325
|
+
const installed = await fileExists(rendercoreUnitPath);
|
|
326
|
+
const status = installed ? await maybeSystemctlShow('kitsune-rendercore.service', ['ActiveState', 'UnitFileState']) : null;
|
|
327
|
+
return {
|
|
328
|
+
id,
|
|
329
|
+
label: def.label,
|
|
330
|
+
installed,
|
|
331
|
+
state: installed ? 'ok' : 'missing',
|
|
332
|
+
path: rendercoreUnitPath,
|
|
333
|
+
detail: installed
|
|
334
|
+
? (status
|
|
335
|
+
? `file=${rendercoreUnitPath} active=${String(status.ActiveState ?? 'n/a')} unit=${String(status.UnitFileState ?? 'n/a')}`
|
|
336
|
+
: `file=${rendercoreUnitPath} status=unavailable`)
|
|
337
|
+
: 'service unit file missing',
|
|
338
|
+
error: ''
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
throw new Error(`Unknown service id: ${id}`);
|
|
342
|
+
}
|
|
343
|
+
async function listSetupStatus(namespace = 'kitowall') {
|
|
344
|
+
const [dependencies, services] = await Promise.all([
|
|
345
|
+
Promise.all(exports.HOST_DEPENDENCY_DEFS.map(item => checkSetupDependency(item.id))),
|
|
346
|
+
Promise.all(exports.HOST_SERVICE_DEFS.map(item => checkSetupService(item.id, namespace)))
|
|
347
|
+
]);
|
|
348
|
+
return {
|
|
349
|
+
ok: dependencies.every(item => item.installed) && services.every(item => item.installed),
|
|
350
|
+
dependencies,
|
|
351
|
+
services,
|
|
352
|
+
counts: {
|
|
353
|
+
dependencies_missing: dependencies.filter(item => !item.installed).length,
|
|
354
|
+
services_missing: services.filter(item => !item.installed).length,
|
|
355
|
+
updates_available: dependencies.filter(item => item.update?.update_available === true).length
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
async function listSetupVersions() {
|
|
360
|
+
const items = {};
|
|
361
|
+
for (const id of ['kitowall', 'kitsune', 'kitsune-rendercore']) {
|
|
362
|
+
const dep = await checkSetupDependency(id);
|
|
363
|
+
items[id] = dep.update ?? { local_version: null, latest_version: null, update_available: false };
|
|
364
|
+
}
|
|
365
|
+
return { ok: true, items };
|
|
366
|
+
}
|
|
367
|
+
async function runBootstrapHostMode(mode) {
|
|
368
|
+
const out = await (0, exec_1.run)('bash', [bootstrapHostPath()], {
|
|
369
|
+
env: {
|
|
370
|
+
...process.env,
|
|
371
|
+
HOME: homeDir(),
|
|
372
|
+
PATH: hostPathEntries().join(':'),
|
|
373
|
+
KITOWALL_SKIP_SYSTEM_DEPS: '1',
|
|
374
|
+
KITOWALL_BOOTSTRAP_MODE: mode
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
return { ok: true, code: out.code, logs: `${out.stdout}${out.stderr}` };
|
|
378
|
+
}
|
|
379
|
+
async function runBootstrapSystemItems(ids) {
|
|
380
|
+
const out = await (0, exec_1.run)('pkexec', ['bash', bootstrapSystemPath(), ...ids], {
|
|
381
|
+
env: {
|
|
382
|
+
...process.env,
|
|
383
|
+
HOME: homeDir(),
|
|
384
|
+
PATH: hostPathEntries().join(':')
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return { ok: true, code: out.code, logs: `${out.stdout}${out.stderr}` };
|
|
388
|
+
}
|
|
389
|
+
async function installSetupItem(id, namespace = 'kitowall') {
|
|
390
|
+
const dep = exports.HOST_DEPENDENCY_DEFS.find(item => item.id === id);
|
|
391
|
+
if (dep?.system)
|
|
392
|
+
return await runBootstrapSystemItems([id]);
|
|
393
|
+
if (id === 'kitowall')
|
|
394
|
+
return await runBootstrapHostMode('kitowall-only');
|
|
395
|
+
if (id === 'kitsune' || id === 'kitsune-rendercore' || id === 'kitsune-rendercore.service') {
|
|
396
|
+
return await runBootstrapHostMode('kitsune-only');
|
|
397
|
+
}
|
|
398
|
+
if (id === 'kitowall-config') {
|
|
399
|
+
await (0, init_1.initKitowall)({ namespace, apply: true, force: true });
|
|
400
|
+
return { ok: true, code: 0, logs: JSON.stringify({ ok: true, init: true, namespace }, null, 2) };
|
|
401
|
+
}
|
|
402
|
+
if (id === 'kitowall-next.timer') {
|
|
403
|
+
await (0, systemd_1.installSystemd)({ every: '600s' });
|
|
404
|
+
return { ok: true, code: 0, logs: JSON.stringify({ ok: true, installed: true, every: '600s' }, null, 2) };
|
|
405
|
+
}
|
|
406
|
+
throw new Error(`Unsupported setup item: ${id}`);
|
|
407
|
+
}
|