#!/bin/bash arch=$(uname -m) if [ "$(id -u)" -eq 0 ]; then echo "This script is not meant to be run as root." echo "It will create a home directory inside the rootfs for your current user, and if that user is root, this might not be what you want." read -p "Are you sure you want to continue as root? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi fi if [ "$arch" != "riscv64" ]; then echo "You are not on 64-bit RISC-V. felix86 only works on 64-bit RISC-V." exit 1 fi if ! command -v curl >/dev/null 2>&1; then echo "Error: curl is not installed. Please install it and try again." exit 1 fi if ! command -v tar >/dev/null 2>&1; then echo "Error: tar is not installed. Please install it and try again." exit 1 fi if ! command -v unzip >/dev/null 2>&1; then echo "Error: unzip is not installed. Please install it and try again." exit 1 fi if ! command -v sudo >/dev/null 2>&1; then echo "Error: sudo is not installed. Please install it and try again." exit 1 fi if ! command -v jq >/dev/null 2>&1; then echo "Error: jq is not installed. Please install it and try again." echo "On Ubuntu/Debian: sudo apt install jq" echo "On Arch: sudo pacman -S jq" echo "On Fedora: sudo dnf install jq" echo "On openSUSE: sudo zypper install jq" exit 1 fi if [ -z "$HOME" ] || [ ! -d "$HOME" ]; then echo "Error: \$HOME is not set or not a valid directory." exit 1 fi if [ -z "$USER" ]; then echo "\$USER is not set" exit 1 fi INSTALLATION_DIR="/opt/felix86" FILE="$INSTALLATION_DIR/felix86" check_url() { local url="$1" if ! curl -k --output /dev/null --silent --head --fail "$url"; then echo "URL is invalid or unreachable: $url" exit 1 else return 0 fi } copy_and_notify() { local src="$1" local dst="$2" if [[ ! -e "$src" ]]; then echo "$src doesn't exist, skipping..." return fi echo "Copying $src to $dst" if ! sudo cp -rp "$src" "$dst"; then echo "Error: failed to copy '$src' to '$dst'" >&2 exit 1 fi } select_release_url() { local url_list check_url "https://cdn.felix86.com/releases/meta.txt" url_list=$(curl -k -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 "┌───────────────────────────────────────┐" echo "│ Welcome to the felix86 installer! │" echo "├───────────────────────────────────────┤" echo "│ │" echo "│ Please choose the version you'd │" echo "│ like to install │" echo "│ │" echo "└───────────────────────────────────────┘" echo for i in "${!entries[@]}"; do local version="${entries[$i]%% *}" local display="felix86 $version" local extra="" if [[ $i -eq 0 ]]; then extra="[Default]" fi printf " %d) %-17s %s\n" $((i+1)) "$display" "$extra" done local latest_index=$(( ${#entries[@]} + 1 )) printf " %d) %-17s %s\n" $latest_index "Nightly version" "[Unstable]" local choose_nightly_index=$(( ${#entries[@]} + 2 )) printf " %d) %s\n" $choose_nightly_index "Choose specific build" echo local choice while true; do read -p "Choose a release (1-$choose_nightly_index) [default: 1]: " choice choice=${choice:-1} if [[ "$choice" =~ ^[1-9][0-9]*$ ]] && (( choice >= 1 && choice <= choose_nightly_index )); then break else echo "Invalid selection. Please choose a number between 1 and $choose_nightly_index." fi done if (( choice == latest_index )); then echo "Fetching latest artifact link..." ver="latest" nightly="https://nightly.link/OFFTKP/felix86/workflows/build/master" FELIX86_LINK="$nightly/linux_artifact.zip" elif (( choice == choose_nightly_index )); then echo "Fetching latest artifacts..." github_api_link="https://api.github.com/repos/OFFTKP/felix86/actions/artifacts?name=linux_artifact&per_page=100" github_api_response=$(curl -s -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "$github_api_link" | jq '.artifacts[] | select(.workflow_run.head_branch == "master")') if [ -z "$github_api_response" ]; then echo "No artifacts found on master branch" exit 1 fi artifacts_array=$(echo "$github_api_response" | jq -s '.') artifacts_length=$(echo "$artifacts_array" | jq 'length') echo "Available artifacts:" echo "$artifacts_array" | jq -r 'to_entries[] | "\(.key + 1)) felix86 commit \(.value.workflow_run.head_sha[0:7]) from \(.value.created_at | strptime("%Y-%m-%dT%H:%M:%SZ") | strftime("%Y-%m-%d %H:%M"))"' echo "" read -p "Select artifact (1-$artifacts_length): " selection if [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le "$artifacts_length" ]; then selected_id=$(echo "$artifacts_array" | jq -r ".[$((selection-1))].workflow_run.id") if [ -z "$selected_id" ]; then echo "Failed to get artifact id?" exit 1 fi FELIX86_LINK=https://nightly.link/OFFTKP/felix86/actions/runs/$selected_id/linux_artifact.zip else echo "Invalid selection" exit 1 fi else ver=${entries[$((choice-1))]} selected="https://github.com/OFFTKP/felix86/releases/download/$ver/felix86.$ver.zip" FELIX86_LINK="${selected#* }" fi } set -e select_release_url echo "Downloading felix86 $ver from $FELIX86_LINK ..." mkdir -p /tmp/felix86_artifact curl -k -L "$FELIX86_LINK" -o /tmp/felix86_artifact/archive.zip echo "Unzipping..." unzip -o -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 # Save the old installation in /tmp/felix86_artifact/old. in case the user wants it back TEMP_OLD=$(mktemp -d /tmp/felix86_artifact/old.XXXXXX) moved_old=0 if [ -f "/usr/bin/felix86" ]; then sudo mv /usr/bin/felix86 $TEMP_OLD/felix86.link moved_old=1 fi if [ -f "$FILE" ]; then sudo mv $FILE $TEMP_OLD/ moved_old=1 fi if [ -f "/usr/bin/felix86-mounter" ]; then sudo mv /usr/bin/felix86-mounter $TEMP_OLD/felix86-mounter.link moved_old=1 fi if [ -f "$INSTALLATION_DIR/felix86-mounter" ]; then sudo mv $INSTALLATION_DIR/felix86-mounter $TEMP_OLD/ moved_old=1 fi if [ -d "$INSTALLATION_DIR/lib" ]; then sudo mv $INSTALLATION_DIR/lib $TEMP_OLD/ moved_old=1 fi if [[ "$moved_old" == "1" ]]; then echo "Moved old felix86 installation to $TEMP_OLD" fi sudo mv /tmp/felix86_artifact/felix86 $FILE if [ -f "/tmp/felix86_artifact/felix86-mounter" ]; then sudo mv /tmp/felix86_artifact/felix86-mounter $INSTALLATION_DIR/felix86-mounter sudo chown root:root $INSTALLATION_DIR/felix86-mounter sudo chmod u+s $INSTALLATION_DIR/felix86-mounter sudo ln -s $INSTALLATION_DIR/felix86-mounter /usr/bin/felix86-mounter fi sudo mv /tmp/felix86_artifact/lib $INSTALLATION_DIR/ sudo ln -s $FILE /usr/bin/felix86 sudo chown 0:0 $FILE sudo chown -R 0:0 $INSTALLATION_DIR/lib echo "Successfully installed felix86 at $FILE and libraries at $INSTALLATION_DIR/lib" felix86 --set-thunks $INSTALLATION_DIR/lib current_rootfs=$(felix86 -g) INSTALL_ROOTFS=1 if [[ -n "$current_rootfs" && -d "$current_rootfs" ]]; then echo "Looks like you already have a rootfs at $current_rootfs" read -p "Do you want to install a new rootfs anyway? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then INSTALL_ROOTFS=0 NEW_ROOTFS=$current_rootfs fi fi if [[ "$INSTALL_ROOTFS" == "1" ]]; then echo "" echo "Downloading rootfs list..." check_url "https://cdn.felix86.com/rootfs/meta.json" json=$(curl -k -s https://cdn.felix86.com/rootfs/meta.json) mapfile -t names < <(jq -r 'to_entries[].key' <<< "$json") default_index=$(jq -r 'to_entries | map(.value.recommended) | index(true)' <<< "$json") echo "┌───────────────────────────────────────┐" echo "│ Rootfs installer │" echo "├───────────────────────────────────────┤" echo "│ │" echo "│ Please choose the rootfs you'd │" echo "│ like to install │" echo "│ │" echo "└───────────────────────────────────────┘" echo i=1 for name in "${names[@]}"; do size=$(jq -r --arg n "$name" '.[$n].size' <<< "$json") recommended=$(jq -r --arg n "$name" '.[$n].recommended' <<< "$json") if [[ "$recommended" == "true" ]]; then printf " %d) %-25s %s\n" $i "$name" "[Size: $size] [Recommended]" else printf " %d) %-25s %s\n" $i "$name" "[Size: $size]" fi ((i++)) done echo " $i) Enter custom path" custom_index=$i echo while true; do read -p "Choose an option (default: ${names[default_index]}): " choice if [[ -z "$choice" ]]; then choice=$(($default_index+1)) fi if [[ "$choice" == "$custom_index" ]]; then echo "You selected to use your own rootfs." echo "Please specify the absolute path to your rootfs" read -p "Path: " line felix86 --set-rootfs "$line" NEW_ROOTFS="$line" break fi if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice < custom_index )); then selected="${names[choice-1]}" selected_url=$(jq -r --arg n "$selected" '.[$n].url' <<< "$json") read -p "Installation path for $selected [default: $INSTALLATION_DIR/rootfs]: " NEW_ROOTFS DEFAULT_ROOTFS=$INSTALLATION_DIR/rootfs NEW_ROOTFS=${NEW_ROOTFS:-$DEFAULT_ROOTFS} NEW_ROOTFS=$(eval echo "$NEW_ROOTFS") if [ ! -e "$NEW_ROOTFS" ] || [ -d "$NEW_ROOTFS" ] && [ -z "$(ls -A "$NEW_ROOTFS" 2> /dev/null)" ]; then echo "Checking if $selected_url is live..." check_url $selected_url echo "Installing rootfs to $NEW_ROOTFS" echo "Creating rootfs directory..." sudo mkdir -p "$NEW_ROOTFS" echo "Downloading $selected..." curl -k -L $selected_url | sudo tar --same-owner -xz -C "$NEW_ROOTFS" sudo chown 0:0 "$NEW_ROOTFS" sudo mkdir "$NEW_ROOTFS/home" CURRENT_USER=$(whoami) echo "Creating home directory for $CURRENT_USER..." sudo mkdir "$NEW_ROOTFS/home/$CURRENT_USER" sudo chown $CURRENT_USER:$CURRENT_USER "$NEW_ROOTFS/home/$CURRENT_USER" echo "Creating /dev..." sudo mkdir -p "$NEW_ROOTFS/dev" echo "Creating /proc..." sudo mkdir -p "$NEW_ROOTFS/proc" echo "Creating /sys..." sudo mkdir -p "$NEW_ROOTFS/sys" echo "Creating /run..." sudo mkdir -p "$NEW_ROOTFS/run" echo "Creating /tmp..." sudo mkdir -p "$NEW_ROOTFS/tmp" echo "$selected was downloaded and extracted in $NEW_ROOTFS" echo "Copying important files to rootfs..." mkdir -p "$NEW_ROOTFS/var/lib" mkdir -p "$NEW_ROOTFS/etc" copy_and_notify "/etc/mtab" "$NEW_ROOTFS/etc/mtab" copy_and_notify "/etc/passwd" "$NEW_ROOTFS/etc/passwd" copy_and_notify "/etc/passwd-" "$NEW_ROOTFS/etc/passwd-" copy_and_notify "/etc/group" "$NEW_ROOTFS/etc/group" copy_and_notify "/etc/group-" "$NEW_ROOTFS/etc/group-" copy_and_notify "/etc/shadow" "$NEW_ROOTFS/etc/shadow" copy_and_notify "/etc/shadow-" "$NEW_ROOTFS/etc/shadow-" copy_and_notify "/etc/gshadow" "$NEW_ROOTFS/etc/gshadow" copy_and_notify "/etc/gshadow-" "$NEW_ROOTFS/etc/gshadow-" copy_and_notify "/etc/hosts" "$NEW_ROOTFS/etc/hosts" copy_and_notify "/etc/hostname" "$NEW_ROOTFS/etc/hostname" copy_and_notify "/etc/timezone" "$NEW_ROOTFS/etc/timezone" copy_and_notify "/etc/localtime" "$NEW_ROOTFS/etc/localtime" copy_and_notify "/etc/fstab" "$NEW_ROOTFS/etc/fstab" copy_and_notify "/etc/subuid" "$NEW_ROOTFS/etc/subuid" copy_and_notify "/etc/subgid" "$NEW_ROOTFS/etc/subgid" copy_and_notify "/etc/machine-id" "$NEW_ROOTFS/etc/machine-id" copy_and_notify "/etc/resolv.conf" "$NEW_ROOTFS/etc/resolv.conf" copy_and_notify "/etc/sudoers" "$NEW_ROOTFS/etc/sudoers" echo "Done!" felix86 --set-rootfs "$NEW_ROOTFS" else echo "$NEW_ROOTFS already exists and is not empty, I won't unpack the rootfs there" exit 1 fi break else echo "Invalid choice, try again." fi done fi # Finally register felix86 in binfmt_misc sudo --preserve-env=HOME felix86 --binfmt-misc echo "Running a test program..." # 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 "felix86 failed to run a simple program -- may not have installed correctly?" echo "Running again with logging enabled:" felix86 "$NEW_ROOTFS/bin/bash" -c "exit 42" exit 1 fi echo "felix86 installed successfully" echo echo echo "┌───────────────────────────────────────┐" echo "│ felix86 is now installed! │" echo "├───────────────────────────────────────┤" echo "│ │" echo "│ To enter an emulated bash, use: │" echo "│ │" echo "│ felix86 --shell (25.12 and later) │" echo "│ │" echo "│ OR │" echo "│ │" echo "│ felix86 /path/to/rootfs/bin/bash │" echo "│ │" echo "├───────────────────────────────────────┤" echo "│ │" echo "│ Alternatively, run programs directly: │" echo "│ │" echo "│ /path/to/rootfs/MyGame.AppImage │" echo "│ │" echo "└───────────────────────────────────────┘" 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 --preserve-env=HOME $FILE -b" echo "If you don't register to binfmt_misc, some apps like sudo (needed by AppImages) will not work" fi