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 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
- await (0, exec_1.run)(bin, ['service', 'install'], { timeoutMs: 20000 });
1659
- await (0, exec_1.run)(bin, ['service', 'enable'], { timeoutMs: 20000 });
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 && deps.runner_bin) {
1979
+ if (opts?.fix) {
1820
1980
  try {
1821
- await (0, exec_1.run)(runnerBin, ['install-deps'], { timeoutMs: 180000 });
1822
- fix.push(`Executed: ${runnerBin} install-deps`);
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 execute '${index.runner.bin_name} install-deps': ${String(e)}`);
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(`Run '${runnerBin} install-deps' to install missing runtime dependencies`);
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
- const extractResult = (err) => {
1882
- const result = err?.result;
1883
- if (!result)
1884
- return null;
1885
- return {
1886
- stdout: String(result.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
- throw err;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitowall",
3
- "version": "3.5.16",
3
+ "version": "3.5.36",
4
4
  "description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
5
5
  "repository": {
6
6
  "type": "git",