Skip to content

Installer fails with "qoderwake not found in qoderwake package" on hosts without bash process substitution (Termux/proot, some containers) #7

Description

@Roch57

Installer fails with "qoderwake not found in qoderwake package" on hosts without bash process substitution (Termux/proot, some containers)

Summary

The install.sh installer aborts at the "locate binary inside archive" step on environments where bash process substitution <(...) is unavailable (e.g. Termux proot-distro, and some minimal containers). The error message (qoderwake not found in qoderwake package) is misleading: the qoderwake binary is present in the downloaded archive and its checksum matches the manifest. The real cause is that find_binary() consumes find's output via done < <(find …), and on these hosts the process-substitution file descriptor (/dev/fd/63) cannot be created, so find_binary always returns empty and the install dies.

Environment

OS Debian 13 (trixie) under Termux proot-distro (Android)
Arch aarch64
Kernel Linux 6.17.0-PRoot-Distro
Shell bash 5.x
qoderwake 0.2.8 (qodercli-wake 1.1.8)
Installer URL https://download.qoder.com/qoderwake/install.sh

Reproduce

curl -fsSL https://download.qoder.com/qoderwake/install.sh | bash

Output:

==> Verifying qoderwake checksum
main: line 432: /dev/fd/63: No such file or directory
Error: qoderwake not found in qoderwake package
find: 'standard output': Broken pipe
find: write error

Root cause

On the affected host /dev/fd exists but bash cannot create the high-numbered fds that <(...) relies on:

$ bash -c 'while IFS= read -r x; do echo "$x"; done < <(printf hello)'
bash: line 1: /dev/fd/63: No such file or directory

find_binary() (install.sh, ~L432) uses exactly that construct:

while IFS= read -r candidate; do
  printf '%s\n' "$candidate"
  return 0
done < <(find "$dir" -type f -name "$binary_name" -print)   # <-- L440

When /dev/fd/63 can't be opened, the while body never executes, find_binary falls through to return 1, and the caller (~L523) runs die "$binary_name not found in $label package". The now-orphaned find then writes to the closed pipe, producing the Broken pipe lines. Note: command substitution $(…), pipes, and here-strings <<< all work fine on this host — only process substitution is broken.

The binary is actually present

$ curl -fsSL …/releases/0.2.8/qoderwake_0.2.8_linux_arm64.tar.gz -o qw.tar.gz
$ sha256sum qw.tar.gz
cc4a36f884e875226d6d94c18682a379569b49418585933f484943f05e60b737  qw.tar.gz   # matches manifest
$ tar -tzf qw.tar.gz | head
qoderwake
qodercli/
qodercli/qodercli-wake
resources/
…

So this is an installer portability bug, not a packaging bug.

All affected process-substitution sites

grep -nE 'done < <\(' install.sh:

  • L440, L446find_binary() (critical path; this is what fails)
  • L1012parse_manifest_with_shell() (manifest JSON fallback; only reached when jq, python3, and node are all absent)
  • L1090list_manifest_platforms() (same fallback path)

Suggested fix

Replace process substitution with command substitution (or a pipe). find_binary():

   find_binary() {
     local dir="$1"
     local binary_name="$2"
     local candidate

-    while IFS= read -r candidate; do
-      printf '%s\n' "$candidate"
-      return 0
-    done < <(find "$dir" -type f -name "$binary_name" -print)
+    candidate="$(find "$dir" -type f -name "$binary_name" -print | head -n1)"
+    if [[ -n "$candidate" ]]; then
+      printf '%s\n' "$candidate"
+      return 0
+    fi

     if [[ "$binary_name" == "qodercli" ]]; then
-      while IFS= read -r candidate; do
-        printf '%s\n' "$candidate"
-        return 0
-      done < <(find "$dir" -type f \( -name "qoderclicn" -o -name "qoderclicn.exe" \) -print)
+      candidate="$(find "$dir" -type f \( -name "qoderclicn" -o -name "qoderclicn.exe" \) -print | head -n1)"
+      if [[ -n "$candidate" ]]; then
+        printf '%s\n' "$candidate"
+        return 0
+      fi
     fi

     return 1
   }

Using $(…) (rather than piping into the while) keeps return in the current shell, avoiding the subshell-return pitfall a pipe would introduce. For the L1012/L1090 manifest fallbacks, done < <(printf … | sed …) can become done <<< "$(printf … | sed …)" (here-strings work here) or a plain pipe.

I verified this end-to-end: with the patch, find_binary resolves …/qoderwake, the installer completes, qoderwake --version0.2.8, login succeeds, and qoderwake status reports the daemon online.

Workaround (for affected users now)

Patch the downloaded script before running:

curl -fsSL https://download.qoder.com/qoderwake/install.sh -o install.sh
# in find_binary(), replace the two `done < <(find …)` blocks with the command-substitution form above
bash install.sh

Secondary note: daemon start on hosts without systemd

Same class of host, separate code path: qoderwake start / restart shell out to systemctl daemon-reload, which exits non-zero on systemd-less hosts and aborts the daemon start. qoderwake start --no-keepalive works correctly as the no-systemd path. It would help to either document this or auto-detect command -v systemctl and fall back to --no-keepalive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions