#!/bin/sh # IraVoice installer for Linux. # # curl -fsSL https://iravoice.com/install.sh | bash # # Installs IraVoice as ~/.local/bin/iravoice (the AppImage, which updates # itself in place), adds it to your applications menu, and starts it. It runs # as you, never asks for sudo, and writes only inside your home folder. # # Run it again to reinstall the latest version. To remove IraVoice: # # curl -fsSL https://iravoice.com/install.sh | bash -s -- --uninstall set -eu : "${HOME:?is not set, so there is no home folder to install IraVoice into}" DOWNLOAD_URL="https://api.iravoice.com/v1/download/linux" USER_AGENT="IraVoice-installer/1" BIN_DIR="$HOME/.local/bin" APP="$BIN_DIR/iravoice" DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" DESKTOP_FILE="$DATA_HOME/applications/iravoice.desktop" ICON_DIR="$DATA_HOME/icons/hicolor" ICON_SIZES="32x32 128x128 256x256" if [ -t 1 ]; then BOLD="$(printf '\033[1m')" GREEN="$(printf '\033[32m')" YELLOW="$(printf '\033[33m')" RED="$(printf '\033[31m')" RESET="$(printf '\033[0m')" else BOLD="" GREEN="" YELLOW="" RED="" RESET="" fi say() { printf '%s\n' "$*"; } ok() { printf '%s✔%s %s\n' "$GREEN" "$RESET" "$*"; } note() { printf '%s!%s %s\n' "$YELLOW" "$RESET" "$*"; } fail() { printf '%s✘%s %s\n' "$RED" "$RESET" "$*" >&2 exit 1 } has() { command -v "$1" >/dev/null 2>&1; } # Shows ~ instead of the home folder (display only, hence the literal ~). # shellcheck disable=SC2088 pretty() { case "$1" in "$HOME"/*) printf '~/%s' "${1#"$HOME"/}" ;; *) printf '%s' "$1" ;; esac } # version_at_least HAVE WANT, for "major.minor" versions. version_at_least() { have_major=${1%%.*} have_minor=${1#*.} have_minor=${have_minor%%.*} want_major=${2%%.*} want_minor=${2#*.} [ "$have_major" -gt "$want_major" ] || { [ "$have_major" -eq "$want_major" ] && [ "$have_minor" -ge "$want_minor" ]; } } check_system() { [ "$(uname -s)" = Linux ] || fail "This installer is for Linux. For a Mac or Windows, download IraVoice from https://iravoice.com/download" case "$(uname -m)" in x86_64 | amd64) ;; *) fail "IraVoice for Linux needs a 64-bit Intel or AMD processor (x86_64); this computer is $(uname -m)." ;; esac if [ "$(id -u)" -eq 0 ] && [ "${IRAVOICE_ALLOW_ROOT:-}" != 1 ]; then fail "Run this as your normal user, not as root: IraVoice installs into your own home folder." fi glibc="$(getconf GNU_LIBC_VERSION 2>/dev/null | awk '{ print $2 }')" [ -n "$glibc" ] || fail "IraVoice needs a Linux with glibc, such as Ubuntu, Debian, Fedora, Arch or openSUSE; this one doesn't have it." version_at_least "$glibc" 2.35 || fail "IraVoice needs Linux from 2022 or later (glibc 2.35, as in Ubuntu 22.04, Debian 12 or Fedora 36); this system has glibc $glibc." } # download URL FILE: prints the final URL after redirects (curl only). download() { if has curl; then progress="-s" [ -t 2 ] && progress="--progress-bar" curl -fL "$progress" --retry 3 -A "$USER_AGENT" -o "$2" -w '%{url_effective}' "$1" elif has wget; then if [ -t 2 ] && wget --help 2>&1 | grep -q -- '--show-progress'; then wget -q --show-progress -U "$USER_AGENT" -O "$2" "$1" else wget -q -U "$USER_AGENT" -O "$2" "$1" fi else fail "Install curl or wget first, then run this again." fi } install_icons() { work="$(mktemp -d)" # --appimage-extract needs no FUSE. if (cd "$work" && "$APP" --appimage-extract 'usr/share/icons/*' >/dev/null 2>&1); then for size in $ICON_SIZES; do source_dir="$size" [ "$size" = 256x256 ] && source_dir="256x256@2" icon="$work/squashfs-root/usr/share/icons/hicolor/$source_dir/apps/iravoice.png" if [ -f "$icon" ]; then mkdir -p "$ICON_DIR/$size/apps" cp "$icon" "$ICON_DIR/$size/apps/iravoice.png" fi done fi rm -rf "$work" } install_menu_entry() { mkdir -p "$(dirname "$DESKTOP_FILE")" cat >"$DESKTOP_FILE" </dev/null 2>&1 || true } is_running() { has pgrep && pgrep -x iravoice >/dev/null 2>&1 } launch() { if has setsid; then setsid "$APP" /dev/null 2>&1 & else nohup "$APP" /dev/null 2>&1 & fi } uninstall() { rm -f "$APP" "$DESKTOP_FILE" for size in $ICON_SIZES; do rm -f "$ICON_DIR/$size/apps/iravoice.png"; done has update-desktop-database && update-desktop-database "$(dirname "$DESKTOP_FILE")" >/dev/null 2>&1 || true ok "IraVoice is removed." say " Your settings and history are still in $(pretty "$DATA_HOME/com.iravoice.app"), and the speech" say " models in $(pretty "$HOME/.cache/huggingface/hub")/models--handy-computer--*; delete them to remove those too." is_running && note "IraVoice is still running: quit it from its tray icon." return 0 } main() { case "${1:-}" in "") ;; --uninstall) uninstall; return ;; *) fail "Unknown option: $1 (use --uninstall to remove IraVoice)" ;; esac check_system say "${BOLD}Installing IraVoice for Linux...${RESET}" was_running="" is_running && was_running=1 mkdir -p "$BIN_DIR" tmp="$BIN_DIR/.iravoice.download.$$" trap 'rm -f "$tmp"' EXIT trap 'exit 130' INT TERM final_url="$(download "$DOWNLOAD_URL" "$tmp")" || fail "The download failed. Check your connection and run this again, or download IraVoice from https://iravoice.com/download" # An AppImage is an ELF program; anything else is an error page. [ "$(head -c 4 "$tmp" | tail -c 3)" = ELF ] || fail "The download wasn't the IraVoice app. Try again in a minute, or download it from https://iravoice.com/download" chmod 755 "$tmp" mv -f "$tmp" "$APP" trap - EXIT install_icons install_menu_entry version="$(printf '%s' "$final_url" | sed -n 's/.*IraVoice-\([0-9][0-9.]*\)-linux-x64\.AppImage.*/\1/p')" say "" ok "IraVoice${version:+ $version} installed" say " Location: $(pretty "$APP")" say " Menu: IraVoice is in your applications menu" say " Updates: IraVoice updates itself" # Setup notes: things IraVoice needs that this script won't install as root. notes="" case ":${PATH:-}:" in *":$BIN_DIR:"*) ;; *) notes=1 shell_name="${SHELL:-}" shell_name="${shell_name##*/}" # A command for the user to paste, so ~ stays literal. # shellcheck disable=SC2088 case "$shell_name" in zsh) rc="~/.zshrc" ;; *) rc="~/.bashrc" ;; esac if [ "$shell_name" = fish ]; then note "$(pretty "$BIN_DIR") isn't on your PATH. To run iravoice from a terminal: fish_add_path ~/.local/bin" else note "$(pretty "$BIN_DIR") isn't on your PATH. To run iravoice from a terminal:" say " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> $rc && source $rc" fi ;; esac if ! has fusermount3 && ! has fusermount; then notes=1 note "IraVoice runs through FUSE, which isn't installed. Install the fuse3 package:" say " sudo apt install fuse3 (Ubuntu, Debian) · sudo dnf install fuse3 (Fedora) · sudo pacman -S fuse3 (Arch)" fi if ! has xdotool && ! has wtype && ! has kwtype && ! has dotool && ! has ydotool; then notes=1 note "IraVoice types with a helper you may need to install: xdotool on X11; on Wayland," say " wtype (Sway, Hyprland), kwtype (KDE Plasma), or dotool or ydotool (GNOME)." fi if [ -x /usr/bin/iravoice ]; then notes=1 note "The IraVoice package is installed too (/usr/bin/iravoice). Keep one copy: remove the package" say " with sudo apt remove iravoice (Ubuntu, Debian) or sudo dnf remove iravoice (Fedora)." fi [ -n "$notes" ] && say "" if [ -n "$was_running" ]; then say " Next: quit IraVoice from its tray icon and open it again to use this version." elif [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && [ "${IRAVOICE_NO_LAUNCH:-}" != 1 ]; then launch say " Next: IraVoice is starting. Hold Ctrl+Space, speak, and release." else say " Next: open IraVoice from your applications menu, or run: iravoice" fi say " On Wayland, if Ctrl+Space doesn't reach IraVoice, bind a desktop shortcut to:" say " $APP --toggle-transcription" } main "$@"