kitowall 3.5.16 → 3.5.36
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 +7 -0
- package/dist/core/live.js +214 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,13 @@ chmod +x ./Kitowall-*.AppImage
|
|
|
55
55
|
```bash
|
|
56
56
|
./scripts/bootstrap-host.sh
|
|
57
57
|
```
|
|
58
|
+
If `bootstrap-host.sh` fails on Arch with errors like `zstd: undefined symbol: POOL_free` or `bsdtar: Write error`, repair core compression/archive libs and retry:
|
|
59
|
+
```bash
|
|
60
|
+
sudo pacman -Syu
|
|
61
|
+
sudo pacman -S --overwrite '*' zstd libarchive
|
|
62
|
+
```
|
|
63
|
+
Reason: this fixes host library mismatches (partial/out-of-sync updates) that break AUR package builds and package compression.
|
|
64
|
+
|
|
58
65
|
4) Initialize services:
|
|
59
66
|
```bash
|
|
60
67
|
kitowall init --namespace kitowall --apply --force
|
package/dist/core/live.js
CHANGED
|
@@ -1042,6 +1042,158 @@ function syncRendercoreEnv(defaults) {
|
|
|
1042
1042
|
}
|
|
1043
1043
|
node_fs_1.default.writeFileSync(p, `${lines.join('\n')}\n`, 'utf8');
|
|
1044
1044
|
}
|
|
1045
|
+
function shSingleQuote(v) {
|
|
1046
|
+
return `'${String(v).replace(/'/g, `'\\''`)}'`;
|
|
1047
|
+
}
|
|
1048
|
+
async function hostCommandExists(cmd) {
|
|
1049
|
+
try {
|
|
1050
|
+
await (0, exec_1.run)('sh', ['-lc', `command -v ${cmd} >/dev/null 2>&1`], { timeoutMs: 5000 });
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function resolveHostExecutablePath(bin) {
|
|
1058
|
+
const raw = clean(bin);
|
|
1059
|
+
if (!raw)
|
|
1060
|
+
throw new Error('empty binary name');
|
|
1061
|
+
if (raw.includes('/')) {
|
|
1062
|
+
if (!node_fs_1.default.existsSync(raw))
|
|
1063
|
+
throw new Error(`binary not found: ${raw}`);
|
|
1064
|
+
return raw;
|
|
1065
|
+
}
|
|
1066
|
+
const out = await (0, exec_1.run)('sh', ['-lc', `command -v ${raw}`], { timeoutMs: 5000 });
|
|
1067
|
+
const resolved = clean(out.stdout.split('\n')[0]);
|
|
1068
|
+
if (!resolved)
|
|
1069
|
+
throw new Error(`binary not found in PATH: ${raw}`);
|
|
1070
|
+
return resolved;
|
|
1071
|
+
}
|
|
1072
|
+
async function installHostPackagesArch(packages) {
|
|
1073
|
+
if (packages.length === 0)
|
|
1074
|
+
return;
|
|
1075
|
+
const quoted = packages.map(shSingleQuote).join(' ');
|
|
1076
|
+
const cmd = `if command -v sudo >/dev/null 2>&1; then ` +
|
|
1077
|
+
`(sudo -n pacman -S --needed --noconfirm ${quoted} || sudo pacman -S --needed ${quoted}); ` +
|
|
1078
|
+
`else pacman -S --needed ${quoted}; fi`;
|
|
1079
|
+
await (0, exec_1.run)('sh', ['-lc', cmd], { timeoutMs: 240000 });
|
|
1080
|
+
}
|
|
1081
|
+
async function ensureRendercoreHostRuntimeDeps() {
|
|
1082
|
+
const missing = [];
|
|
1083
|
+
if (!(await hostCommandExists('ffmpeg')))
|
|
1084
|
+
missing.push('ffmpeg');
|
|
1085
|
+
if (!(await hostCommandExists('hyprctl')))
|
|
1086
|
+
missing.push('hyprctl');
|
|
1087
|
+
if (!(await hostCommandExists('systemctl')))
|
|
1088
|
+
missing.push('systemctl');
|
|
1089
|
+
if (missing.length === 0)
|
|
1090
|
+
return { installed: [], missing: [] };
|
|
1091
|
+
const canPacman = await hostCommandExists('pacman');
|
|
1092
|
+
const toInstall = new Set();
|
|
1093
|
+
if (missing.includes('ffmpeg'))
|
|
1094
|
+
toInstall.add('ffmpeg');
|
|
1095
|
+
if (missing.includes('hyprctl'))
|
|
1096
|
+
toInstall.add('hyprland');
|
|
1097
|
+
if (canPacman && toInstall.size > 0) {
|
|
1098
|
+
await installHostPackagesArch(Array.from(toInstall));
|
|
1099
|
+
}
|
|
1100
|
+
const afterMissing = [];
|
|
1101
|
+
if (!(await hostCommandExists('ffmpeg')))
|
|
1102
|
+
afterMissing.push('ffmpeg');
|
|
1103
|
+
if (!(await hostCommandExists('hyprctl')))
|
|
1104
|
+
afterMissing.push('hyprctl');
|
|
1105
|
+
if (!(await hostCommandExists('systemctl')))
|
|
1106
|
+
afterMissing.push('systemctl');
|
|
1107
|
+
return { installed: Array.from(toInstall), missing: afterMissing };
|
|
1108
|
+
}
|
|
1109
|
+
function rendercoreVideoMapPath() {
|
|
1110
|
+
return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'kitsune-rendercore', 'video-map.conf');
|
|
1111
|
+
}
|
|
1112
|
+
function rendercoreUserServicePath() {
|
|
1113
|
+
return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'systemd', 'user', 'kitsune-rendercore.service');
|
|
1114
|
+
}
|
|
1115
|
+
function readEnvPairs(p) {
|
|
1116
|
+
const out = {};
|
|
1117
|
+
if (!node_fs_1.default.existsSync(p))
|
|
1118
|
+
return out;
|
|
1119
|
+
const raw = node_fs_1.default.readFileSync(p, 'utf8');
|
|
1120
|
+
for (const line of raw.split('\n')) {
|
|
1121
|
+
const t = line.trim();
|
|
1122
|
+
if (!t || t.startsWith('#'))
|
|
1123
|
+
continue;
|
|
1124
|
+
const eq = t.indexOf('=');
|
|
1125
|
+
if (eq <= 0)
|
|
1126
|
+
continue;
|
|
1127
|
+
out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim();
|
|
1128
|
+
}
|
|
1129
|
+
return out;
|
|
1130
|
+
}
|
|
1131
|
+
function writeEnvPairs(p, pairs) {
|
|
1132
|
+
const keys = Object.keys(pairs).sort((a, b) => a.localeCompare(b));
|
|
1133
|
+
const lines = keys.map((k) => `${k}=${pairs[k]}`);
|
|
1134
|
+
node_fs_1.default.writeFileSync(p, `${lines.join('\n')}\n`, 'utf8');
|
|
1135
|
+
}
|
|
1136
|
+
function ensureRendercoreServiceFiles(bin, defaults) {
|
|
1137
|
+
const envPath = rendercoreEnvPath();
|
|
1138
|
+
const mapPath = rendercoreVideoMapPath();
|
|
1139
|
+
const servicePath = rendercoreUserServicePath();
|
|
1140
|
+
(0, fs_1.ensureDir)(node_path_1.default.dirname(envPath));
|
|
1141
|
+
(0, fs_1.ensureDir)(node_path_1.default.dirname(servicePath));
|
|
1142
|
+
if (!node_fs_1.default.existsSync(mapPath))
|
|
1143
|
+
node_fs_1.default.writeFileSync(mapPath, '', 'utf8');
|
|
1144
|
+
const envPairs = readEnvPairs(envPath);
|
|
1145
|
+
envPairs.KRC_VIDEO_MAP_FILE = mapPath;
|
|
1146
|
+
envPairs.KRC_VIDEO_FPS = String(defaults.video_fps);
|
|
1147
|
+
envPairs.KRC_VIDEO_SPEED = String(defaults.video_speed);
|
|
1148
|
+
envPairs.KRC_HWACCEL = defaults.hwaccel;
|
|
1149
|
+
envPairs.KRC_QUALITY = defaults.quality;
|
|
1150
|
+
envPairs.KRC_PAUSE_ON_STEAM_GAME = defaults.pause_on_steam_game ? 'true' : 'false';
|
|
1151
|
+
envPairs.KRC_STEAM_POLL_MS = String(defaults.steam_poll_ms);
|
|
1152
|
+
writeEnvPairs(envPath, envPairs);
|
|
1153
|
+
const home = node_os_1.default.homedir();
|
|
1154
|
+
const pathEnv = `${home}/.local/bin:${home}/.cargo/bin:/usr/local/bin:/usr/bin:/bin`;
|
|
1155
|
+
const serviceBody = [
|
|
1156
|
+
'[Unit]',
|
|
1157
|
+
'Description=Kitsune RenderCore Live Wallpaper',
|
|
1158
|
+
'After=graphical-session.target',
|
|
1159
|
+
'Wants=graphical-session.target',
|
|
1160
|
+
'',
|
|
1161
|
+
'[Service]',
|
|
1162
|
+
'Type=simple',
|
|
1163
|
+
`Environment=PATH=${pathEnv}`,
|
|
1164
|
+
`EnvironmentFile=-${envPath}`,
|
|
1165
|
+
`ExecStart=${bin}`,
|
|
1166
|
+
'Restart=on-failure',
|
|
1167
|
+
'RestartSec=1',
|
|
1168
|
+
'',
|
|
1169
|
+
'[Install]',
|
|
1170
|
+
'WantedBy=default.target',
|
|
1171
|
+
''
|
|
1172
|
+
].join('\n');
|
|
1173
|
+
node_fs_1.default.writeFileSync(servicePath, serviceBody, 'utf8');
|
|
1174
|
+
return { service_path: servicePath, env_path: envPath, map_path: mapPath };
|
|
1175
|
+
}
|
|
1176
|
+
function extractExecResult(err) {
|
|
1177
|
+
const result = err?.result;
|
|
1178
|
+
if (!result)
|
|
1179
|
+
return null;
|
|
1180
|
+
return {
|
|
1181
|
+
stdout: String(result.stdout ?? ''),
|
|
1182
|
+
stderr: String(result.stderr ?? ''),
|
|
1183
|
+
code: Number(result.code ?? 1)
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
async function runSystemctlUser(args, acceptNonZero = false) {
|
|
1187
|
+
try {
|
|
1188
|
+
return await (0, exec_1.run)('systemctl', ['--user', ...args], { timeoutMs: 30000 });
|
|
1189
|
+
}
|
|
1190
|
+
catch (err) {
|
|
1191
|
+
const partial = extractExecResult(err);
|
|
1192
|
+
if (acceptNonZero && partial)
|
|
1193
|
+
return partial;
|
|
1194
|
+
throw err;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1045
1197
|
async function resolvePost(provider, pageUrl) {
|
|
1046
1198
|
const html = await fetchHtml(pageUrl, pageUrl);
|
|
1047
1199
|
const parsed = liveParsePostFromHtml(provider, pageUrl, html);
|
|
@@ -1655,8 +1807,16 @@ async function liveApply(opts) {
|
|
|
1655
1807
|
integratedRendercoreStart(bin);
|
|
1656
1808
|
}
|
|
1657
1809
|
else {
|
|
1658
|
-
|
|
1659
|
-
|
|
1810
|
+
const deps = await ensureRendercoreHostRuntimeDeps();
|
|
1811
|
+
if (deps.missing.length > 0) {
|
|
1812
|
+
throw new Error(`Missing host dependencies: ${deps.missing.join(', ')}. ` +
|
|
1813
|
+
'Install on host (Arch): sudo pacman -S --needed ffmpeg hyprland');
|
|
1814
|
+
}
|
|
1815
|
+
const binPath = await resolveHostExecutablePath(bin);
|
|
1816
|
+
ensureRendercoreServiceFiles(binPath, index.apply_defaults);
|
|
1817
|
+
await runSystemctlUser(['daemon-reload']);
|
|
1818
|
+
await runSystemctlUser(['enable', 'kitsune-rendercore.service']);
|
|
1819
|
+
await runSystemctlUser(['start', 'kitsune-rendercore.service']);
|
|
1660
1820
|
}
|
|
1661
1821
|
withLiveLock(() => {
|
|
1662
1822
|
const current = readIndex();
|
|
@@ -1816,10 +1976,15 @@ async function liveDoctor(opts) {
|
|
|
1816
1976
|
if (!deps.runner_bin) {
|
|
1817
1977
|
fix.push(`Install ${runnerBin} and ensure it is available in PATH`);
|
|
1818
1978
|
}
|
|
1819
|
-
if (opts?.fix
|
|
1979
|
+
if (opts?.fix) {
|
|
1820
1980
|
try {
|
|
1821
|
-
|
|
1822
|
-
|
|
1981
|
+
const ensured = await ensureRendercoreHostRuntimeDeps();
|
|
1982
|
+
if (ensured.installed.length > 0) {
|
|
1983
|
+
fix.push(`Installed host packages: ${ensured.installed.join(', ')}`);
|
|
1984
|
+
}
|
|
1985
|
+
if (ensured.missing.length > 0) {
|
|
1986
|
+
fix.push(`Still missing host dependencies: ${ensured.missing.join(', ')}`);
|
|
1987
|
+
}
|
|
1823
1988
|
try {
|
|
1824
1989
|
await (0, exec_1.run)('ffmpeg', ['-version'], { timeoutMs: 4000 });
|
|
1825
1990
|
deps.ffmpeg = true;
|
|
@@ -1836,11 +2001,11 @@ async function liveDoctor(opts) {
|
|
|
1836
2001
|
}
|
|
1837
2002
|
}
|
|
1838
2003
|
catch (e) {
|
|
1839
|
-
fix.push(`Failed to
|
|
2004
|
+
fix.push(`Failed to install host dependencies: ${String(e)}`);
|
|
1840
2005
|
}
|
|
1841
2006
|
}
|
|
1842
2007
|
else if (!deps.ffmpeg || !deps.hyprctl) {
|
|
1843
|
-
fix.push(
|
|
2008
|
+
fix.push('Run `kitowall live doctor --fix` to install missing runtime dependencies');
|
|
1844
2009
|
}
|
|
1845
2010
|
return {
|
|
1846
2011
|
ok: Object.values(deps).every(Boolean) || (deps.hyprctl === false && Object.keys(deps).filter(k => k !== 'hyprctl').every(k => deps[k])),
|
|
@@ -1878,62 +2043,55 @@ async function liveServiceAutostart(action) {
|
|
|
1878
2043
|
};
|
|
1879
2044
|
}
|
|
1880
2045
|
let out;
|
|
1881
|
-
|
|
1882
|
-
const
|
|
1883
|
-
if (
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
stdout:
|
|
1887
|
-
stderr: String(result.stderr ?? ''),
|
|
1888
|
-
code: Number(result.code ?? 1)
|
|
1889
|
-
};
|
|
1890
|
-
};
|
|
1891
|
-
try {
|
|
1892
|
-
out = await (0, exec_1.run)(bin, ['service', action], { timeoutMs: 30000 });
|
|
1893
|
-
}
|
|
1894
|
-
catch (err) {
|
|
1895
|
-
const partial = extractResult(err);
|
|
1896
|
-
if (action === 'status' && partial) {
|
|
1897
|
-
out = partial;
|
|
1898
|
-
return {
|
|
1899
|
-
ok: true,
|
|
1900
|
-
action,
|
|
1901
|
-
runner: bin,
|
|
1902
|
-
stdout: out.stdout.trim(),
|
|
1903
|
-
stderr: out.stderr.trim(),
|
|
1904
|
-
code: out.code
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
// Legacy compatibility: if user still points to kitsune-livewallpaper,
|
|
1908
|
-
// map to its older subcommand to avoid hard failure.
|
|
1909
|
-
if (clean(bin) === 'kitsune-livewallpaper') {
|
|
1910
|
-
try {
|
|
1911
|
-
out = await (0, exec_1.run)(bin, ['service-autostart', action], { timeoutMs: 30000 });
|
|
1912
|
-
}
|
|
1913
|
-
catch (legacyErr) {
|
|
1914
|
-
const legacyPartial = extractResult(legacyErr);
|
|
1915
|
-
if (action === 'status' && legacyPartial) {
|
|
1916
|
-
out = legacyPartial;
|
|
1917
|
-
return {
|
|
1918
|
-
ok: true,
|
|
1919
|
-
action,
|
|
1920
|
-
runner: bin,
|
|
1921
|
-
stdout: out.stdout.trim(),
|
|
1922
|
-
stderr: out.stderr.trim(),
|
|
1923
|
-
code: out.code
|
|
1924
|
-
};
|
|
1925
|
-
}
|
|
1926
|
-
throw legacyErr;
|
|
1927
|
-
}
|
|
2046
|
+
if (action === 'install') {
|
|
2047
|
+
const deps = await ensureRendercoreHostRuntimeDeps();
|
|
2048
|
+
if (deps.missing.length > 0) {
|
|
2049
|
+
const message = `Missing host dependencies: ${deps.missing.join(', ')}. ` +
|
|
2050
|
+
'Install on host (Arch): sudo pacman -S --needed ffmpeg hyprland';
|
|
2051
|
+
out = { stdout: '', stderr: message, code: 1 };
|
|
1928
2052
|
}
|
|
1929
2053
|
else {
|
|
1930
|
-
|
|
2054
|
+
const binPath = await resolveHostExecutablePath(bin);
|
|
2055
|
+
const installed = ensureRendercoreServiceFiles(binPath, index.apply_defaults);
|
|
2056
|
+
await runSystemctlUser(['daemon-reload']);
|
|
2057
|
+
out = {
|
|
2058
|
+
stdout: `Installed user service at ${installed.service_path}`,
|
|
2059
|
+
stderr: '',
|
|
2060
|
+
code: 0
|
|
2061
|
+
};
|
|
1931
2062
|
}
|
|
1932
2063
|
}
|
|
2064
|
+
else if (action === 'status') {
|
|
2065
|
+
out = await runSystemctlUser(['status', '--no-pager', 'kitsune-rendercore.service'], true);
|
|
2066
|
+
}
|
|
2067
|
+
else if (action === 'enable') {
|
|
2068
|
+
const binPath = await resolveHostExecutablePath(bin);
|
|
2069
|
+
ensureRendercoreServiceFiles(binPath, index.apply_defaults);
|
|
2070
|
+
await runSystemctlUser(['daemon-reload']);
|
|
2071
|
+
out = await runSystemctlUser(['enable', 'kitsune-rendercore.service']);
|
|
2072
|
+
}
|
|
2073
|
+
else if (action === 'disable') {
|
|
2074
|
+
out = await runSystemctlUser(['disable', '--now', 'kitsune-rendercore.service'], true);
|
|
2075
|
+
}
|
|
2076
|
+
else if (action === 'start') {
|
|
2077
|
+
const binPath = await resolveHostExecutablePath(bin);
|
|
2078
|
+
ensureRendercoreServiceFiles(binPath, index.apply_defaults);
|
|
2079
|
+
await runSystemctlUser(['daemon-reload']);
|
|
2080
|
+
out = await runSystemctlUser(['start', 'kitsune-rendercore.service']);
|
|
2081
|
+
}
|
|
2082
|
+
else if (action === 'stop') {
|
|
2083
|
+
out = await runSystemctlUser(['stop', 'kitsune-rendercore.service'], true);
|
|
2084
|
+
}
|
|
2085
|
+
else {
|
|
2086
|
+
const binPath = await resolveHostExecutablePath(bin);
|
|
2087
|
+
ensureRendercoreServiceFiles(binPath, index.apply_defaults);
|
|
2088
|
+
await runSystemctlUser(['daemon-reload']);
|
|
2089
|
+
out = await runSystemctlUser(['restart', 'kitsune-rendercore.service']);
|
|
2090
|
+
}
|
|
1933
2091
|
return {
|
|
1934
2092
|
ok: true,
|
|
1935
2093
|
action,
|
|
1936
|
-
runner: bin
|
|
2094
|
+
runner: `${bin} (systemd-user)`,
|
|
1937
2095
|
stdout: out.stdout.trim(),
|
|
1938
2096
|
stderr: out.stderr.trim(),
|
|
1939
2097
|
code: out.code
|