mirror of
https://github.com/FEX-Emu/FEX.git
synced 2024-12-15 01:49:00 +00:00
Merge pull request #1488 from Sonicadvance1/python_auto_install
Scripts: Adds a python script that can hand hold a user through FEX install
This commit is contained in:
commit
b276750c8a
15
Readme.md
15
Readme.md
@ -4,10 +4,25 @@ It has native support for a rootfs overlay, so you don't need to chroot, as well
|
||||
FEX presents a Linux 5.0 interface to the guest, and supports both AArch64 and x86-64 as hosts.
|
||||
FEX is very much work in progress, so expect things to change.
|
||||
|
||||
|
||||
## Quick start guide
|
||||
### For Ubuntu 20.04, 21.04, 21.10, 22.04
|
||||
Execute the following command in the terminal to install FEX through a PPA.
|
||||
|
||||
`curl --silent https://raw.githubusercontent.com/FEX-Emu/FEX/main/Scripts/InstallFEX.py | python3`
|
||||
|
||||
This command will walk you through installing FEX through a PPA, and downloading a RootFS for use with FEX.
|
||||
|
||||
Ubuntu PPA is updated with our monthly releases.
|
||||
|
||||
### For everyone else
|
||||
Follow the guide on the official FEX-Emu Wiki [Here](https://wiki.fex-emu.org/index.php/QuickStartGuide)
|
||||
|
||||
## Getting Started
|
||||
FEX has been tested to build and run on ARMv8.0, ARMv8.1+, and x86-64(AVX or newer) hardware.
|
||||
ARMv7 and older x86 hardware will not work.
|
||||
Expected operating system usage is Linux. FEX has been tested with Ubuntu 20.04, 20.10, and 21.04. Also Arch Linux.
|
||||
|
||||
On AArch64 hosts the user **MUST** have an x86-64 RootFS [Creating a RootFS](#RootFS-Generation).
|
||||
|
||||
### Navigating the Source
|
||||
|
399
Scripts/InstallFEX.py
Executable file
399
Scripts/InstallFEX.py
Executable file
@ -0,0 +1,399 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
_Arch = None
|
||||
def GetArch():
|
||||
global _Arch
|
||||
|
||||
if _Arch == None:
|
||||
_Arch = subprocess.check_output(['uname', '-m']).decode("utf-8").strip()
|
||||
return _Arch
|
||||
|
||||
_Distro = None
|
||||
def GetDistro():
|
||||
global _Distro
|
||||
|
||||
# Query files in order
|
||||
# /etc/lsb-release
|
||||
# /etc/os-release
|
||||
|
||||
if _Distro == None:
|
||||
if os.path.exists("/etc/lsb-release"):
|
||||
File = open("/etc/lsb-release", "r")
|
||||
Lines = File.readlines()
|
||||
File.close()
|
||||
|
||||
Found = 0
|
||||
Distro = ""
|
||||
Version = ""
|
||||
for Line in Lines:
|
||||
Key, Val = Line.split("=", 1)
|
||||
|
||||
if Key == "DISTRIB_ID":
|
||||
Distro = Val.strip().lower()
|
||||
Found+=1
|
||||
if Key == "DISTRIB_RELEASE":
|
||||
Version = Val.strip()
|
||||
Found+=1
|
||||
|
||||
if Found == 2:
|
||||
_Distro = [Distro, Version]
|
||||
return _Distro
|
||||
|
||||
if os.path.exists("/etc/os-release"):
|
||||
File = open("/etc/os-release", "r")
|
||||
Lines = File.readlines()
|
||||
File.close()
|
||||
|
||||
Found = 0
|
||||
Distro = ""
|
||||
Version = ""
|
||||
for Line in Lines:
|
||||
Key, Val = Line.split("=", 1)
|
||||
|
||||
if Key == "ID":
|
||||
Distro = Val.strip()
|
||||
Found+=1
|
||||
if Key == "VERSION_ID":
|
||||
# Strip the double quotes from the version id
|
||||
Version = Val.strip()[1:-1]
|
||||
Found+=1
|
||||
|
||||
if Found == 2:
|
||||
_Distro = [Distro, Version]
|
||||
return _Distro
|
||||
|
||||
# Unknown
|
||||
_Distro = ["Unknown", "0.0"]
|
||||
|
||||
return _Distro
|
||||
|
||||
def IsSupportedArch():
|
||||
Arch = GetArch()
|
||||
return Arch == "aarch64"
|
||||
|
||||
def IsSupportedDistro():
|
||||
Distro = GetDistro()
|
||||
|
||||
# We only support Ubuntu
|
||||
if Distro[0] == "ubuntu":
|
||||
# We only support what is available in ppa:fex-emu/fex
|
||||
return Distro[1] == "20.04" or \
|
||||
Distro[1] == "21.04" or \
|
||||
Distro[1] == "21.10" or \
|
||||
Distro[1] == "22.04"
|
||||
|
||||
return False
|
||||
|
||||
_ArchVersion = None
|
||||
def ListContainsRequired(Features, RequiredFeatures):
|
||||
for Req in RequiredFeatures:
|
||||
if not Req in Features:
|
||||
return False
|
||||
return True
|
||||
|
||||
def GetCPUFeaturesVersion():
|
||||
global _ArchVersion
|
||||
|
||||
# Also LOR but kernel doesn't expose this
|
||||
v8_1Mandatory = ["atomics", "asimdrdm", "crc32"]
|
||||
v8_2Mandatory = v8_1Mandatory + ["dcpop"]
|
||||
v8_3Mandatory = v8_2Mandatory + ["fcma", "jscvt", "lrcpc", "paca", "pacg"]
|
||||
v8_4Mandatory = v8_3Mandatory + ["asimddp", "flagm", "ilrcpc", "uscat"]
|
||||
|
||||
# fphp asimdhp asimddp
|
||||
|
||||
if _ArchVersion == None:
|
||||
File = open("/proc/cpuinfo", "r")
|
||||
Lines = File.readlines()
|
||||
File.close()
|
||||
|
||||
# Minimum spec is ARMv8.0
|
||||
_ArchVersion = "8.0"
|
||||
for Line in Lines:
|
||||
if "Features" in Line:
|
||||
Features = Line.split(":")[1].strip().split(" ")
|
||||
|
||||
# We don't care beyond 8.4 right now
|
||||
if ListContainsRequired(Features, v8_4Mandatory):
|
||||
_ArchVersion = "8.4"
|
||||
elif ListContainsRequired(Features, v8_3Mandatory):
|
||||
_ArchVersion = "8.3"
|
||||
elif ListContainsRequired(Features, v8_2Mandatory):
|
||||
_ArchVersion = "8.2"
|
||||
elif ListContainsRequired(Features, v8_1Mandatory):
|
||||
_ArchVersion = "8.1"
|
||||
break;
|
||||
|
||||
return _ArchVersion
|
||||
|
||||
_PPAInstalled = None
|
||||
FEXPPA = "http://ppa.launchpad.net/fex-emu/fex/ubuntu"
|
||||
|
||||
def GetPPAStatus():
|
||||
global _PPAInstalled
|
||||
|
||||
if _PPAInstalled == None:
|
||||
_PPAInstalled = False
|
||||
|
||||
CacheResults = subprocess.check_output(['apt-cache', 'policy']).decode("utf-8")
|
||||
|
||||
for Line in CacheResults.split("\n"):
|
||||
if "http" in Line:
|
||||
Line = Line.strip()
|
||||
LineSplit = Line.split(" ")
|
||||
|
||||
# 'status' 'URL' 'series' 'arch' 'type'
|
||||
if LineSplit[1] == FEXPPA:
|
||||
_PPAInstalled = True
|
||||
break
|
||||
|
||||
return _PPAInstalled
|
||||
|
||||
def InstallPPA():
|
||||
print ("Installing PPA: ppa:fex-emu/fex")
|
||||
print ("This bit will ask for your password")
|
||||
|
||||
DidInstall = False
|
||||
try:
|
||||
CmdResult = subprocess.call(["sudo", "add-apt-repository", "-y", "ppa:fex-emu/fex"])
|
||||
DidInstall = CmdResult == 0
|
||||
except KeyboardInterrupt:
|
||||
DidInstall = False
|
||||
pass
|
||||
|
||||
if DidInstall:
|
||||
print("PPA installed")
|
||||
else:
|
||||
print("PPA failed to install")
|
||||
|
||||
return DidInstall
|
||||
|
||||
ARMVersionToPackage = {
|
||||
"8.0": "fex-emu-armv8.0",
|
||||
"8.1": "fex-emu-armv8.0",
|
||||
"8.2": "fex-emu-armv8.2",
|
||||
"8.3": "fex-emu-armv8.2",
|
||||
"8.4": "fex-emu-armv8.4",
|
||||
}
|
||||
|
||||
def GetPackagesToInstall():
|
||||
return [
|
||||
ARMVersionToPackage[GetCPUFeaturesVersion()],
|
||||
"fex-emu-binfmt32",
|
||||
"fex-emu-binfmt64",
|
||||
]
|
||||
|
||||
def UpdatePPA():
|
||||
print ("Updating apt sources")
|
||||
print ("This bit will ask for your password")
|
||||
|
||||
DidUpdate = False
|
||||
try:
|
||||
CmdResult = subprocess.call(["sudo", "apt-get", "update"])
|
||||
DidUpdate = CmdResult == 0
|
||||
except KeyboardInterrupt:
|
||||
DidUpdate = False
|
||||
pass
|
||||
|
||||
if DidUpdate:
|
||||
print("PPA installed")
|
||||
else:
|
||||
print("PPA failed to install")
|
||||
|
||||
return DidUpdate
|
||||
|
||||
def CheckAndInstallPackageUpdates():
|
||||
PackagesToInstall = GetPackagesToInstall()
|
||||
for Package in PackagesToInstall[:]:
|
||||
UpgradableStatus = subprocess.check_output(["apt", "list", "--upgradable", Package]).decode("utf-8")
|
||||
Found = False
|
||||
for Line in UpgradableStatus.split("\n"):
|
||||
# If the package exists to be upgraded then it will appear in this list
|
||||
# We need to check multiple lines
|
||||
# $ apt list --upgradable <Package>
|
||||
# With upgrade available
|
||||
# Listing... Done
|
||||
# <Package>/<Repo> <NewVersion> <arch> [upgradable from: <Installed version>]
|
||||
# Without upgrade available
|
||||
# Listing... Done
|
||||
# <EOF>
|
||||
if Package in Line and "upgradable" in Line:
|
||||
Found = True
|
||||
|
||||
if Found == False:
|
||||
PackagesToInstall.remove(Package)
|
||||
|
||||
if len(PackagesToInstall) > 0:
|
||||
print ("Found updates for packages: {}".format(PackagesToInstall))
|
||||
print ("This bit may ask for your password")
|
||||
|
||||
DidInstall = False
|
||||
try:
|
||||
CmdResult = subprocess.call(["sudo", "apt-get", "-y", "install"] + PackagesToInstall)
|
||||
DidInstall = CmdResult == 0
|
||||
except KeyboardInterrupt:
|
||||
print ("Keyboard interrupt")
|
||||
DidInstall = False
|
||||
pass
|
||||
|
||||
if DidInstall:
|
||||
print("Packages updated")
|
||||
else:
|
||||
print("Packages failed to update")
|
||||
|
||||
return DidInstall
|
||||
|
||||
return True
|
||||
|
||||
def CheckPackageInstallStatus():
|
||||
PackagesToInstall = GetPackagesToInstall()
|
||||
for Package in PackagesToInstall[:]:
|
||||
CmdResult = subprocess.call(["dpkg", "-s", Package], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if CmdResult == 0:
|
||||
PackagesToInstall.remove(Package)
|
||||
|
||||
return PackagesToInstall
|
||||
|
||||
def InstallPackages(Packages):
|
||||
print("Installing packages: {}".format(Packages))
|
||||
|
||||
DidInstall = False
|
||||
try:
|
||||
CmdResult = subprocess.call(["sudo", "apt-get", "-y", "install"] + Packages)
|
||||
DidInstall = CmdResult == 0
|
||||
except KeyboardInterrupt:
|
||||
print ("Keyboard interrupt")
|
||||
DidInstall = False
|
||||
pass
|
||||
|
||||
if DidInstall:
|
||||
print("Packages installed")
|
||||
else:
|
||||
print("Packages failed to install")
|
||||
|
||||
return DidInstall
|
||||
|
||||
_RootFSPath = None
|
||||
def GetRootFSPath():
|
||||
global _RootFSPath
|
||||
|
||||
if _RootFSPath == None:
|
||||
# Follows the same logic as FEXCore::Config::GetDataDirectory()
|
||||
HomeDir = os.getenv("HOME")
|
||||
if HomeDir == None:
|
||||
HomeDir = os.getenv("PWD")
|
||||
if HomeDir == None:
|
||||
HomeDir = "."
|
||||
|
||||
Path = HomeDir
|
||||
DataXDG = os.getenv("XDG_DATA_HOME")
|
||||
if DataXDG != None:
|
||||
Path = DataXDG
|
||||
|
||||
Path = Path + "/.fex-emu"
|
||||
|
||||
DataOverride = os.getenv("FEX_APP_DATA_LOCATION")
|
||||
|
||||
if DataOverride != None:
|
||||
Path = DataOverride
|
||||
|
||||
_RootFSPath = Path + "/RootFS/"
|
||||
|
||||
return _RootFSPath
|
||||
|
||||
def CheckRootFSInstallStatus():
|
||||
# Matches what is available on https://rootfs.fex-emu.org/file/fex-rootfs/RootFS_links.txt
|
||||
UbuntuVersionToRootFS = {
|
||||
"20.04": "Ubuntu_21_04.sqsh",
|
||||
"21.04": "Ubuntu_21_04.sqsh",
|
||||
"21.10": "Ubuntu_21_10.sqsh",
|
||||
"22.04": "Ubuntu_21_10.sqsh",
|
||||
}
|
||||
|
||||
return os.path.exists(GetRootFSPath() + UbuntuVersionToRootFS[GetDistro()[1]])
|
||||
|
||||
def TryInstallRootFS():
|
||||
DidInstall = False
|
||||
try:
|
||||
CmdResult = subprocess.call(["FEXRootFSFetcher"])
|
||||
DidInstall = CmdResult == 0
|
||||
except KeyboardInterrupt:
|
||||
print ("Keyboard interrupt")
|
||||
DidInstall = False
|
||||
pass
|
||||
return DidInstall
|
||||
|
||||
def TryBasicProgramExecution():
|
||||
return subprocess.call(["FEXInterpreter", "/usr/bin/uname", "-a"]) == 0
|
||||
|
||||
def ExitWithStatus(Status):
|
||||
# Remove the cached credentials
|
||||
subprocess.call(["sudo", "-K"])
|
||||
sys.exit(Status)
|
||||
|
||||
def main():
|
||||
# Only run on supported arch
|
||||
if not IsSupportedArch():
|
||||
print ( "{} is not a supported architecture".format(GetArch()))
|
||||
ExitWithStatus(-1)
|
||||
|
||||
if not IsSupportedDistro():
|
||||
Distro = GetDistro()
|
||||
print ( "'{} {}' is not a supported distro".format(Distro[0], Distro[1]))
|
||||
ExitWithStatus(-1)
|
||||
|
||||
if GetDistro()[0] == "ubuntu":
|
||||
print ("Getting PPA status: {}".format(("NotInstalled", "Installed")[GetPPAStatus()]))
|
||||
|
||||
if GetPPAStatus():
|
||||
if not UpdatePPA():
|
||||
print ("apt sources failed to update. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
if not CheckAndInstallPackageUpdates():
|
||||
print ("apt packages failed to update. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
else:
|
||||
if not InstallPPA():
|
||||
print ("PPA failed to install. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
|
||||
Packages = CheckPackageInstallStatus()
|
||||
if len(Packages) > 0:
|
||||
if not InstallPackages(Packages):
|
||||
print ("Failed to install packages. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
|
||||
if not CheckRootFSInstallStatus():
|
||||
print ("RootFS not found. Running FEXRootFSFetcher to get rootfs")
|
||||
if not TryInstallRootFS():
|
||||
print ("Failed to install RootFS. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
|
||||
print ("FEX is now installed. Trying basic program run")
|
||||
if not TryBasicProgramExecution():
|
||||
print ("FEXInterpreter failed to run. Not continuing")
|
||||
ExitWithStatus(-1)
|
||||
|
||||
print ("")
|
||||
print ("===================================================")
|
||||
print ("FEX test run executed. You should be set to run FEX")
|
||||
print ("===================================================")
|
||||
print ("Usage examples:")
|
||||
print ("# steam is a bash script. Wrap with FEXBash")
|
||||
print ("\tFEXBash steam")
|
||||
print ("# Full path execution execution will wrap the application if it exists in the rootfs")
|
||||
print ("\tFEXInterpreter /usr/bin/uname")
|
||||
print ("# Freestanding x86/x86-64 programs can be executed directly. binfmt_misc will redirect to FEX")
|
||||
print ("\t$HOME/PetalCrashOnline.AppImage")
|
||||
print ("# If you need a terminal that emulates everything.")
|
||||
print ("# Run FEXBash without arguments. Double check uname to see if running under FEX")
|
||||
print ("\tFEXBash")
|
||||
|
||||
ExitWithStatus(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
Loading…
Reference in New Issue
Block a user