kitowall 7.7.0 → 7.9.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.
@@ -140,6 +140,11 @@ function bootstrapHostPath() {
140
140
  function bootstrapSystemPath() {
141
141
  return (0, node_path_1.join)(ROOT_DIR, 'scripts', 'bootstrap-system.sh');
142
142
  }
143
+ async function assertBootstrapScriptExists(targetPath, label) {
144
+ if (await fileExists(targetPath))
145
+ return;
146
+ throw new Error(`${label} not found at ${targetPath}. Reinstala el CLI con: npm i -g --prefix ~/.local kitowall@latest`);
147
+ }
143
148
  async function bootstrapVersionsPath() {
144
149
  return (0, node_path_1.join)(homeDir(), '.local', 'share', 'kitowall', 'bootstrap-versions.json');
145
150
  }
@@ -365,7 +370,9 @@ async function listSetupVersions() {
365
370
  return { ok: true, items };
366
371
  }
367
372
  async function runBootstrapHostMode(mode) {
368
- const out = await (0, exec_1.run)('bash', [bootstrapHostPath()], {
373
+ const scriptPath = bootstrapHostPath();
374
+ await assertBootstrapScriptExists(scriptPath, 'bootstrap-host.sh');
375
+ const out = await (0, exec_1.run)('bash', [scriptPath], {
369
376
  env: {
370
377
  ...process.env,
371
378
  HOME: homeDir(),
@@ -377,7 +384,9 @@ async function runBootstrapHostMode(mode) {
377
384
  return { ok: true, code: out.code, logs: `${out.stdout}${out.stderr}` };
378
385
  }
379
386
  async function runBootstrapSystemItems(ids) {
380
- const out = await (0, exec_1.run)('pkexec', ['bash', bootstrapSystemPath(), ...ids], {
387
+ const scriptPath = bootstrapSystemPath();
388
+ await assertBootstrapScriptExists(scriptPath, 'bootstrap-system.sh');
389
+ const out = await (0, exec_1.run)('pkexec', ['bash', scriptPath, ...ids], {
381
390
  env: {
382
391
  ...process.env,
383
392
  HOME: homeDir(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitowall",
3
- "version": "7.7.0",
3
+ "version": "7.9.0",
4
4
  "description": "CLI/daemon for Hyprland wallpapers using swww with pack-based rotation.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,6 +10,8 @@
10
10
  "type": "commonjs",
11
11
  "files": [
12
12
  "dist",
13
+ "scripts/bootstrap-host.sh",
14
+ "scripts/bootstrap-system.sh",
13
15
  "README.md",
14
16
  "LICENSE.md",
15
17
  "NOTICE.md",
@@ -0,0 +1,552 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Host bootstrap for AppImage/non-Flatpak installations.
5
+ # Installs system dependencies plus kitowall/kitsune/kitsune-rendercore binaries.
6
+
7
+ KITSUNE_REPO="${KITSUNE_REPO:-https://github.com/KitotsuMolina/Kitsune.git}"
8
+ KITSUNE_RENDERCORE_REPO="${KITSUNE_RENDERCORE_REPO:-https://github.com/KitotsuMolina/Kitsune-RenderCore.git}"
9
+ KITSUNE_TAG="${KITSUNE_TAG:-}"
10
+ KITSUNE_RENDERCORE_TAG="${KITSUNE_RENDERCORE_TAG:-}"
11
+ KITOWALL_BOOTSTRAP_MODE="${KITOWALL_BOOTSTRAP_MODE:-full}"
12
+ KITOWALL_SKIP_SYSTEM_DEPS="${KITOWALL_SKIP_SYSTEM_DEPS:-0}"
13
+
14
+ BOOTSTRAP_KITSUNE_VERSION=""
15
+ BOOTSTRAP_RENDERCORE_VERSION=""
16
+
17
+ need_cmd() {
18
+ command -v "$1" >/dev/null 2>&1
19
+ }
20
+
21
+ prepend_user_path() {
22
+ local home_dir="${HOME:?HOME is required}"
23
+ export PATH="$home_dir/.local/bin:$home_dir/.cargo/bin:$PATH"
24
+ }
25
+
26
+ run_sudo() {
27
+ if need_cmd sudo; then
28
+ sudo "$@"
29
+ else
30
+ "$@"
31
+ fi
32
+ }
33
+
34
+ wait_pacman_lock() {
35
+ local lock_file="/var/lib/pacman/db.lck"
36
+ local tries=0
37
+ local max_tries=40
38
+
39
+ while [[ -e "$lock_file" ]]; do
40
+ # If a package manager is still running, just wait.
41
+ if pgrep -x pacman >/dev/null 2>&1 || \
42
+ pgrep -x yay >/dev/null 2>&1 || \
43
+ pgrep -x paru >/dev/null 2>&1 || \
44
+ pgrep -x makepkg >/dev/null 2>&1; then
45
+ tries=$((tries + 1))
46
+ if ((tries > max_tries)); then
47
+ echo "[bootstrap] pacman lock is busy for too long: $lock_file" >&2
48
+ return 1
49
+ fi
50
+ echo "[bootstrap] waiting for pacman lock (${tries}/${max_tries})..."
51
+ sleep 3
52
+ continue
53
+ fi
54
+
55
+ # No package process alive: stale lock, safe to remove.
56
+ echo "[bootstrap] removing stale pacman lock: $lock_file"
57
+ run_sudo rm -f "$lock_file"
58
+ break
59
+ done
60
+ }
61
+
62
+ ensure_user_bin_dirs() {
63
+ local home_dir
64
+ home_dir="${HOME:?HOME is required}"
65
+ mkdir -p "$home_dir/.local/bin" "$home_dir/.cargo/bin"
66
+ prepend_user_path
67
+ }
68
+
69
+ install_arch_deps() {
70
+ local repo_pkgs=(
71
+ nodejs npm
72
+ hyprland
73
+ swww
74
+ cava
75
+ pkgconf
76
+ gtk4
77
+ gtk4-layer-shell
78
+ jq
79
+ git
80
+ base-devel
81
+ )
82
+ # Avoid rust/rustup conflicts on Arch.
83
+ if need_cmd rustup; then
84
+ repo_pkgs+=(rustup)
85
+ elif need_cmd rustc && need_cmd cargo; then
86
+ :
87
+ else
88
+ repo_pkgs+=(rustup)
89
+ fi
90
+
91
+ echo "[bootstrap] installing Arch repo packages: ${repo_pkgs[*]}"
92
+ wait_pacman_lock
93
+ run_sudo pacman -S --needed --noconfirm "${repo_pkgs[@]}"
94
+ }
95
+
96
+ install_ubuntu_deps() {
97
+ local pkgs=(
98
+ nodejs npm
99
+ rustc cargo
100
+ pkg-config
101
+ libgtk-4-dev
102
+ jq curl git
103
+ cava
104
+ )
105
+ echo "[bootstrap] installing Debian/Ubuntu packages: ${pkgs[*]}"
106
+ run_sudo apt-get update
107
+ run_sudo apt-get install -y "${pkgs[@]}"
108
+ }
109
+
110
+ install_system_deps() {
111
+ if need_cmd pacman; then
112
+ install_arch_deps
113
+ return
114
+ fi
115
+ if need_cmd apt-get; then
116
+ install_ubuntu_deps
117
+ return
118
+ fi
119
+ echo "[bootstrap] unsupported distro package manager. Install manually: nodejs npm rust cargo pkg-config gtk4 gtk4-layer-shell swww hyprland cava" >&2
120
+ exit 1
121
+ }
122
+
123
+ install_kitowall_cli() {
124
+ if ! need_cmd npm; then
125
+ echo "[bootstrap] npm is not available after dependency install" >&2
126
+ exit 1
127
+ fi
128
+ local home_dir
129
+ home_dir="${HOME:?HOME is required}"
130
+ echo "[bootstrap] installing/updating kitowall CLI in user prefix ($home_dir/.local)"
131
+ npm i -g --prefix "$home_dir/.local" kitowall
132
+ }
133
+
134
+ install_github_release_bin() {
135
+ local repo="$1" # owner/name
136
+ local asset_name="$2" # exact asset name
137
+ local out_bin="$3" # absolute output path
138
+
139
+ if ! need_cmd curl; then
140
+ echo "[bootstrap] missing curl for GitHub release install" >&2
141
+ return 1
142
+ fi
143
+
144
+ local asset_url="https://github.com/${repo}/releases/latest/download/${asset_name}"
145
+ local release_tag=""
146
+ local out_dir tmp_bin
147
+ out_dir="$(dirname "$out_bin")"
148
+ mkdir -p "$out_dir"
149
+ tmp_bin="$(mktemp "${out_dir}/.${asset_name}.XXXXXX.tmp")"
150
+
151
+ echo "[bootstrap] downloading ${repo} asset: ${asset_name}"
152
+ if ! curl -fL \
153
+ --retry 3 \
154
+ --retry-delay 2 \
155
+ -H "User-Agent: Kitowall-Bootstrap" \
156
+ -H "Accept: application/octet-stream" \
157
+ "$asset_url" -o "$tmp_bin"; then
158
+ rm -f "$tmp_bin"
159
+ release_tag="$(latest_release_tag "$repo" || true)"
160
+ if [[ -n "${release_tag:-}" ]]; then
161
+ asset_url="https://github.com/${repo}/releases/download/${release_tag}/${asset_name}"
162
+ echo "[bootstrap] retrying ${repo} asset with explicit tag: ${release_tag}"
163
+ tmp_bin="$(mktemp "${out_dir}/.${asset_name}.XXXXXX.tmp")"
164
+ if ! curl -fL \
165
+ --retry 3 \
166
+ --retry-delay 2 \
167
+ -H "User-Agent: Kitowall-Bootstrap" \
168
+ -H "Accept: application/octet-stream" \
169
+ "$asset_url" -o "$tmp_bin"; then
170
+ rm -f "$tmp_bin"
171
+ return 1
172
+ fi
173
+ else
174
+ return 1
175
+ fi
176
+ fi
177
+ chmod 755 "$tmp_bin"
178
+ mv -f "$tmp_bin" "$out_bin"
179
+ }
180
+
181
+ latest_release_tag() {
182
+ local repo="$1"
183
+
184
+ if ! need_cmd curl || ! need_cmd jq; then
185
+ return 1
186
+ fi
187
+
188
+ curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" | jq -r '.tag_name // empty'
189
+ }
190
+
191
+ write_kitsune_wrapper() {
192
+ local home_dir="${HOME:?HOME is required}"
193
+ local share_dir="$home_dir/.local/share/kitsune"
194
+ local config_dir="$home_dir/.config/kitsune"
195
+ local state_dir="$home_dir/.local/state/kitsune"
196
+
197
+ mkdir -p "$home_dir/.local/bin" "$config_dir" "$state_dir/run"
198
+ rm -f "$home_dir/.local/bin/kitsune"
199
+ cat > "$home_dir/.local/bin/kitsune" <<EOF
200
+ #!/usr/bin/env bash
201
+ set -euo pipefail
202
+ export KITSUNE_HOME="$share_dir"
203
+ export KITSUNE_BIN_DIR="$share_dir/bin"
204
+ export KITSUNE_CFG="$config_dir/base.conf"
205
+ export KITSUNE_CAVA_CFG="$config_dir/cava.conf"
206
+ export KITSUNE_RUN_PREFIX="$state_dir/run"
207
+ mkdir -p "\$KITSUNE_RUN_PREFIX"
208
+ exec "$share_dir/scripts/kitsune.sh" "\$@"
209
+ EOF
210
+ chmod 755 "$home_dir/.local/bin/kitsune"
211
+ }
212
+
213
+ is_kitsune_bundle_corrupted() {
214
+ local home_dir="${HOME:?HOME is required}"
215
+ local script_path="$home_dir/.local/share/kitsune/scripts/kitsune.sh"
216
+
217
+ [[ ! -f "$script_path" ]] && return 0
218
+ grep -Fq 'exec "/home/' "$script_path" && grep -Fq '/scripts/kitsune.sh" "$@"' "$script_path"
219
+ }
220
+
221
+ is_kitsune_binary_corrupted() {
222
+ local home_dir="${HOME:?HOME is required}"
223
+ local bin_path="$home_dir/.local/share/kitsune/bin/kitsune"
224
+
225
+ [[ ! -f "$bin_path" ]] && return 0
226
+ file "$bin_path" 2>/dev/null | grep -Fq 'ELF' || return 0
227
+ return 1
228
+ }
229
+
230
+ repair_kitsune_host_layout() {
231
+ local home_dir="${HOME:?HOME is required}"
232
+ local share_dir="$home_dir/.local/share/kitsune"
233
+ local config_dir="$home_dir/.config/kitsune"
234
+ local state_dir="$home_dir/.local/state/kitsune"
235
+
236
+ if [[ ! -d "$share_dir/scripts" || ! -d "$share_dir/bin" ]]; then
237
+ echo "[bootstrap] kitsune share layout missing in $share_dir; run full bootstrap first" >&2
238
+ return 1
239
+ fi
240
+
241
+ if is_kitsune_binary_corrupted; then
242
+ echo "[bootstrap] kitsune binary is corrupted; restoring binaries from latest release"
243
+ install_kitsune_bins
244
+ return 0
245
+ fi
246
+
247
+ if is_kitsune_bundle_corrupted; then
248
+ echo "[bootstrap] kitsune bundle is corrupted; restoring bundle from latest release"
249
+ install_kitsune_bundle
250
+ return 0
251
+ fi
252
+
253
+ mkdir -p "$config_dir" "$state_dir/run"
254
+ if [[ -f "$share_dir/config/base.conf" && ! -f "$config_dir/base.conf" ]]; then
255
+ cp "$share_dir/config/base.conf" "$config_dir/base.conf"
256
+ fi
257
+ if [[ -f "$share_dir/config/cava.conf" && ! -f "$config_dir/cava.conf" ]]; then
258
+ cp "$share_dir/config/cava.conf" "$config_dir/cava.conf"
259
+ fi
260
+
261
+ write_kitsune_wrapper
262
+ echo "[bootstrap] repaired kitsune host layout"
263
+ }
264
+
265
+ install_kitsune_bundle() {
266
+ local home_dir="${HOME:?HOME is required}"
267
+ local share_dir="$home_dir/.local/share/kitsune"
268
+ local config_dir="$home_dir/.config/kitsune"
269
+ local state_dir="$home_dir/.local/state/kitsune"
270
+ local tmp_dir
271
+ tmp_dir="$(mktemp -d)"
272
+
273
+ if ! need_cmd curl || ! need_cmd jq || ! need_cmd tar; then
274
+ echo "[bootstrap] missing curl/jq/tar for kitsune bundle install" >&2
275
+ rm -rf "$tmp_dir"
276
+ return 1
277
+ fi
278
+
279
+ local api_url="https://api.github.com/repos/KitotsuMolina/Kitsune/releases/latest"
280
+ local tar_url
281
+ tar_url="$(curl -fsSL "$api_url" | jq -r '.tarball_url // empty')"
282
+ if [[ -z "${tar_url:-}" || "$tar_url" == "null" ]]; then
283
+ echo "[bootstrap] kitsune release tarball URL not found" >&2
284
+ rm -rf "$tmp_dir"
285
+ return 1
286
+ fi
287
+
288
+ echo "[bootstrap] downloading kitsune bundle sources"
289
+ curl -fL --retry 3 --retry-delay 2 "$tar_url" -o "$tmp_dir/kitsune.tar.gz"
290
+ tar -xzf "$tmp_dir/kitsune.tar.gz" -C "$tmp_dir"
291
+ local src_dir
292
+ src_dir="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d | head -n1)"
293
+ if [[ -z "${src_dir:-}" || ! -d "$src_dir/scripts" || ! -d "$src_dir/config" ]]; then
294
+ echo "[bootstrap] invalid kitsune source layout in tarball" >&2
295
+ rm -rf "$tmp_dir"
296
+ return 1
297
+ fi
298
+
299
+ mkdir -p "$share_dir" "$config_dir" "$state_dir"
300
+ rm -rf "$share_dir/scripts" "$share_dir/config" "$share_dir/completions"
301
+ cp -a "$src_dir/scripts" "$share_dir/"
302
+ cp -a "$src_dir/config" "$share_dir/"
303
+ [[ -d "$src_dir/completions" ]] && cp -a "$src_dir/completions" "$share_dir/" || true
304
+ mkdir -p "$share_dir/bin"
305
+
306
+ if [[ ! -f "$config_dir/base.conf" ]]; then
307
+ cp "$share_dir/config/base.conf" "$config_dir/base.conf"
308
+ fi
309
+ if [[ ! -f "$config_dir/cava.conf" ]]; then
310
+ cp "$share_dir/config/cava.conf" "$config_dir/cava.conf"
311
+ fi
312
+
313
+ # Ensure scripts point to prebuilt binaries instead of local cargo build paths.
314
+ sed -i \
315
+ -e '/^echo "\[i\] Building Rust renderer\.\.\."$/d' \
316
+ -e '/^cargo build --release --locked --bins$/d' \
317
+ -e '/^cargo build --release --bins$/d' \
318
+ -e '/^echo "\[i\] Building GTK overlay frontend\.\.\."$/d' \
319
+ -e '/^cargo build --release --bin kitsune-overlay$/d' \
320
+ -e 's|\./target/release/kitsune-overlay|"${KITSUNE_BIN_DIR:-./bin}"/kitsune-overlay|g' \
321
+ -e 's|\./target/release/kitsune|"${KITSUNE_BIN_DIR:-./bin}"/kitsune|g' \
322
+ -e 's|"${KITSUNE_BIN_DIR:-./bin}"/kitsune --config|"${KITSUNE_BIN_DIR:-./bin}"/kitsune run --config|g' \
323
+ "$share_dir/scripts/start.sh" \
324
+ "$share_dir/scripts/kitsune.sh"
325
+ sed -i -e '/^cargo build --release$/d' "$share_dir/scripts/install.sh" || true
326
+
327
+ write_kitsune_wrapper
328
+
329
+ rm -rf "$tmp_dir"
330
+ }
331
+
332
+ cargo_install_git_bin() {
333
+ local repo="$1"
334
+ local bin="$2"
335
+ local extra_args="$3"
336
+ local tag="${4:-}"
337
+
338
+ local args=(install --git "$repo" --locked --force "$bin")
339
+ if [[ -n "$tag" ]]; then
340
+ args+=(--tag "$tag")
341
+ fi
342
+ if [[ -n "$extra_args" ]]; then
343
+ # shellcheck disable=SC2206
344
+ local split_extra=($extra_args)
345
+ args+=("${split_extra[@]}")
346
+ fi
347
+
348
+ echo "[bootstrap] cargo ${args[*]}"
349
+ cargo "${args[@]}"
350
+ }
351
+
352
+ install_kitsune_bins() {
353
+ local home_dir="${HOME:?HOME is required}"
354
+ local bin_dir="$home_dir/.local/bin"
355
+ local share_bin_dir="$home_dir/.local/share/kitsune/bin"
356
+ local release_ok=0
357
+ mkdir -p "$share_bin_dir"
358
+
359
+ # Prefer prebuilt binaries from GitHub Releases to avoid local toolchain/submodule issues.
360
+ if install_github_release_bin "KitotsuMolina/Kitsune" "kitsune-linux-x86_64" "$share_bin_dir/kitsune" && \
361
+ install_github_release_bin "KitotsuMolina/Kitsune" "kitsune-overlay-linux-x86_64" "$share_bin_dir/kitsune-overlay" && \
362
+ install_github_release_bin "KitotsuMolina/Kitsune" "kitsune-color-resolve-linux-x86_64" "$share_bin_dir/kitsune-color-resolve" && \
363
+ install_github_release_bin "KitotsuMolina/Kitsune-RenderCore" "kitsune-rendercore-linux-x86_64" "$bin_dir/kitsune-rendercore"; then
364
+ ln -sf "$share_bin_dir/kitsune-overlay" "$bin_dir/kitsune-overlay"
365
+ ln -sf "$share_bin_dir/kitsune-color-resolve" "$bin_dir/kitsune-color-resolve"
366
+ BOOTSTRAP_KITSUNE_VERSION="$(latest_release_tag "KitotsuMolina/Kitsune" | sed 's/^v//')"
367
+ BOOTSTRAP_RENDERCORE_VERSION="$(latest_release_tag "KitotsuMolina/Kitsune-RenderCore" | sed 's/^v//')"
368
+ release_ok=1
369
+ fi
370
+
371
+ if [[ "$release_ok" -eq 1 ]]; then
372
+ install_kitsune_bundle || echo "[bootstrap] warning: failed to install kitsune script bundle; falling back to raw binary command" >&2
373
+ return
374
+ fi
375
+
376
+ echo "[bootstrap] warning: release binary install failed, falling back to cargo install" >&2
377
+ if ! need_cmd cargo; then
378
+ echo "[bootstrap] cargo is not available after dependency install" >&2
379
+ exit 1
380
+ fi
381
+ cargo_install_git_bin "$KITSUNE_REPO" kitsune "" "$KITSUNE_TAG"
382
+ cargo_install_git_bin "$KITSUNE_RENDERCORE_REPO" kitsune-rendercore "--features wayland-layer" "$KITSUNE_RENDERCORE_TAG"
383
+ BOOTSTRAP_KITSUNE_VERSION="${KITSUNE_TAG#v}"
384
+ BOOTSTRAP_RENDERCORE_VERSION="${KITSUNE_RENDERCORE_TAG#v}"
385
+ }
386
+
387
+ ensure_rendercore_service() {
388
+ if ! need_cmd kitsune-rendercore; then
389
+ echo "[bootstrap] warning: kitsune-rendercore binary not found; skipping service install" >&2
390
+ return
391
+ fi
392
+
393
+ local home_dir="${HOME:?HOME is required}"
394
+ local user_systemd_dir="$home_dir/.config/systemd/user"
395
+ local app_config_dir="$home_dir/.config/kitsune-rendercore"
396
+ local env_dst="$app_config_dir/env"
397
+ local map_dst="$app_config_dir/video-map.conf"
398
+ local bin_path
399
+ bin_path="$(command -v kitsune-rendercore || true)"
400
+ if [[ -z "$bin_path" ]]; then
401
+ echo "[bootstrap] warning: kitsune-rendercore binary path not found; skipping service install" >&2
402
+ return
403
+ fi
404
+
405
+ mkdir -p "$user_systemd_dir" "$app_config_dir"
406
+ if [[ ! -f "$env_dst" ]]; then
407
+ cat > "$env_dst" <<EOF
408
+ # Kitsune RenderCore user env
409
+ KRC_VIDEO_MAP_FILE=$map_dst
410
+ KRC_VIDEO_FPS=30
411
+ KRC_VIDEO_SPEED=1.0
412
+ KRC_QUALITY=high
413
+ KRC_PAUSE_ON_STEAM_GAME=true
414
+ KRC_STEAM_POLL_MS=1000
415
+ EOF
416
+ fi
417
+ if [[ ! -f "$map_dst" ]]; then
418
+ : > "$map_dst"
419
+ fi
420
+
421
+ cat > "$user_systemd_dir/kitsune-rendercore.service" <<EOF
422
+ [Unit]
423
+ Description=Kitsune RenderCore Live Wallpaper
424
+ After=graphical-session.target
425
+ Wants=graphical-session.target
426
+
427
+ [Service]
428
+ Type=simple
429
+ Environment=PATH=$home_dir/.local/bin:$home_dir/.cargo/bin:/usr/local/bin:/usr/bin:/bin
430
+ EnvironmentFile=-$env_dst
431
+ ExecStart=$bin_path
432
+ Restart=on-failure
433
+ RestartSec=1
434
+
435
+ [Install]
436
+ WantedBy=default.target
437
+ EOF
438
+
439
+ systemctl --user daemon-reload || true
440
+ systemctl --user enable kitsune-rendercore.service || true
441
+ }
442
+
443
+ verify_bins() {
444
+ local required_bins=()
445
+ if [[ "$KITOWALL_BOOTSTRAP_MODE" == "kitsune-only" || "$KITOWALL_BOOTSTRAP_MODE" == "kitsune-repair" ]]; then
446
+ required_bins=(kitsune kitsune-rendercore)
447
+ elif [[ "$KITOWALL_BOOTSTRAP_MODE" == "kitowall-only" ]]; then
448
+ required_bins=(kitowall)
449
+ else
450
+ required_bins=(kitowall kitsune kitsune-rendercore swww swww-daemon cava)
451
+ fi
452
+ local missing=()
453
+ for b in "${required_bins[@]}"; do
454
+ if ! need_cmd "$b"; then
455
+ missing+=("$b")
456
+ fi
457
+ done
458
+
459
+ if ((${#missing[@]} > 0)); then
460
+ echo "[bootstrap] missing binaries after bootstrap: ${missing[*]}" >&2
461
+
462
+ local needs_kitsune_recovery=0
463
+ for b in "${missing[@]}"; do
464
+ if [[ "$b" == "kitsune" || "$b" == "kitsune-rendercore" ]]; then
465
+ needs_kitsune_recovery=1
466
+ break
467
+ fi
468
+ done
469
+
470
+ if [[ "$needs_kitsune_recovery" -eq 1 ]]; then
471
+ echo "[bootstrap] attempting release-based recovery for Kitsune binaries" >&2
472
+ install_kitsune_bins
473
+ missing=()
474
+ for b in "${required_bins[@]}"; do
475
+ if ! need_cmd "$b"; then
476
+ missing+=("$b")
477
+ fi
478
+ done
479
+ fi
480
+
481
+ if ((${#missing[@]} > 0)); then
482
+ echo "[bootstrap] missing binaries after recovery: ${missing[*]}" >&2
483
+ exit 2
484
+ fi
485
+ fi
486
+ }
487
+
488
+ write_bootstrap_versions() {
489
+ local home_dir="${HOME:?HOME is required}"
490
+ local state_dir="$home_dir/.local/share/kitowall"
491
+ local state_file="$state_dir/bootstrap-versions.json"
492
+
493
+ mkdir -p "$state_dir"
494
+ cat > "$state_file" <<EOF
495
+ {
496
+ "kitsune": {
497
+ "version": "${BOOTSTRAP_KITSUNE_VERSION}",
498
+ "source": "${KITOWALL_BOOTSTRAP_MODE}"
499
+ },
500
+ "kitsune-rendercore": {
501
+ "version": "${BOOTSTRAP_RENDERCORE_VERSION}",
502
+ "source": "${KITOWALL_BOOTSTRAP_MODE}"
503
+ }
504
+ }
505
+ EOF
506
+ }
507
+
508
+ main() {
509
+ # Some launchers provide a stale/non-existent CWD; recover to HOME.
510
+ cd "${HOME:?HOME is required}" || true
511
+ # Clean host Python env that can break AUR build tools (meson/python).
512
+ unset PYTHONHOME || true
513
+ unset PYTHONPATH || true
514
+
515
+ ensure_user_bin_dirs
516
+ if [[ "$KITOWALL_BOOTSTRAP_MODE" == "kitsune-repair" ]]; then
517
+ repair_kitsune_host_layout
518
+ verify_bins
519
+ echo "[ok] kitsune host repair complete"
520
+ exit 0
521
+ fi
522
+ if [[ "$KITOWALL_BOOTSTRAP_MODE" == "kitowall-only" ]]; then
523
+ if [[ "$KITOWALL_SKIP_SYSTEM_DEPS" != "1" ]]; then
524
+ install_system_deps
525
+ fi
526
+ install_kitowall_cli
527
+ verify_bins
528
+ echo "[ok] kitowall bootstrap complete"
529
+ echo "[paths] HOME=$HOME"
530
+ echo "[paths] kitowall=$(command -v kitowall || echo '<missing>')"
531
+ echo "[info] bootstrap finished; manual init is only needed on a fresh host without config"
532
+ exit 0
533
+ fi
534
+ if [[ "$KITOWALL_BOOTSTRAP_MODE" != "kitsune-only" && "$KITOWALL_SKIP_SYSTEM_DEPS" != "1" ]]; then
535
+ install_system_deps
536
+ install_kitowall_cli
537
+ elif [[ "$KITOWALL_BOOTSTRAP_MODE" != "kitsune-only" ]]; then
538
+ install_kitowall_cli
539
+ fi
540
+ install_kitsune_bins
541
+ ensure_rendercore_service
542
+ write_bootstrap_versions
543
+ verify_bins
544
+ echo "[ok] host bootstrap complete"
545
+ echo "[paths] HOME=$HOME"
546
+ echo "[paths] kitowall=$(command -v kitowall || echo '<missing>')"
547
+ echo "[paths] kitsune=$(command -v kitsune || echo '<missing>')"
548
+ echo "[paths] kitsune-rendercore=$(command -v kitsune-rendercore || echo '<missing>')"
549
+ echo "[info] bootstrap finished; manual init is only needed on a fresh host without config"
550
+ }
551
+
552
+ main "$@"
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Root-only helper for installing host system packages.
5
+ # Intended to be invoked via pkexec from Kitowall.
6
+
7
+ need_cmd() {
8
+ command -v "$1" >/dev/null 2>&1
9
+ }
10
+
11
+ require_root() {
12
+ if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
13
+ echo "[bootstrap-system] this helper must run as root" >&2
14
+ exit 1
15
+ fi
16
+ }
17
+
18
+ wait_pacman_lock() {
19
+ local lock_file="/var/lib/pacman/db.lck"
20
+ local tries=0
21
+ local max_tries=40
22
+
23
+ while [[ -e "$lock_file" ]]; do
24
+ if pgrep -x pacman >/dev/null 2>&1 || \
25
+ pgrep -x yay >/dev/null 2>&1 || \
26
+ pgrep -x paru >/dev/null 2>&1 || \
27
+ pgrep -x makepkg >/dev/null 2>&1; then
28
+ tries=$((tries + 1))
29
+ if ((tries > max_tries)); then
30
+ echo "[bootstrap-system] pacman lock busy for too long: $lock_file" >&2
31
+ return 1
32
+ fi
33
+ echo "[bootstrap-system] waiting for pacman lock (${tries}/${max_tries})..."
34
+ sleep 3
35
+ continue
36
+ fi
37
+
38
+ echo "[bootstrap-system] removing stale pacman lock: $lock_file"
39
+ rm -f "$lock_file"
40
+ break
41
+ done
42
+ }
43
+
44
+ install_arch_deps() {
45
+ local repo_pkgs=(
46
+ nodejs npm
47
+ hyprland
48
+ swww
49
+ cava
50
+ pkgconf
51
+ gtk4
52
+ gtk4-layer-shell
53
+ jq
54
+ git
55
+ base-devel
56
+ )
57
+
58
+ if need_cmd rustup; then
59
+ repo_pkgs+=(rustup)
60
+ elif need_cmd rustc && need_cmd cargo; then
61
+ :
62
+ else
63
+ repo_pkgs+=(rustup)
64
+ fi
65
+
66
+ echo "[bootstrap-system] installing Arch packages: ${repo_pkgs[*]}"
67
+ wait_pacman_lock
68
+ pacman -S --needed --noconfirm "${repo_pkgs[@]}"
69
+ }
70
+
71
+ arch_packages_for_ids() {
72
+ local id
73
+ local pkgs=()
74
+ for id in "$@"; do
75
+ case "$id" in
76
+ nodejs) pkgs+=(nodejs) ;;
77
+ npm) pkgs+=(npm) ;;
78
+ hyprctl) pkgs+=(hyprland) ;;
79
+ swww|swww-daemon) pkgs+=(swww) ;;
80
+ cava) pkgs+=(cava) ;;
81
+ pkgconf) pkgs+=(pkgconf) ;;
82
+ gtk4) pkgs+=(gtk4) ;;
83
+ gtk4-layer-shell) pkgs+=(gtk4-layer-shell) ;;
84
+ jq) pkgs+=(jq) ;;
85
+ git) pkgs+=(git) ;;
86
+ base-devel) pkgs+=(base-devel) ;;
87
+ rustup|cargo|rustc) pkgs+=(rustup) ;;
88
+ esac
89
+ done
90
+ printf '%s\n' "${pkgs[@]}" | awk 'NF && !seen[$0]++'
91
+ }
92
+
93
+ install_arch_selected() {
94
+ local requested=("$@")
95
+ local repo_pkgs=()
96
+ mapfile -t repo_pkgs < <(arch_packages_for_ids "${requested[@]}")
97
+ if ((${#repo_pkgs[@]} == 0)); then
98
+ echo "[bootstrap-system] no supported Arch packages mapped from: ${requested[*]}" >&2
99
+ return 1
100
+ fi
101
+ echo "[bootstrap-system] installing selected Arch packages: ${repo_pkgs[*]}"
102
+ wait_pacman_lock
103
+ pacman -S --needed --noconfirm "${repo_pkgs[@]}"
104
+ }
105
+
106
+ install_ubuntu_deps() {
107
+ local pkgs=(
108
+ nodejs npm
109
+ rustc cargo
110
+ pkg-config
111
+ libgtk-4-dev
112
+ jq curl git
113
+ cava
114
+ )
115
+ echo "[bootstrap-system] installing Debian/Ubuntu packages: ${pkgs[*]}"
116
+ apt-get update
117
+ apt-get install -y "${pkgs[@]}"
118
+ }
119
+
120
+ ubuntu_packages_for_ids() {
121
+ local id
122
+ local pkgs=()
123
+ for id in "$@"; do
124
+ case "$id" in
125
+ nodejs) pkgs+=(nodejs) ;;
126
+ npm) pkgs+=(npm) ;;
127
+ cava) pkgs+=(cava) ;;
128
+ jq) pkgs+=(jq) ;;
129
+ git) pkgs+=(git) ;;
130
+ rustup|cargo|rustc) pkgs+=(rustc cargo) ;;
131
+ pkgconf) pkgs+=(pkg-config) ;;
132
+ gtk4) pkgs+=(libgtk-4-dev) ;;
133
+ esac
134
+ done
135
+ printf '%s\n' "${pkgs[@]}" | awk 'NF && !seen[$0]++'
136
+ }
137
+
138
+ install_ubuntu_selected() {
139
+ local requested=("$@")
140
+ local pkgs=()
141
+ mapfile -t pkgs < <(ubuntu_packages_for_ids "${requested[@]}")
142
+ if ((${#pkgs[@]} == 0)); then
143
+ echo "[bootstrap-system] no supported Debian/Ubuntu packages mapped from: ${requested[*]}" >&2
144
+ return 1
145
+ fi
146
+ echo "[bootstrap-system] installing selected Debian/Ubuntu packages: ${pkgs[*]}"
147
+ apt-get update
148
+ apt-get install -y "${pkgs[@]}"
149
+ }
150
+
151
+ main() {
152
+ require_root
153
+
154
+ if (($# > 0)); then
155
+ if need_cmd pacman; then
156
+ install_arch_selected "$@"
157
+ echo "[ok] selected system dependencies installed"
158
+ exit 0
159
+ fi
160
+
161
+ if need_cmd apt-get; then
162
+ install_ubuntu_selected "$@"
163
+ echo "[ok] selected system dependencies installed"
164
+ exit 0
165
+ fi
166
+
167
+ echo "[bootstrap-system] unsupported distro package manager for selected install: $*" >&2
168
+ exit 1
169
+ fi
170
+
171
+ if need_cmd pacman; then
172
+ install_arch_deps
173
+ echo "[ok] system dependencies installed"
174
+ exit 0
175
+ fi
176
+
177
+ if need_cmd apt-get; then
178
+ install_ubuntu_deps
179
+ echo "[ok] system dependencies installed"
180
+ exit 0
181
+ fi
182
+
183
+ echo "[bootstrap-system] unsupported distro package manager. Install manually: nodejs npm rust cargo pkg-config gtk4 gtk4-layer-shell swww hyprland cava" >&2
184
+ exit 1
185
+ }
186
+
187
+ main "$@"