#!/usr/bin/env bash # PhoneMic receiver for Linux. # # curl -fsSL https://phonemicapp.com/iphone-mic-pc/linux/install.sh | bash # curl -fsSL https://phonemicapp.com/iphone-mic-pc/linux/install.sh | bash -s -- --headless # curl -fsSL https://phonemicapp.com/iphone-mic-pc/linux/install.sh | bash -s -- --uninstall # # Installs ~/.local/bin/phonemic-receiver and opens UDP 45333 + mDNS in firewalld # or ufw. From a desktop session it installs the PhoneMic app (menu entry, tray # icon, starts at sign-in); over SSH, or with --headless, it runs as a systemd # --user service instead. --app forces the app. PHONEMIC_NO_FIREWALL=1 skips the # firewall step, PHONEMIC_GAIN=4 starts it louder. set -euo pipefail VERSION=0.2.0 BASE=${PHONEMIC_BASE:-https://phonemicapp.com/iphone-mic-pc/linux} BIN_DIR=$HOME/.local/bin BIN=$BIN_DIR/phonemic-receiver PORT=45333 say() { printf '\033[1m==> %s\033[0m\n' "$*"; } note() { printf ' %s\n' "$*"; } die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null; then sudo "$@" else return 1 fi } missing_pipewire_hint() { . /etc/os-release 2>/dev/null || true case " ${ID:-} ${ID_LIKE:-} " in *" fedora "*|*" rhel "*) echo "sudo dnf install pipewire-libs" ;; *" debian "*|*" ubuntu "*) echo "sudo apt install libpipewire-0.3-0" ;; *" arch "*) echo "sudo pacman -S pipewire" ;; *" opensuse"*|*" suse "*) echo "sudo zypper install libpipewire-0_3-0" ;; *) echo "install your distro's PipeWire client library (libpipewire-0.3.so.0)" ;; esac } DATA_DIR=${XDG_DATA_HOME:-$HOME/.local/share} CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config} uninstall() { say "removing PhoneMic" if [ -x "$BIN" ]; then "$BIN" --quit >/dev/null 2>&1 || true "$BIN" --autostart off || true fi rm -f "$BIN" "$DATA_DIR/applications/phonemic.desktop" "$CONFIG_DIR/autostart/phonemic.desktop" \ "$DATA_DIR/icons/hicolor/256x256/apps/phonemic.png" note "removed $BIN and the menu entry" note "firewall rules were left alone; to close them:" note " firewalld: sudo firewall-cmd --permanent --remove-port=$PORT/udp && sudo firewall-cmd --reload" note " ufw: sudo ufw delete allow $PORT/udp" exit 0 } main() { MODE=auto case "${1:-}" in --uninstall) uninstall ;; --headless) MODE=headless ;; --app) MODE=app ;; "") ;; *) die "unknown option ${1}. Use --app, --headless or --uninstall" ;; esac if [ "$MODE" = auto ]; then if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then MODE=app; else MODE=headless; fi fi [ "$(uname -s)" = Linux ] || die "this installer is for Linux. Windows: https://phonemicapp.com/iphone-mic-pc/" case "$(uname -m)" in x86_64|amd64) ARCH=x86_64 ;; aarch64|arm64) ARCH=aarch64 ;; *) die "no build for $(uname -m) yet. Build from source: https://phonemicapp.com/iphone-mic-pc/linux/" ;; esac [ "$(id -u)" -ne 0 ] || die "run this as your normal user, not root. The mic has to live in your own audio session." if command -v curl >/dev/null; then fetch() { curl -fsSL "$1" -o "$2"; } elif command -v wget >/dev/null; then fetch() { wget -qO "$2" "$1"; } else die "needs curl or wget" fi say "downloading phonemic-receiver $VERSION ($ARCH)" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT tarball=phonemic-receiver-$VERSION-linux-$ARCH.tar.gz fetch "$BASE/$tarball" "$tmp/$tarball" || die "download failed: $BASE/$tarball" fetch "$BASE/$tarball.sha256" "$tmp/$tarball.sha256" || die "download failed: $BASE/$tarball.sha256" (cd "$tmp" && sha256sum -c --quiet "$tarball.sha256") || die "checksum mismatch, not installing" tar -xzf "$tmp/$tarball" -C "$tmp" note "checksum ok" if ldd "$tmp/phonemic-receiver" 2>/dev/null | grep -q "libpipewire.*not found"; then die "PipeWire's client library is missing: $(missing_pipewire_hint)" fi "$tmp/phonemic-receiver" --version >/dev/null 2>&1 \ || die "the binary does not run here (it needs glibc 2.35 or newer: Fedora 36+, Ubuntu 22.04+, Debian 12+)" say "installing" if [ -x "$BIN" ]; then "$BIN" --quit >/dev/null 2>&1 || true "$BIN" --autostart off >/dev/null 2>&1 || true fi install -Dm755 "$tmp/phonemic-receiver" "$BIN" note "$BIN" say "firewall" if [ -n "${PHONEMIC_NO_FIREWALL:-}" ]; then note "skipped (PHONEMIC_NO_FIREWALL set)" elif systemctl is-active --quiet firewalld 2>/dev/null; then note "firewalld is on: opening $PORT/udp and mdns (asks for your password)" if as_root firewall-cmd --permanent --add-port=$PORT/udp >/dev/null 2>&1 \ && as_root firewall-cmd --permanent --add-service=mdns >/dev/null 2>&1 \ && as_root firewall-cmd --reload >/dev/null; then note "opened" else note "could not change firewalld. Run these yourself, or the phone gets no audio through:" note " sudo firewall-cmd --permanent --add-port=$PORT/udp" note " sudo firewall-cmd --permanent --add-service=mdns" note " sudo firewall-cmd --reload" fi elif systemctl is-active --quiet ufw 2>/dev/null; then note "ufw is on: allowing $PORT/udp and 5353/udp (asks for your password)" if as_root ufw allow $PORT/udp comment PhoneMic >/dev/null \ && as_root ufw allow 5353/udp comment mDNS >/dev/null; then note "allowed" else note "could not change ufw. Run these yourself, or the phone gets no audio through:" note " sudo ufw allow $PORT/udp" note " sudo ufw allow 5353/udp" fi else note "no firewalld or ufw running, nothing to open" fi if [ ! -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pipewire-0" ]; then note "warning: PipeWire does not seem to be running in this session." note "Phone Mic needs a PipeWire desktop (Fedora, Ubuntu 22.10+, Arch and most 2023+ distros)." fi if [ "$MODE" = app ]; then say "app" "$BIN" --setup-desktop note "menu entry: PhoneMic (starts at sign-in, lives in the tray)" args=(--gui) [ -n "${PHONEMIC_GAIN:-}" ] && args+=(--gain "$PHONEMIC_GAIN") if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then setsid "$BIN" "${args[@]}" >/dev/null 2>&1 < /dev/null & note "opened the PhoneMic window" else note "open PhoneMic from your app menu" fi else say "autostart" if systemctl --user show-environment >/dev/null 2>&1; then args=(--autostart on) [ -n "${PHONEMIC_GAIN:-}" ] && args+=(--gain "$PHONEMIC_GAIN") "$BIN" "${args[@]}" | sed 's/^/ /' sleep 2 if systemctl --user is-active --quiet phonemic-receiver; then note "running" else note "the service did not stay up: journalctl --user -u phonemic-receiver -n 30" fi else note "no systemd user session; start it by hand: $BIN" fi fi case ":$PATH:" in *":$BIN_DIR:"*) ;; *) note "note: $BIN_DIR is not on your PATH (PhoneMic does not need it)" ;; esac host=$(cat /proc/sys/kernel/hostname 2>/dev/null || hostname) printf '\n\033[1mDone.\033[0m\n' printf ' 1. Open PhoneMic on your phone (same Wi-Fi as this computer) and pick "%s".\n' "$host" printf ' 2. In Zoom, Meet, Discord or OBS choose "Phone Mic" as the microphone.\n\n' printf ' Check: %s --doctor\n' "$BIN" if [ "$MODE" = app ]; then printf ' Settings: the PhoneMic window (app menu or tray icon)\n' else printf ' Logs: journalctl --user -u phonemic-receiver -f\n' fi printf ' Uninstall: curl -fsSL %s/install.sh | bash -s -- --uninstall\n' "$BASE" } main "$@"