Skip to content

Instantly share code, notes, and snippets.

@flowerornament
Created March 9, 2026 06:08
Show Gist options
  • Select an option

  • Save flowerornament/64a23037204c6a6ea58ebac20156703e to your computer and use it in GitHub Desktop.

Select an option

Save flowerornament/64a23037204c6a6ea58ebac20156703e to your computer and use it in GitHub Desktop.
Lobster install.sh test report — ARM64 on Hetzner CAX21

Lobster install.sh Test Report

Tested by: morgan (via Claude Code) Date: 2026-03-08 Script source: https://raw.githubusercontent.com/sayhar/lobster/main/install.sh Test mode: --non-interactive

Environment

Property Value
Provider Hetzner Cloud
Server type CAX21 (ARM Ampere)
Location Nuremberg, Germany (nbg1)
OS Ubuntu 24.04 (aarch64)
Kernel 6.8.0-90-generic
RAM 7.5 GB
Disk 75 GB
Architecture aarch64 (ARM64)

This is the first known test on ARM64. The existing golden image path targets x86 Debian 12 on AWS Lightsail.

Result: PASS (with one bug fix)

The installer completed successfully after patching one bug. All components installed and verified on ARM64.

Bugs Found

BUG 1 (Blocker): set -o pipefail + grep -v on empty crontab kills the script

Lines affected: 962, 977, 1002

Symptom: Script silently stops at "Setting up health monitoring..." with no error message.

Root cause: The script uses set -euo pipefail (line 16). The crontab update pattern is:

(crontab -l 2>/dev/null | grep -v "$MARKER" | grep -v "some-pattern"; \
 echo "new-cron-entry") | crontab -

On a fresh install, crontab -l outputs nothing. grep -v receives empty input and exits with code 1 (no matches). With pipefail enabled, the subshell inherits that exit code, and set -e terminates the script.

Fix: Wrap each crontab update block with set +o pipefail / set -o pipefail:

+set +o pipefail
 (crontab -l 2>/dev/null | grep -v "$HEALTH_MARKER" | grep -v "health-check"; \
  echo "*/2 * * * * $INSTALL_DIR/scripts/health-check-v3.sh $HEALTH_MARKER") | crontab -
+set -o pipefail

Apply this to all three crontab blocks (lines 962, 977, 1002).

Alternative fix: Use { grep -v "$MARKER" || true; } but this is less clean because the { } grouping interacts poorly with the outer subshell piped to crontab -.


BUG 2 (Blocker for bash <(curl ...) invocation): exec sudo -u lobster bash "$0" fails with process substitution

Line affected: 329

Symptom: bash: /dev/fd/63: No such file or directory

Root cause: When run as bash <(curl -fsSL ...), $0 is /dev/fd/63 (a process substitution file descriptor). The script detects it's running as root, creates a lobster user, then does:

exec sudo -u lobster bash "$0" "$@"

The file descriptor /dev/fd/63 belongs to the root process. After exec sudo -u lobster, the fd is no longer accessible to the new user/process.

Workaround: Download the script first:

curl -fsSL https://raw.githubusercontent.com/sayhar/lobster/main/install.sh -o /tmp/install.sh
bash /tmp/install.sh

Suggested fix: Copy the script to a temporary file before re-execing:

if [ "$(id -u)" = "0" ]; then
    # ... create lobster user ...

    # If running from a pipe/fd, copy to a real file first
    if [[ "$0" == /dev/fd/* || "$0" == /proc/self/fd/* ]]; then
        SELF_COPY=$(mktemp /tmp/lobster-install.XXXXXX.sh)
        cat "$0" > "$SELF_COPY"
        chmod +x "$SELF_COPY"
        exec sudo -u lobster bash "$SELF_COPY" "$@"
    else
        exec sudo -u lobster bash "$0" "$@"
    fi
fi

BUG 3 (Cosmetic): Duplicated "IMPORTANT" message at end

Lines affected: 2078-2083 and 2084-2088

The "IMPORTANT: If 'claude' or 'lobster' commands are not found, run: source ~/.bashrc" block is printed twice verbatim.


ARM64 Compatibility Notes

Everything works on ARM64 out of the box. Specific observations:

Component ARM64 Status Notes
System packages (apt) OK All packages available for arm64
Claude Code CLI OK Installed via claude.ai/install.sh, ARM64 native build
uv (Python package manager) OK aarch64-unknown-linux-gnu build
Python venv + packages OK All pip packages installed cleanly
fastembed OK Installed without issues (was flagged as potential ARM risk)
sqlite-vec OK Stable release loaded correctly (no ELFCLASS32 bug on this version)
whisper.cpp OK Built from source with cmake, ~2 min compile time
Whisper model download OK 465MB model downloaded at ~400MB/s
Node.js / npm OK Not directly installed by this script (Claude Code handles it)
Syncthing N/A Not installed by this script (hyperlobster adds it)
systemd services OK Generated from templates, installed correctly

Verdict: The installer is ARM64-ready. No architecture-specific changes needed.

Other Observations

  • The tarball install mode falls back to git clone when no GitHub release exists (No release found, falling back to git clone...). This works but means every "tarball" install is actually a full git clone.
  • DEBIAN_FRONTEND=noninteractive and NEEDRESTART_MODE=a (lines 19-20) are good — they prevent the debconf TTY errors from blocking the install. The debconf warnings still appear in output but don't cause failures.
  • The lobster user creation + sudo setup (lines 314-330) is clean. SSH keys are correctly copied from root.
  • MCP server registration worked on first try.
  • The --non-interactive flag correctly skips all credential prompts while still completing the full mechanical install.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment