windvn/mods/live-installer/data/opt/wdvn/installer/main
2023-08-01 17:34:45 +02:00

434 lines
12 KiB
Python
Executable File

#!/usr/bin/python3
import sys
import os
import subprocess
import getpass
import time
import shutil
import random
def nerr(func,*args,**kwargs):
try:
return func(*args,**kwargs)
except Exception:
return
def choiceYn(question,defaultPick = None):
qstring = "\n" + question + " y/n"
if defaultPick != None: qstring = qstring + " [" +defaultPick+ "]"
qstring = qstring + ": "
while True:
chinput = input(qstring)
if chinput == "" and defaultPick != None:
chinput = defaultPick
chinput = chinput.lower()
if chinput == "y": return True
if chinput == "n": return False
def choice(question,options,defaultPick = None,direction = 1):
qstring = "\n"
for option in options:
qstring = qstring + "- " + option + "\n"
qstring = qstring + question
if defaultPick != None: qstring = qstring + " [" +defaultPick+ "]"
qstring = qstring + ": "
while True:
chinput = input(qstring)
if chinput == "" and defaultPick != None:
chinput = defaultPick
if chinput in options: return chinput
def choiceInput(question,defaultPick = None):
qstring = "\n" + question
if defaultPick != None: qstring = qstring + " [" +defaultPick+ "]"
qstring = qstring + ": "
while True:
chinput = input(qstring)
if chinput == "":
if defaultPick == None: continue
return defaultPick
return chinput
def choicePass(question):
while True:
print("")
passw = getpass.getpass(question+ ": ")
passwConfirm = getpass.getpass(question+ " (repeat): ")
if passw == passwConfirm: return passw
print("Passwords don't match, try again.")
def checkProc(proc):
rtn = proc.wait()
if rtn != 0: raise Exception("Process returned error: " +str(rtn))
def call(*args,**kwargs):
proc = subprocess.Popen(*args,**kwargs)
checkProc(proc)
return
def callString(*args,**kwargs):
proc = subprocess.Popen(*args,**kwargs,stdout=subprocess.PIPE)
output = proc.stdout.read().decode("utf-8").strip("\n")
checkProc(proc)
return output
def callList(*args,**kwargs):
proc = subprocess.Popen(*args,**kwargs,stdout=subprocess.PIPE)
output = proc.stdout.read().decode("utf-8").strip("\n").split("\n")
checkProc(proc)
return output
def getDisks():
return callList(["lsblk","-ndo","PATH"])
def getPartitions():
disks = getDisks()
partitions = callList(["lsblk","-no","PATH"])
index = 0
length = len(partitions)
while index < length:
if partitions[index] in disks:
del partitions[index]
length -= 1
continue
index += 1
return partitions
def getDiskType(disk):
return callString(["blkid","-o","value","-s","PTTYPE",disk])
def getPartitionType(partition):
return callString(["blkid","-o","value","-s","TYPE",partition])
def getPartitionUUID(partition):
return callString(["blkid","-o","value","-s","UUID",partition])
def getDiskTable(disk):
if getDiskType(disk) == "gpt":
results = callList(["fdisk","-x",disk,"-o","Device,Type-UUID"])
else:
results = callList(["fdisk","-x",disk,"-o","Device,Id"])
length = len(results)
index = length - 1
while index >= 0:
if results[index] == "": break
index -= 1
if index < 0: return {}
results = results[index + 2:]
length = len(results)
index = 0
while index < length:
while results[index].replace(" "," ") != results[index]:
results[index] = results[index].replace(" "," ")
results[index] = results[index].split(" ")
index += 1
rtn = {}
for part in results:
rtn[part[0]] = part[1]
return rtn
def getGptReport(disk):
rtn = {}
rtn["isGPT"] = False
rtn["espPart"] = None
rtn["espFormatted"] = False
rtn["biosPart"] = None
if getDiskType(disk) != "gpt": return rtn
rtn["isGPT"] = True
partitions = callList(["lsblk",disk,"-no","PATH"])
del partitions[0]
diskTable = getDiskTable(disk)
for partition in partitions:
if rtn["espPart"] == None:
if diskTable[partition] == "C12A7328-F81F-11D2-BA4B-00A0C93EC93B":
rtn["espPart"] = partition
rtn["espFormatted"] = (getPartitionType(partition) == "vfat")
if rtn["biosPart"] == None:
if diskTable[partition] == "21686148-6449-6E6F-744E-656564454649":
rtn["biosPart"] = partition
return rtn
def setKbLayout(kb):
call(["loadkeys",kb])
def resetKbLayout():
call(["service","console-setup.sh","restart"])
call(["udevadm","trigger","--subsystem-match=input","--action=change"])
call(["service","keyboard-setup.sh","restart"])
def ipth(path = ""):
return "/media/install/" +path
def mkdirp(path):
if not os.path.isdir(path): os.makedirs(path)
def chroot(path,cmd,*args,**kwargs):
dirs = ["dev","dev/pts","sys","proc"]
try:
for dir in dirs:
call(["mount","-o","bind","/" +dir,path + "/" +dir])
call(["chroot",path] + cmd,*args,**kwargs)
for dir in reversed(dirs):
call(["umount","-l",path + "/" +dir])
except Exception as e:
for dir in reversed(dirs):
try:
call(["umount","-l",path + "/" +dir])
except:
pass
raise e
def randhex(length):
chars = "0123456789ABCDEF"
output = ""
cLength = 0
while cLength < length:
output = output + chars[random.randrange(0,15)]
cLength += 1
return output
def main():
while choiceYn("Partition disk?"):
try:
disk = choice("Disk",getDisks())
call(["fdisk",disk])
except KeyboardInterrupt:
pass
installPartition = choice("OS Partition",getPartitions())
formatPartition = choiceYn("Format partition?","y")
if formatPartition:
fileSystems = []
for path in os.environ["PATH"].split(":"):
if not os.path.isdir(path): continue
for root,dirs,files in os.walk(path):
for file in files:
ffile = os.path.join(root,file)
lfile = ffile.replace(path + os.path.sep,"",1)
if lfile.startswith("mkfs."): fileSystems.append(lfile.replace("mkfs.","",1))
fileSystems.sort()
defaultChoice = None
if "ext4" in fileSystems: defaultChoice = "ext4"
formatPartition = choice("File system",fileSystems,defaultChoice)
else:
formatPartition = None
if formatPartition != None:
encrypt = choiceYn("Encrypt data?")
if encrypt:
encryptionPasswords = []
setKbLayout("us")
try:
encryptionPasswords.append(choicePass("Encryption password"))
except KeyboardInterrupt:
encrypt = False
resetKbLayout()
if encrypt:
while choiceYn("Add another password?"):
setKbLayout("us")
try:
encryptionPasswords.append(choicePass("Encryption password"))
except KeyboardInterrupt:
resetKbLayout()
break
resetKbLayout()
else:
encrypt = False
installGRUB = choiceYn("Install GRUB?","y")
if installGRUB:
try:
grubDisk = choice("GRUB disk",getDisks())
except KeyboardInterrupt:
installGRUB = False
if installGRUB:
gptInfo = getGptReport(grubDisk)
if gptInfo["isGPT"] == True:
if gptInfo["espPart"] == None:
if gptInfo["biosPart"] == None:
if choiceYn("WARNING: No ESP nor BIOS partition found on GPT disk " +grubDisk+ ", GRUB will not be installed. Continue?","n") == False:
sys.exit(1)
else:
installGRUB = False
else:
if choiceYn("WARNING: No ESP partition found. The disk might only boot on BIOS (legacy). Continue?","n") == False:
sys.exit(1)
else:
formatEsp = False
if gptInfo["espFormatted"] == False:
formatEsp = choiceYn("The disk's ESP partition (" +gptInfo["espPart"]+ ") does not seem to be formatted correctly. Format it?")
defaultChoice = "i386"
if callString(["uname","-m"]) == "x86_64": defaultChoice = "x86_64"
grubVersion = choice("Which version of EFI GRUB should be installed? Note that 'both' is not always compatible.",["i386","x86_64","both"],defaultChoice)
print("")
print("The rest of this setup will continue without your input ...")
time.sleep(5)
print("")
if formatPartition != None:
print("> Wiping partition signature ...")
call(["wipefs","-a",installPartition])
if encrypt:
print("> Encrypting partition ...")
call(["apt-get","-y","install","cryptsetup"],stdout=subprocess.DEVNULL)
cryptPartition = installPartition
mainPass = encryptionPasswords.pop(0)
proc = subprocess.Popen(["cryptsetup","luksFormat","--type","luks1",cryptPartition],stdin=subprocess.PIPE)
proc.stdin.write((mainPass + "\n").encode("utf-8"))
proc.stdin.flush()
checkProc(proc)
for passw in encryptionPasswords:
print("Adding another password ...")
proc = subprocess.Popen(["cryptsetup","luksAddKey",cryptPartition],stdin=subprocess.PIPE)
proc.stdin.write((mainPass + "\n" + passw + "\n").encode("utf-8"))
proc.stdin.flush()
checkProc(proc)
print("Creating block device ...")
proc = subprocess.Popen(["cryptsetup","luksOpen",cryptPartition,"system"],stdin=subprocess.PIPE)
proc.stdin.write((mainPass + "\n").encode("utf-8"))
proc.stdin.flush()
checkProc(proc)
installPartition = "/dev/mapper/system"
if formatPartition != None:
print("> Formatting partition ...")
call(["mkfs." +formatPartition,installPartition])
print("> Grabbing partition UUIDs ...")
installPartitionUUID = getPartitionUUID(installPartition)
if encrypt: cryptPartitionUUID = getPartitionUUID(cryptPartition)
print("> Mounting main partition ...")
mkdirp(ipth())
call(["mount",installPartition,ipth()])
print("> Adding files")
print("Creating swap ...")
hasSwap = False
try:
if formatPartition == "btrfs":
call(["truncate","-s","0",ipth("swap")])
call(["chattr","+C",ipth("swap")])
call(["fallocate","-l","512M",ipth("swap")])
else:
call(["dd","if=/dev/zero","of=" +ipth("swap"),"bs=1M","count=512","status=progress"])
call(["chmod","0600",ipth("swap")])
call(["mkswap",ipth("swap")])
call(["swapon",ipth("swap")])
hasSwap = True
except Exception as e:
print(e)
print("WARNING: Creating swap failed, skipping.")
try:
os.remove(ipth("swap"))
except Exception:
pass
print("Unpacking OS ...")
call(["unsquashfs","-f","-d",ipth(),"/lib/live/mount/medium/live/filesystem.squashfs"])
print("Setting hostname ...")
hostname = randhex(8)
fh = open(ipth("etc/hostname"),"w")
fh.write(hostname + "\n")
fh.close()
if encrypt:
print("Writing crypto grub config ...")
fh = open(ipth("etc/default/grub.d/cryptodisk.cfg"),"w")
fh.write("GRUB_ENABLE_CRYPTODISK=y\n")
fh.write('GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX cryptdevice=UUID=' +cryptPartitionUUID+ ' root=UUID=' +installPartitionUUID+ '"\n')
fh.close()
print("Writing crypttab ...")
fh = open(ipth("etc/crypttab"),"w")
fh.write('system UUID=' +cryptPartitionUUID+ ' none luks\n')
fh.close()
print("Writing fstab ...")
fh = open(ipth("etc/fstab"),"w")
fh.write('UUID=' +installPartitionUUID+ ' / ' +getPartitionType(installPartition)+ ' defaults 0 1\n')
if hasSwap: fh.write('/swap none swap sw 0 0\n')
fh.close()
print("Copying keyboard settings ...")
shutil.copyfile("/etc/default/keyboard",ipth("etc/default/keyboard"))
print("> Removing live-specific packages ...")
chroot(ipth(),["apt-get","-y","--purge","remove","live-boot","live-boot-initramfs-tools"])
chroot(ipth(),["apt-get","-y","--purge","autoremove"])
os.remove(ipth("bin/login"))
os.rename(ipth("bin/login.oobe"),ipth("bin/login"))
if encrypt:
print("> Installing encryption-specific packages ...")
chroot(ipth(),["apt-get","-y","install","cryptsetup-initramfs"])
else:
print("> Updating initramfs ...")
chroot(ipth(),["update-initramfs","-u"])
if installGRUB:
print("> Installing GRUB")
if gptInfo["isGPT"]:
if gptInfo["espPart"] != None:
if formatEsp:
print("Formatting ESP ...")
call(["wipefs","-a",gptInfo["espPart"]])
call(["mkfs.vfat","-F","32",gptInfo["espPart"]])
print("Mounting ESP ...")
mkdirp(ipth("boot/efi"))
call(["mount",gptInfo["espPart"],ipth("boot/efi")])
print("Installing GRUB (EFI) ...")
grubTargets = []
if grubVersion in ["i386","both"]: grubTargets.append("i386-efi")
if grubVersion in ["x86_64","both"]: grubTargets.append("x86_64-efi")
for target in grubTargets:
chroot(ipth(),["grub-install","--target=" +target,"--uefi-secure-boot","--efi-directory=/boot/efi","--boot-directory=/boot","--force-extra-removable","--no-nvram",grubDisk])
print("Unmounting ESP ...")
call(["umount","-l",ipth("boot/efi")])
os.rmdir(ipth("boot/efi"))
if gptInfo["biosPart"] != None:
print("Installing GRUB (BIOS) ...")
chroot(ipth(),["grub-install","--target=i386-pc","--boot-directory=/boot",grubDisk])
else:
print("Installing GRUB (BIOS) ...")
chroot(ipth(),["grub-install","--target=i386-pc","--boot-directory=/boot",grubDisk])
print("Configuring GRUB ...")
chroot(ipth(),["update-grub"])
print("> Unmounting ...")
if hasSwap: call(["swapoff",ipth("swap")])
call(["umount","-l",ipth()])
os.rmdir(ipth())
if encrypt: call(["cryptsetup","luksClose","system"])
print("Success. Press ENTER to quit setup.")
input()
main()