karnel-termux 4.18.0 → 4.18.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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <p align="center">
10
10
  <a href="https://github.com/karnel-termuxOFC/karnel-termux">
11
- <img src="https://img.shields.io/badge/version-4.18.0-0078D4?style=for-the-badge" alt="Version">
11
+ <img src="https://img.shields.io/badge/version-4.18.2-0078D4?style=for-the-badge" alt="Version">
12
12
  </a>
13
13
  <a href="https://www.npmjs.com/package/karnel-termux">
14
14
  <img src="https://img.shields.io/npm/v/karnel-termux?style=for-the-badge&logo=npm&color=cb3837" alt="npm">
@@ -78,7 +78,7 @@ Why it's great:
78
78
  ### Via checksummed GitHub release (recommended)
79
79
 
80
80
  ```bash
81
- version=4.18.0
81
+ version=4.18.2
82
82
  tmpdir=$(mktemp -d) && trap 'rm -rf "$tmpdir"' EXIT
83
83
  base="https://github.com/karnel-termuxOFC/karnel-termux/releases/download/v$version"
84
84
  curl -fsSL "$base/karnel-termux-install.sh" -o "$tmpdir/karnel-termux-install.sh"
package/docs/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ layout: base
6
6
 
7
7
  # Documentation Changelog
8
8
 
9
+ ## 4.18.2
10
+
11
+ - **Wrappers no longer hand a preload to glibc.** `login(1)` exports `LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so`, a bionic object; glibc's `ld.so` cannot load it, so every wrapped tool died before `main()` with `.../glibc/lib/libc.so: invalid ELF header`. Tier 3's own preload was fatal in the other direction: exporting the glibc `libtermux-exec.so` shim left every bionic child of the tool failing with `library "libc.so.6" not found`. Every wrapper now starts with `unset LD_PRELOAD`, and tier 3 exports `PATH` only.
12
+ - **Wrappers written by older Karnels are refreshed.** `compat_wrapper_is_current()` checks the invariants the current code emits; when one is missing the wrapper is rewritten in place before `compat_escalate` probes it, so a stale launcher can no longer keep a tool pinned to a tier it no longer reaches.
13
+ - **An orphaned parked copy no longer blocks adaptation.** Freebuff's own entry re-extracts its runtime in place, dropping a raw ELF over the wrapper and leaving `.karnel-real` behind; `compat_wrap` refused to touch that file, so the tool could never be adapted again — which is how `karnel install ai --freebuff` ended in "does not start at any tier" and then "Freebuff runtime still does not run after repair". Identical bytes are dropped (a 130 MB runtime is not copied to prove a point) and a different copy is parked as `.karnel-real.stale`.
14
+ - **Tests.** Four new cases pin the behaviour — no wrapper hands a preload to the loader on tier 2 or tier 3, a launcher from an older karnel is refreshed in place, an orphaned parked copy does not block adaptation — in a suite of 23 cases running in 8 seconds. Every branch was proven to fail first: removing the `unset` fails 2 cases, restoring the tier 3 preload fails 5, dropping the refresh fails 1, restoring the refusal fails 1, and always discarding the orphan fails 1.
15
+
16
+ ## 4.18.1
17
+
18
+ - **The ladder is reachable from every installer, not just Freebuff:** `compat_adapt_installed()` is the single entry point behind `_tool_compat_adapt` (utils/tools.sh), `_ai_tool_compat_adapt` (tools/ai/all.sh) and the post-install loop in `cli/commands/install.sh`, so it already covered all 16 categories — but it only ever called `compat_wrap()`, which stops at the loader tier. Tiers 3 (glibc userland) and 4 (proot FHS root) were reachable solely from Freebuff's `compat_adapt()` call, which is why no other tool could ever climb above tier 2. It now escalates after wrapping, and re-probes wrappers already on disk so a tool that stops starting at its current tier climbs on the next install or update instead of staying broken behind a launcher nobody re-examines.
19
+ - **No cost for what already runs:** native binaries, scripts and plain files still return before any probe, so the added `--version` probe applies only to tools that actually needed a wrapper — the hundreds of native entries in a Termux prefix are untouched.
20
+ - **Tests:** three new cases pin the behaviour (ladder reachable from the installer path, existing wrappers re-evaluated, natives never probed — the last with a positive control so its zero cannot pass vacuously). Each was proven to fail first: dropping the escalation fails 3 cases, probing natives fails the efficiency case alone, removing tier 3's `PATH` export fails the 3 ladder cases, and letting tier 4 write a wrapper without a root fails the refusal case alone. The suite runs 20 cases in 7 seconds.
21
+
9
22
  ## 4.18.0
10
23
 
11
24
  - **Three-tier compatibility layer:** a foreign glibc binary is no longer handled by one fixed trick. `compat_adapt` wraps, probes and climbs to the cheapest tier that actually starts the tool — the glibc loader alone (free, installs nothing), the glibc userland (`$PREFIX/glibc/bin`, 383 glibc tools, prepended to `PATH` together with the glibc exec shim so child processes resolve the same way), or proot over a synthetic `/lib`, `/usr` and `/etc` assembled from the sysroot already on the device. That last tier replaces `proot-distro install ubuntu` for binaries that merely hardcode an absolute path, and it downloads nothing. A tier that cannot be reached is refused before the wrapper is written, so `proot -r ''` can never ship, and every step unwraps back to `<path>.karnel-real`.
@@ -1 +1 @@
1
- a6c1aeb11507e48d4e14c3f6c2eb9bd9e9f969da
1
+ fff0d351f344943ef88e0745cc0f7f56c7aaf970
@@ -206,6 +206,19 @@ compat_wrapper_tier() {
206
206
  esac
207
207
  }
208
208
 
209
+ # A wrapper written by an older karnel is still a wrapper, but it may hand
210
+ # LD_PRELOAD straight to the glibc loader and die in a login shell. Returns 0
211
+ # only when the launcher carries every invariant the current code emits.
212
+ compat_wrapper_is_current() {
213
+ local file="$1"
214
+ _compat_is_wrapper "$file" || return 1
215
+ grep -qF 'unset LD_PRELOAD' -- "$file" 2>/dev/null || return 1
216
+ if grep -qF 'export LD_PRELOAD' -- "$file" 2>/dev/null; then
217
+ return 1
218
+ fi
219
+ return 0
220
+ }
221
+
209
222
  # Builds (once) a synthetic filesystem root out of the glibc sysroot that is
210
223
  # already on the device. proot then maps /lib, /usr and /etc onto it, which is
211
224
  # everything a binary that hardcodes those paths needs -- for a few kilobytes
@@ -273,24 +286,23 @@ _compat_write_wrapper() {
273
286
  printf '%s\n' "#!$bash_bin"
274
287
  printf '%s\n' "$COMPAT_WRAPPER_MARKER"
275
288
  printf '%s\n' "$COMPAT_WRAPPER_TIER_MARKER $tier"
289
+ # login(1) exports LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so,
290
+ # a bionic object. glibc's ld.so cannot load it and dies with
291
+ # "libc.so: invalid ELF header" on every start. The glibc termux-exec
292
+ # shim is equally unusable: every bionic child this tool spawns then
293
+ # fails with "library libc.so.6 not found". So no preload survives.
294
+ printf '%s\n' "unset LD_PRELOAD"
276
295
  case "$tier" in
277
296
  "$COMPAT_TIER_USERLAND")
278
- # The $root/$PATH/$LD_PRELOAD below must reach the wrapper verbatim.
297
+ # The $root/$PATH below must reach the wrapper verbatim.
279
298
  # shellcheck disable=SC2016
280
299
  printf '%s\n' "# Tier 3: the glibc loader plus the glibc userland, so the child"
281
- printf '%s\n' "# processes this tool spawns find the same coreutils and exec shim."
300
+ printf '%s\n' "# processes this tool spawns find the same coreutils."
282
301
  # Everything below is emitted literally and expanded by the wrapper at
283
302
  # runtime, where "root" is the assignment written on the previous line.
284
303
  printf 'root=%q\n' "$(compat_glibc_root)"
285
304
  # shellcheck disable=SC2016 # expands in the wrapper
286
305
  printf 'export PATH="$root/bin:$PATH"\n'
287
- # The glibc termux-exec shim is optional; a preload pointing nowhere
288
- # makes ld.so complain on every start of the tool.
289
- # shellcheck disable=SC2016 # expands in the wrapper
290
- printf 'if [ -f "$root/lib/libtermux-exec.so" ]; then\n'
291
- # shellcheck disable=SC2016 # expands in the wrapper
292
- printf ' export LD_PRELOAD="$root/lib/libtermux-exec.so${LD_PRELOAD:+:$LD_PRELOAD}"\n'
293
- printf 'fi\n'
294
306
  printf 'exec %q --library-path %q %q "$@"\n' "$loader" "$libpath" "$real"
295
307
  ;;
296
308
  "$COMPAT_TIER_PROOT")
@@ -328,22 +340,43 @@ compat_wrap() {
328
340
  [[ -f "$real" ]] || return 1
329
341
  if _compat_is_wrapper "$real"; then
330
342
  current="$(compat_wrapper_tier "$real")"
331
- [[ "$current" == "$tier" ]] && return 0
332
- # Re-tier an existing wrapper: the original is already parked next to it,
333
- # so only the launcher changes.
343
+ if [[ "$current" == "$tier" ]] && compat_wrapper_is_current "$real"; then
344
+ return 0
345
+ fi
346
+ # Re-tier an existing wrapper, or refresh one written by an older karnel:
347
+ # the original is already parked next to it, so only the launcher changes.
334
348
  stored="$real.karnel-real"
335
349
  [[ -e "$stored" ]] || return 1
336
350
  loader="$(compat_glibc_loader)" || loader="/lib/ld-linux-aarch64.so.1"
337
351
  _compat_write_wrapper "$real" "$stored" "$loader" "$tier" || return 1
338
- log_info "compat: $real retiered to level $tier"
352
+ if [[ "$current" == "$tier" ]]; then
353
+ log_info "compat: $real wrapper refreshed"
354
+ else
355
+ log_info "compat: $real retiered to level $tier"
356
+ fi
339
357
  return 0
340
358
  fi
341
359
  compat_is_glibc_elf "$real" || return 1
342
360
  loader="$(compat_glibc_loader)" || return 1
343
361
  stored="$real.karnel-real"
344
362
  if [[ -e "$stored" ]]; then
345
- log_error "compat: refusing to overwrite existing $stored"
346
- return 1
363
+ # The file on disk is a real ELF, not our wrapper, while a parked copy is
364
+ # already sitting beside it: something outside karnel replaced the
365
+ # launcher (freebuff's node entry re-extracts its runtime in place) and
366
+ # orphaned the copy. The file on disk is authoritative now, so the parked
367
+ # copy is stale. A refusal here would leave the tool permanently
368
+ # unrunnable, so the copy is dropped when it is the same bytes and kept
369
+ # as .karnel-real.stale otherwise - a 130 MB runtime must not be copied
370
+ # just to prove a point.
371
+ if cmp -s -- "$stored" "$real" 2>/dev/null; then
372
+ rm -f -- "$stored" || return 1
373
+ log_warn "compat: dropped a stale parked copy for $real"
374
+ elif ! mv -f -- "$stored" "$stored.stale" 2>/dev/null; then
375
+ log_error "compat: could not move the stale parked copy for $real"
376
+ return 1
377
+ else
378
+ log_warn "compat: replaced a stale parked copy for $real"
379
+ fi
347
380
  fi
348
381
  if ! mv -- "$real" "$stored"; then
349
382
  log_error "compat: could not move $real aside"
@@ -392,6 +425,11 @@ compat_escalate() {
392
425
  real="$(readlink -f -- "$target" 2>/dev/null || printf '%s' "$target")"
393
426
  _compat_is_wrapper "$real" || return 0
394
427
 
428
+ # Refresh a launcher written by an older karnel before probing it: the probe
429
+ # has to exercise the wrapper we are about to ship, not the stale one.
430
+ compat_wrapper_is_current "$real" ||
431
+ compat_wrap "$real" "$(compat_wrapper_tier "$real")" || true
432
+
395
433
  compat_probe "$real" && return 0
396
434
 
397
435
  if compat_wrap "$real" "$COMPAT_TIER_USERLAND" && compat_probe "$real"; then
@@ -507,10 +545,17 @@ compat_adapt_installed() {
507
545
  real="$(readlink -f -- "$path" 2>/dev/null || printf '%s' "$path")"
508
546
  [[ -f "$real" && -x "$real" ]] || return 0
509
547
  if _compat_is_wrapper "$real"; then
548
+ # Already wrapped: re-probe it, so a binary that later stops starting at
549
+ # this tier climbs instead of being handed to the user as-is. Escalation
550
+ # is what every installer reaches through this function; only freebuff
551
+ # calls compat_adapt() directly, so without this the glibc userland and
552
+ # proot tiers would stay unreachable for the rest of the catalog.
553
+ compat_escalate "$real" || true
510
554
  return 0
511
555
  fi
512
556
  if compat_is_glibc_elf "$real"; then
513
557
  compat_wrap "$real" || true
558
+ compat_escalate "$real" || true
514
559
  fi
515
560
  return 0
516
561
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karnel-termux",
3
- "version": "4.18.0",
3
+ "version": "4.18.2",
4
4
  "description": "Modular Dev Environment for Termux — install languages, databases, AI agents, editors, and more with one command",
5
5
  "bin": {
6
6
  "karnel": "karnel/bin/karnel"