#!/bin/bash set -euo pipefail main() { die() { echo "Error: $*" >&2; exit 1; } file_sha256() { if command -v openssl >/dev/null 2>&1; then openssl sha256 -r "$1" | cut -d' ' -f1 else sha256sum "$1" | cut -d' ' -f1 fi } arch=$(uname -m) if [ "$(id -u)" -eq 0 ]; then if command -v whiptail >/dev/null 2>&1; then if ! whiptail --title "Warning" --yesno \ "This script is not meant to be run as root.\n\nAre you sure you want to continue?" \ 0 0; then exit 1 fi else echo "Warning: This script is not meant to be run as root." read -r -p "Are you sure you want to continue? [y/N] " reply case "$reply" in [Yy]|[Yy][Ee][Ss]) ;; *) exit 1 ;; esac fi fi if [ "$arch" != "riscv64" ]; then die "You are not on 64-bit RISC-V. felix86 only works on 64-bit RISC-V." fi missing=() for cmd in curl tar unzip sudo jq patchelf whiptail sha256sum; do command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd") done if [ ${#missing[@]} -gt 0 ]; then die "missing required tools: ${missing[*]} Ubuntu/Debian: sudo apt install ${missing[*]} Arch: sudo pacman -S ${missing[*]}" fi if [ -z "$HOME" ] || [ ! -d "$HOME" ]; then die "\$HOME is not set or not a valid directory." fi if [ -z "$USER" ]; then die "\$USER is not set" fi INSTALLATION_DIR="/opt/felix86" FILE="$INSTALLATION_DIR/bin/felix86" ICONS_SHA256="665a264e462b2ddd07f478b83cf34219497def4a1d8cab4cab04d56c0864d847" IS_UPDATE=0 if [ -x "$FILE" ]; then IS_UPDATE=1 fi felix86_version_gte() { local required="$1" local version version=$(felix86 --version 2>/dev/null | grep -oP '\d+\.\d+' | head -1) if [[ -z "$version" ]]; then return 1 fi local req_major req_minor ver_major ver_minor IFS='.' read -r req_major req_minor <<< "$required" IFS='.' read -r ver_major ver_minor <<< "$version" if (( 10#$ver_major > 10#$req_major )) || (( 10#$ver_major == 10#$req_major && 10#$ver_minor >= 10#$req_minor )); then return 0 fi return 1 } check_url() { local url="$1" if ! curl --output /dev/null --silent --head --fail "$url"; then die "URL is invalid or unreachable: $url" fi } GITHUB_ARTIFACTS_JSON="[]" fetch_nightly_artifacts() { local api="https://api.github.com/repos/OFFTKP/felix86/actions/artifacts?name=linux_artifact&per_page=100" local json json=$(curl -sfL --max-time 20 \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" "$api" 2>/dev/null \ | jq -c '[.artifacts[] | select(.workflow_run.head_branch == "master" and .expired == false)] | sort_by(.created_at) | reverse' 2>/dev/null) || json="" if [[ -n "$json" && "$json" != "null" ]]; then GITHUB_ARTIFACTS_JSON="$json" fi } nightly_run_link() { echo "https://nightly.link/OFFTKP/felix86/actions/runs/$1/linux_artifact.zip" } select_release_url() { local url_list check_url "https://cdn.felix86.com/releases/meta.txt" url_list=$(curl -s https://cdn.felix86.com/releases/meta.txt) if [[ -z "$url_list" ]]; then echo "Failed to fetch the releases list or it's empty." return 1 fi local total_lines total_lines=$(echo "$url_list" | wc -l) if (( total_lines > 5 )); then echo "Failed to parse https://cdn.felix86.com/releases/meta.txt" return 1 fi local entries=() while IFS= read -r line && [[ ${#entries[@]} -lt 5 ]]; do entries+=("$line") done <<< "$url_list" if [[ ${#entries[@]} -eq 0 ]]; then echo "No valid entries found." return 1 fi echo "Fetching list of artifacts from GitHub..." fetch_nightly_artifacts local nightly_count nightly_count=$(echo "$GITHUB_ARTIFACTS_JSON" | jq 'length') local menu_items=() for i in "${!entries[@]}"; do local version="${entries[$i]%% *}" local desc="" if [[ $i -eq 0 ]]; then desc=" (Recommended)" fi local num=$((i+1)) menu_items+=("${num})" "felix86 $version$desc") done local latest_index=$(( ${#entries[@]} + 1 )) menu_items+=("${latest_index})" "Nightly version (Unstable)") local choose_nightly_index=$(( ${#entries[@]} + 2 )) menu_items+=("${choose_nightly_index})" "Choose specific build") local choice choice=$(whiptail --title "felix86 Installer" \ --menu "Welcome to the felix86 installer!\n\nChoose the version you'd like to install:" \ 0 0 0 \ "${menu_items[@]}" \ --default-item "1)" \ 3>&1 1>&2 2>&3) || exit 1 choice="${choice%)}" if (( choice == latest_index )); then ver="latest" if (( nightly_count == 0 )); then die "Could not fetch list of artifacts from GitHub" fi local run_id run_id=$(echo "$GITHUB_ARTIFACTS_JSON" | jq -r '.[0].workflow_run.id') if [[ -z "$run_id" || "$run_id" == "null" ]]; then die "Failed to get latest nightly build" fi FELIX86_LINK=$(nightly_run_link "$run_id") elif (( choice == choose_nightly_index )); then if (( nightly_count == 0 )); then die "Could not fetch the list of nightly builds from GitHub" fi local artifact_menu=() local sha date while IFS=$'\t' read -r sha date; do local num=$(( ${#artifact_menu[@]} / 2 + 1 )) artifact_menu+=("${num})" "commit $sha ($date)") done < <(echo "$GITHUB_ARTIFACTS_JSON" | jq -r '.[] | "\(.workflow_run.head_sha[0:7])\t\(.created_at | strptime("%Y-%m-%dT%H:%M:%SZ") | strftime("%Y-%m-%d %H:%M"))"') local artifact_menu_height=$((nightly_count > 10 ? 10 : nightly_count)) local selection selection=$(whiptail --title "felix86 Installer" \ --menu "Choose a specific nightly build:" \ 20 60 $artifact_menu_height \ "${artifact_menu[@]}" \ 3>&1 1>&2 2>&3) || exit 1 selection="${selection%)}" ver="nightly" local run_id run_id=$(echo "$GITHUB_ARTIFACTS_JSON" | jq -r ".[$((selection-1))].workflow_run.id") if [[ -z "$run_id" || "$run_id" == "null" ]]; then die "Failed to get run id for latest build" fi FELIX86_LINK=$(nightly_run_link "$run_id") else ver=${entries[$((choice-1))]} selected="https://github.com/OFFTKP/felix86/releases/download/$ver/felix86.$ver.zip" FELIX86_LINK="${selected#* }" fi } select_release_url echo "Downloading felix86 from $FELIX86_LINK ..." mkdir -p /tmp/felix86_artifact curl -fL --progress-bar "$FELIX86_LINK" -o /tmp/felix86_artifact/archive.zip unzip -oq -d /tmp/felix86_artifact /tmp/felix86_artifact/archive.zip rm /tmp/felix86_artifact/archive.zip echo "Moving felix86 executable to $FILE, requesting permission..." sudo mkdir -p "$INSTALLATION_DIR" sudo mkdir -p "$INSTALLATION_DIR/bin" sudo mkdir -p "$INSTALLATION_DIR/lib" sudo mkdir -p "$INSTALLATION_DIR/lib/x86_64-linux-gnu" sudo mkdir -p "$INSTALLATION_DIR/lib/riscv64-linux-gnu" sudo mkdir -p "$INSTALLATION_DIR/licenses" sudo mv /tmp/felix86_artifact/felix86 $FILE if [ -f "/tmp/felix86_artifact/felix86.png" ]; then sudo mv "/tmp/felix86_artifact/felix86.png" "$INSTALLATION_DIR/felix86.png" fi if ! command -v xdg-icon-resource >/dev/null 2>&1; then echo "xdg-icon-resource not found, skipping icon installation" elif [ ! -f "$HOME/.local/share/icons/hicolor/128x128/apps/offtkp-felix86.png" ]; then echo "Installing icons..." curl -fsSL -o /tmp/felix86_icons.zip https://cdn.felix86.com/images/icons.zip ICONS_ACTUAL_SHA256=$(file_sha256 "/tmp/felix86_icons.zip") if [[ "$ICONS_ACTUAL_SHA256" != "$ICONS_SHA256" ]]; then rm -f /tmp/felix86_icons.zip echo "Checksum mismatch for icons.zip (expected $ICONS_SHA256, got $ICONS_ACTUAL_SHA256), skipping icon installation" else TMP_DIR="$(mktemp -d /tmp/felix86_icons.XXXXXX)" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT unzip -oq /tmp/felix86_icons.zip -d "$TMP_DIR" rm /tmp/felix86_icons.zip for size in 16 24 32 48 96 128; do xdg-icon-resource install --context apps --size "$size" \ "$TMP_DIR/icons/${size}x${size}/felix86.png" offtkp-felix86 done fi fi sudo mv /tmp/felix86_artifact/lib/x86_64-linux-gnu/* "$INSTALLATION_DIR/lib/x86_64-linux-gnu/" FELIX86_GLIBC_VERSION_FILE="$INSTALLATION_DIR/lib/riscv64-linux-gnu/.felix86-glibc-version" INSTALLED_GLIBC_VERSION="" if [ -f "$FELIX86_GLIBC_VERSION_FILE" ]; then INSTALLED_GLIBC_VERSION=$(cat "$FELIX86_GLIBC_VERSION_FILE") fi FELIX86_GLIBC_VERSION=$(curl -sfL --max-time 20 \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/repos/felix86-emu/glibc/releases/latest" 2>/dev/null \ | jq -r '.tag_name // empty' 2>/dev/null) || FELIX86_GLIBC_VERSION="" if [ -z "$FELIX86_GLIBC_VERSION" ]; then echo "Could not fetch latest glibc release version, skipping custom glibc installation" elif [ ! -f "$INSTALLATION_DIR/lib/riscv64-linux-gnu/libc.so.6" ] || [ "$INSTALLED_GLIBC_VERSION" != "$FELIX86_GLIBC_VERSION" ]; then echo "Downloading custom glibc fork ($FELIX86_GLIBC_VERSION)..." mkdir -p /tmp/felix86_artifact/glibc FELIX86_GLIBC_LINK="https://github.com/felix86-emu/glibc/releases/download/$FELIX86_GLIBC_VERSION/glibc-felix86.zip" curl -fSL --progress-bar "$FELIX86_GLIBC_LINK" -o /tmp/felix86_artifact/glibc/glibc.zip unzip -oq -d /tmp/felix86_artifact/glibc /tmp/felix86_artifact/glibc/glibc.zip rm /tmp/felix86_artifact/glibc/glibc.zip sudo mv -f /tmp/felix86_artifact/glibc/lib/riscv64-linux-gnu/* "$INSTALLATION_DIR/lib/riscv64-linux-gnu/" sudo mv -f /tmp/felix86_artifact/glibc/licenses/COPYING.LESSERv2 "$INSTALLATION_DIR/licenses/glibc_LICENSE" echo "$FELIX86_GLIBC_VERSION" | sudo tee "$FELIX86_GLIBC_VERSION_FILE" >/dev/null rmdir /tmp/felix86_artifact/glibc/lib/riscv64-linux-gnu rmdir /tmp/felix86_artifact/glibc/lib rmdir /tmp/felix86_artifact/glibc/licenses rmdir /tmp/felix86_artifact/glibc fi # Old script would install in /usr/bin/felix86, check if its such a symlink and if it is remove it if [ -e "/usr/bin/felix86" ] || [ -L "/usr/bin/felix86" ]; then if [ -L "/usr/bin/felix86" ]; then if [ "$(readlink -f "/usr/bin/felix86")" = "$(readlink -f "$INSTALLATION_DIR/felix86")" ]; then echo "/usr/bin/felix86 is a symlink to $INSTALLATION_DIR/felix86, leftover from older installation script, removing it" sudo rm "/usr/bin/felix86" fi fi fi # This is now moved to $INSTALLATION_DIR/bin, remove old executable to prevent confusion if [ -f "$INSTALLATION_DIR/felix86" ] && [ -x "$INSTALLATION_DIR/felix86" ]; then echo "$INSTALLATION_DIR/felix86 exists, leftover from older installation script, removing it" sudo rm "$INSTALLATION_DIR/felix86" fi sudo ln -sf "$FILE" /usr/local/bin/felix86 FELIX86_LD="$INSTALLATION_DIR/lib/riscv64-linux-gnu/ld-linux-riscv64-lp64d.so.1" if [ -f "$FELIX86_LD" ] && [ -f "$INSTALLATION_DIR/lib/riscv64-linux-gnu/libc.so.6" ]; then sudo patchelf --set-interpreter "$FELIX86_LD" \ --add-rpath "$INSTALLATION_DIR/lib/riscv64-linux-gnu" \ "$FILE" else echo "Custom glibc not found in $INSTALLATION_DIR/lib/riscv64-linux-gnu, using the system glibc" fi sudo chown 0:0 "$FILE" sudo chmod +x "$FILE" sudo chown -R 0:0 "$INSTALLATION_DIR/lib" echo "Successfully installed felix86 at $FILE and libraries at $INSTALLATION_DIR/lib" if felix86_version_gte "26.08"; then sudo felix86 --set-config general.thunks_path=$INSTALLATION_DIR/lib else felix86 --set-thunks $INSTALLATION_DIR/lib fi rmdir /tmp/felix86_artifact/lib/x86_64-linux-gnu rmdir /tmp/felix86_artifact/lib rmdir /tmp/felix86_artifact if felix86_version_gte "26.08"; then current_rootfs=$(felix86 --get-config general.rootfs_path) else current_rootfs=$(felix86 -g) fi INSTALL_ROOTFS=1 if [[ -n "$current_rootfs" && -d "$current_rootfs" ]]; then INSTALL_ROOTFS=0 echo "Looks like you already have a rootfs at $current_rootfs, I will not install a new rootfs" echo "If you want to install a new rootfs use the rootfs installer script:" echo " curl -fsSL https://install.felix86.com/rootfs.sh" fi if [[ "$INSTALL_ROOTFS" == "1" ]]; then bash <(curl -fsSL https://install.felix86.com/rootfs.sh) fi if felix86_version_gte "26.08"; then NEW_ROOTFS=$(felix86 --get-config general.rootfs_path) else NEW_ROOTFS=$(felix86 -g) fi # Finally register felix86 in binfmt_misc sudo --preserve-env=HOME felix86 --binfmt-misc # Let's try to run a test program set +e FELIX86_QUIET=1 felix86 "$NEW_ROOTFS/bin/bash" -c "exit 42" exit_code=$? # If everything went fine, felix86 should return 42 if [ "$exit_code" -ne 42 ]; then echo "Running again with logging enabled:" felix86 "$NEW_ROOTFS/bin/bash" -c "exit 42" die "felix86 failed to run a simple program -- may not have installed correctly?" fi if [ "$IS_UPDATE" -eq 1 ]; then echo "felix86 successfully updated." else whiptail --title "felix86 is now installed!" --msgbox \ "To enter an emulated bash, use: felix86 --shell OR felix86 /path/to/rootfs/bin/bash Alternatively, run programs directly: /path/to/rootfs/MyGame.AppImage" \ 20 50 fi if ! felix86 -d; then echo "Failed binfmt_misc installation check" echo "felix86 is currently not used as the default emulator for x86 applications" echo "If you have other x86 emulators registered then you will need to unregister them" echo "Example of unregistering an emulator: sudo sh -c \"echo -1 > /proc/sys/fs/binfmt_misc/qemu-x86_64\"" echo "To make this change permanent, make sure to remove the respective file from one of these directories:" echo " /etc/binfmt.d" echo " /run/binfmt.d" echo " /usr/local/lib/binfmt.d" echo " /usr/lib/binfmt.d" echo "After you're done, to register felix86, run: sudo $FILE -b" echo "If you don't register to binfmt_misc, some apps like sudo (needed by AppImages) will not work" fi echo "Refer to the usage guide for more info: https://felix86.com/docs/users/usage-guide/" } main "$@"