From 47843831537e60e93110cc2698a0908aafc450f1 Mon Sep 17 00:00:00 2001 From: Fierelier Date: Sun, 20 Feb 2022 18:36:34 +0100 Subject: [PATCH] First commit --- LICENSE | 2 +- README.md | 6 +- mdevuan.py | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++ oobe | 108 +++++++++++++++++++++++++++ setup | 18 +++++ 5 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 mdevuan.py create mode 100644 oobe create mode 100644 setup diff --git a/LICENSE b/LICENSE index 2071b23..9173a77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2022 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 54161f8..482e573 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # mdevuan -a devuan image maker \ No newline at end of file +A Devuan image maker + +Commands required: +Syntax: `python3 mdevuan.py ` +Example: `python3 mdevuan.py devuan.img /tmp/devuan` \ No newline at end of file diff --git a/mdevuan.py b/mdevuan.py new file mode 100644 index 0000000..31e7756 --- /dev/null +++ b/mdevuan.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +import sys +import os +import shutil +diskSize = "1536M" +swapSize = "256M" +outputImage = os.path.abspath(sys.argv[1]) +mountPoint = os.path.abspath(sys.argv[2].rstrip("/")) + +mountPoints = [ # List of mount points to dismount when quitting + os.path.join(mountPoint,"proc"), + os.path.join(mountPoint,"sys"), + os.path.join(mountPoint,"dev"), + mountPoint +] + +preReqs = [ # List of required commands to check for + "fallocate", + "losetup", + "fdisk", + "losetup", + "mkswap", + "mkfs.ext4", + "mount", + "blkid", + "debootstrap", + "chmod", + "chroot", + "umount" +] + +loopName = False + +class processError(Exception): pass + +import subprocess +def procWait(proc): + rtn = proc.wait() + if rtn != 0: raise processError + +def call(*args,**kwargs): + proc = subprocess.Popen(*args,**kwargs) + procWait(proc) + +def callo(*args,**kwargs): + data = b"" + proc = subprocess.Popen(*args,**kwargs,stdout=subprocess.PIPE) + while True: + b = proc.stdout.read() + if b == b"": break + data += b + procWait(proc) + return data.decode("utf-8") + +def cleanUp(): + global diskSize + global swapSize + global outputImage + global mountPoint + global loopName + + for mp in mountPoints: + print("Dismounting " +mp+ " ...") + try: + call(["umount",mp]) + except: pass + + try: + os.rmdir(mountPoint) + except Exception as e: + print(e) + + print("Detaching loop device ...") + try: + call(["losetup","-d",loopName]) + except: + pass + +def main(): + global diskSize + global swapSize + global outputImage + global mountPoint + global loopName + + print("Checking prerequisites ...") + call(["sh","-c","command"]) + for req in preReqs: + try: + call(["sh","-c","command -v " + req],stdout=subprocess.DEVNULL) + except: + print("Missing command: " +req) + raise + + print("Creating disk image ...") + if os.path.isfile(outputImage): os.remove(outputImage) + call(["fallocate","-l",diskSize,outputImage]) + + print("Creating loop device ...") + call(["losetup","-fP",outputImage]) + + print("Setting up loop device ...") + table = callo(["losetup","--list"]).replace("\r","").split("\n") + length = len(table) + index = 0 + while index < length: + while " " in table[index]: + table[index] = table[index].replace(" "," ") + if table[index] == "": + del table[index] + length -= 1 + continue + table[index] = table[index].split(" ") + index += 1 + nameIndex = table[0].index("NAME") + backfileIndex = table[0].index("BACK-FILE") + + index = 1 + while index < length: + if table[index][backfileIndex] == outputImage: + loopName = table[index][nameIndex] + break + index += 1 + + if loopName == False: + print("Loop device not found.") + raise processError + + print("Partitioning ...") + proc = subprocess.Popen(["fdisk",loopName],stdin=subprocess.PIPE) + proc.communicate(("""\ +n +e + + ++""" +swapSize+ """ +t +swap +n +p + + + +t + +linux +w + +\ +""").encode("utf-8")) + procWait(proc) + + print("Making swap ...") + call(["mkswap",loopName + "p1"]) + + print("Formatting / (ext4) ...") + call(["mkfs.ext4",loopName+ "p2"]) + + print("Mounting / ...") + if not os.path.isdir(mountPoint): os.makedirs(mountPoint) + call(["mount",loopName + "p2",mountPoint]) + + print("Installing Devuan Chimaera ...") + tries = 0 + while tries < 10: + try: + call(["debootstrap","--arch=i386","chimaera",mountPoint,"http://deb.devuan.org/merged"]) + break + except: + tries += 1 + continue + + if tries >= 10: + raise processError + + print("Setting up chroot ...") + call(["mount","-o","bind","/dev",os.path.join(mountPoint,"dev")]) + call(["mount","-o","bind","/sys",os.path.join(mountPoint,"sys")]) + call(["mount","-t","proc","/proc",os.path.join(mountPoint,"proc")]) + shutil.copyfile("/proc/mounts",os.path.join(mountPoint,"etc","mtab")) + with open("setup","r",encoding="utf-8") as fhIn: + with open(os.path.join(mountPoint,"setup"),"w",encoding="utf-8") as fhOut: + for line in fhIn: + fhOut.write(line.replace("$LOOPNAME",loopName)) + call(["chmod","+x",os.path.join(mountPoint,"setup")]) + + print("Adding devices to fstab ...") + idSwap = callo(["blkid","-o","value","-s","UUID",loopName+ "p1"]).strip(" \t\r\n") + idRoot = callo(["blkid","-o","value","-s","UUID",loopName+ "p2"]).strip(" \t\r\n") + with open(os.path.join(mountPoint,"etc","fstab"),"a+",encoding="utf-8") as fh: + fh.write("\nUUID=" +idSwap+ " none swap sw 0 0") + fh.write("\nUUID=" +idRoot+ " / ext4 errors=remount-ro 0 1") + + print("Running setup ...") + call(["chroot",mountPoint,"/setup"]) + + print("Injecting OOBE ...") + os.rename(os.path.join(mountPoint,"bin","login"),os.path.join(mountPoint,"bin","login.bak")) + shutil.copyfile("oobe",os.path.join(mountPoint,"bin","login")) + call(["chmod","+x",os.path.join(mountPoint,"bin","login")]) + + cleanUp() + +try: + main() +except: + print("\n --- AN ERROR OCCURED, CLEANING UP ... --- ") + cleanUp() + print("") + raise \ No newline at end of file diff --git a/oobe b/oobe new file mode 100644 index 0000000..4286810 --- /dev/null +++ b/oobe @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# This script is ran when booting into the OS, and the setup isn't finished +import os +import traceback +import subprocess +debug = False +os.environ["PATH"] = "/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" + +def procWait(proc): + rtn = proc.wait() + if debug: print(rtn) + if rtn != 0: raise processError + +def call(*args,**kwargs): + if debug: print(str(args[0])) + proc = subprocess.Popen(*args,**kwargs) + procWait(proc) + +def mchoice(choices): + while True: + choice = input("Choice: ").lower() + if not choice in choices: continue + return choice + +def main(): + while True: + print("Welcome to Devuan Linux.") + print("1: Finish installation") + print("2: Clone to other device") + print("3: Open bash") + print("4: Shut down") + choice = mchoice(["1","2","3","4"]) + if choice == "1": + call(["apt","install","keyboard-configuration","console-setup","locales","tzdata","--yes"]) + #call(["dpkg-reconfigure","keyboard-configuration"]) + #call(["setupcon"]) + #call(["dpkg-reconfigure","console-setup"]) + #call(["setupcon"]) + call(["dpkg-reconfigure","locales"]) + call(["dpkg-reconfigure","tzdata"]) + + print("") + hostname = input("Hostname: ") + with open("/etc/hosts","w",encoding="utf-8") as fh: + fh.write("""\ +127.0.0.1 localhost +127.0.1.1 $HOSTNAME + +# The following lines are desirable for IPv6 capable hosts +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters\ +""".replace("$HOSTNAME",hostname) + ) + with open("/etc/hostname","w",encoding="utf-8") as fh: + fh.write(hostname) + os.environ["HOSTNAME"] = hostname + + print("") + print("Configure your network now, using nmtui? (y/n)") + if mchoice(["y","n"]) == "y": call(["nmtui"]) + + print("") + call(["groupadd","-f","sudo"]) + username = False + while True: + username = input("First user (Administrator): ").lower().strip(" ") + if username == "root": continue + try: + call(["id","-u",username]) + except: + try: + call(["adduser",username]) + except: + try: + call(["deluser","--remove-home",username]) + except: + continue + + try: + call(["usermod","-a","-G","sudo",username]) + except: + print("Warning: Couldn't add user to sudo group!") + break + + print("") + print("Booting into new environment ...") + os.remove("/bin/login") + os.rename("/bin/login.bak","/bin/login") + return + + if choice == "2": + print("Not implemented.") + continue + + if choice == "3": + call(["bash"]) + return + + if choice == "4": + call(["poweroff"]) + return + +try: + main() +except: + print(traceback.format_exc()) + input("Press RETURN to quit.") diff --git a/setup b/setup new file mode 100644 index 0000000..9957d8a --- /dev/null +++ b/setup @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# This script is ran within in the chroot +set -e +echo 'APT::Acquire::Retries "10";' > /etc/apt/apt.conf.d/80-retry +apt autoclean --yes +apt install linux-image-686 grub2 sudo network-manager python3 --yes +apt autoclean --yes +apt install --download-only console-setup keyboard-configuration locales tzdata --yes +rm /etc/apt/apt.conf.d/80-retry +grub-install --boot-directory="/boot" --modules=part_msdos "$LOOPNAME" +update-grub +update-initramfs -u -k all +echo "" +echo "Do any manual changes now, enter 'exit' to finish setup." +bash +if [[ -f "$HOME/.bash_history" ]]; then + rm "$HOME/.bash_history" +fi