├── ArchInstaller ├── LICENSE.md ├── README.md ├── config.yaml └── configs └── simple.sh /ArchInstaller: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import concurrent.futures, yaml, requests, time, sys, os 3 | from subprocess import run, PIPE 4 | 5 | class TUI: 6 | OKPURPLE = '\033[95m' 7 | OKBLUE = '\033[94m' 8 | OKGREEN = '\033[92m' 9 | FAIL = '\033[91m' 10 | ENDC = '\033[0m' 11 | ANIMATION= [ 12 | "[ ]", 13 | "[= ]", 14 | "[== ]", 15 | "[=== ]", 16 | "[==== ]", 17 | "[===== ]", 18 | "[====== ]", 19 | "[======= ]", 20 | "[========]", 21 | "[ =======]", 22 | "[ ======]", 23 | "[ =====]", 24 | "[ ====]", 25 | "[ ===]", 26 | "[ ==]", 27 | "[ =]", 28 | "[ ]", 29 | "[ ]" 30 | ] 31 | 32 | 33 | 34 | 35 | class ArchInstaller: 36 | def __init__(self) -> None: 37 | try: 38 | if len(sys.argv) != 1: 39 | with open("./config.yaml", mode="w") as f: 40 | response = requests.get(sys.argv[1]) 41 | f.write(response.text) 42 | with open("./config.yaml") as data: 43 | config = yaml.load(data, Loader=yaml.FullLoader) 44 | if os.environ.get("USER") == "root": 45 | self.config = config["root"] 46 | else: 47 | self.config = config["user"] 48 | except: 49 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} There is a problem with the config.yaml") 50 | exit() 51 | try: 52 | requests.get("https://google.com/") 53 | except: 54 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Your connection is not stable") 55 | exit() 56 | 57 | def ascii(self) -> None: 58 | ASCII = """ ___ ____ ________ _______ ________________ __ __ __________ 59 | / | / __ \/ ____/ / / / _/ | / / ___/_ __/ | / / / / / ____/ __ \\ 60 | / /| | / /_/ / / / /_/ // // |/ /\__ \ / / / /| | / / / / / __/ / /_/ / 61 | / ___ |/ _, _/ /___/ __ // // /| /___/ // / / ___ |/ /___/ /___/ /___/ _, _/ 62 | /_/ |_/_/ |_|\____/_/ /_/___/_/ |_//____//_/ /_/ |_/_____/_____/_____/_/ |_| 63 | """ 64 | print(TUI.OKGREEN + ASCII + TUI.ENDC) 65 | 66 | def exec(self,command: str, description: str|None = None , chroot:bool = False): 67 | if chroot: 68 | sh = 'arch-chroot /mnt ' + command 69 | else: 70 | sh = command 71 | with concurrent.futures.ThreadPoolExecutor() as executor: 72 | future = executor.submit(lambda: run(sh, stdout=PIPE, stderr=PIPE, shell=True, encoding='utf-8')) 73 | if description != None: 74 | i = 0 75 | while future.running(): 76 | print(description + ' ' + TUI.OKPURPLE + TUI.ANIMATION[i % len(TUI.ANIMATION)] + TUI.ENDC, end='\r') 77 | time.sleep(.1) 78 | i += 1 79 | if future.result().returncode == 0: 80 | print(description + f' {TUI.OKGREEN}✔{TUI.ENDC}' + " ", end='\n') 81 | else: 82 | print(description + f' {TUI.FAIL}✘{TUI.ENDC}' + " ", end='\n') 83 | 84 | def setNtp(self) -> None: 85 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} set ntp to {self.config['settings']['ntp']}" 86 | command = f"timedatectl set-ntp {self.config['settings']['ntp']}" 87 | self.exec(command,description) 88 | 89 | def driveConfigurator(self) -> None: 90 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Config Hard Drive") # with fdisk 91 | 92 | drive = self.config["settings"]["drive"] 93 | 94 | # Format Hard Drive 95 | if drive["erase"]: 96 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} Formatting Drive : {drive['blk']}" 97 | command = f"echo 'g\nw\n' | fdisk {drive['blk']}" 98 | self.exec(command, description) 99 | 100 | # Making Partitions 101 | print(f" {TUI.OKBLUE}-->{TUI.ENDC} Create Partitions") 102 | for partition in drive["partitions"]: 103 | size = partition["size"] 104 | number = partition["number"] 105 | label = partition["label"] 106 | filesystem = partition["filesystem"] 107 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} Partition : {partition['label']}" 108 | if partition["number"] == 1: 109 | command = f"echo 'n\n{number}\n\n{size}\nt\n{filesystem}\nw\n' | fdisk {drive['blk']}" 110 | else: 111 | command = f"echo 'n\n{number}\n\n{size}\nt\n{number}\n{filesystem}\nw\n' | fdisk {drive['blk']}" 112 | self.exec(command, description) 113 | 114 | # Make FileSystem & add Label to Partition 115 | description = f" {TUI.OKPURPLE}-->{TUI.ENDC} Make filesystem : {filesystem}" 116 | match filesystem: 117 | case "uefi": 118 | self.exec(f"mkfs.fat -F32 {drive['blk']}{number}") 119 | command = f"fatlabel {drive['blk']}{number} {label}" 120 | case "linux": 121 | command = f"echo 'y\n' | mkfs.ext4 -L {label} {drive['blk']}{number}" 122 | case "swap": 123 | command = f"mkswap {drive['blk']}{number}" 124 | self.exec(command,description) 125 | 126 | def mount(self): 127 | 128 | drive = self.config["settings"]["drive"] 129 | # Mount Partitions 130 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Mount Partitions ") 131 | 132 | # root -> /mnt 133 | while not os.path.exists("/dev/disk/by-label/ROOT"): 134 | time.sleep(1) 135 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} ROOT" 136 | command = f"mount /dev/disk/by-label/ROOT /mnt" 137 | self.exec(command,description) 138 | 139 | self.exec("mkdir -p /mnt/{home,boot/efi}") 140 | 141 | for partition in drive["partitions"]: 142 | 143 | # boot -> /mnt/boot/efi 144 | if partition["label"] == "BOOT": 145 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} BOOT" 146 | command = f"mount {drive['blk']}{partition['number']} /mnt/boot/efi" 147 | self.exec(command,description) 148 | 149 | # Mount Home Partition if exist 150 | # home -> /mnt/home 151 | if partition["label"] == "HOME": 152 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} HOME" 153 | command = f"mount {drive['blk']}{partition['number']} /mnt/home" 154 | self.exec(command,description) 155 | 156 | # enable swap if exists 157 | if partition["label"] == "SWAP": 158 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} Enable SWAP Partition " 159 | command = f"swapon {drive['blk']}{partition['number']}" 160 | self.exec(command,description) 161 | 162 | def installPackages(self) -> None: 163 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Install Packages") 164 | if os.environ.get('USER') == "root": 165 | for package in self.config["packages"]: 166 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} {package}" 167 | command = f"pacstrap /mnt {package}" 168 | self.exec(command,description) 169 | else: 170 | for package in self.config["packages"]: 171 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} {package}" 172 | command = f"{self.config['aurhelper']} -S --skipreview --noconfirm {package}" 173 | self.exec(command,description) 174 | 175 | def genFstab(self) -> None: 176 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Generating FSTAB {TUI.OKGREEN}✔{TUI.ENDC}") 177 | with open('/mnt/etc/fstab', 'w') as fstab: 178 | run(['genfstab', '-U', '/mnt'], stdout=fstab) 179 | 180 | def setLocale(self) -> None: 181 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Setting Locale") 182 | with open("/mnt/etc/locale.gen", "a") as f: 183 | for locale in self.config["settings"]["locale"]: 184 | print(f" {TUI.OKBLUE}-->{TUI.ENDC} Generating {locale} {TUI.OKGREEN}✔{TUI.ENDC}") 185 | f.write(f"{locale}\n") 186 | self.exec("locale-gen", chroot=True) 187 | print(f" {TUI.OKBLUE}-->{TUI.ENDC} set LANG env to {self.config['settings']['lang']} {TUI.OKGREEN}✔{TUI.ENDC}") 188 | with open("/mnt/etc/locale.conf", "w") as f: 189 | f.write(f"LANG={self.config['settings']['lang']}\n") 190 | 191 | def setTimeZone(self) -> None: 192 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} setting TimeZone to {self.config['settings']['timezone']}" 193 | command = f"ln -fs /usr/share/zoneinfo/{self.config['settings']['timezone']} /etc/localtime" 194 | self.exec(command, description, True) 195 | self.exec("hwclock --systohc --utc", chroot=True) 196 | 197 | def installBootLoader(self): 198 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Generating BootLoader" 199 | command = f"grub-install --recheck" 200 | self.exec(command,description,True) 201 | self.exec(f"grub-mkconfig -o /boot/grub/grub.cfg", chroot=True) 202 | 203 | def setHostName(self) -> None: 204 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} set hostname to {self.config['settings']['hostname']} {TUI.OKGREEN}✔{TUI.ENDC}") 205 | with open("/mnt/etc/hostname", "w") as f: 206 | f.write(self.config["settings"]["hostname"]) 207 | 208 | def setHosts(self) -> None: 209 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} set hosts {TUI.OKGREEN}✔{TUI.ENDC}") 210 | with open("/mnt/etc/hosts", "a") as f: 211 | f.write( 212 | f"127.0.0.1 localhost.localdomain localhost\n::1 localhost.localdomain localhost\n127.0.1.1 {self.config['settings']['hostname']}.localdomain {self.config['settings']['hostname']}" 213 | ) 214 | 215 | def enableServices(self): 216 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Enabling Services") 217 | for service in self.config["services"]: 218 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} {service}" 219 | if os.environ.get('USER') == "root": 220 | command = f"systemctl enable {service}" 221 | chroot = True 222 | else: 223 | command = f"sudo systemctl enable {service}" 224 | chroot = False 225 | self.exec(command,description,chroot) 226 | 227 | def setRootPassword(self): 228 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Set Root Password" 229 | command = f"echo -n 'root:password' | chpasswd" 230 | self.exec(command, description, True) 231 | 232 | def createUser(self): 233 | username = self.config["username"] 234 | shell = self.config["settings"]["shell"] 235 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Creating User ({username})" 236 | command = f"useradd -m -g users -G wheel -s {shell} {username}" 237 | self.exec(command, description, True) 238 | self.exec(f"echo '{username}:password' | chpasswd", chroot=True) 239 | 240 | def sudoersConf(self, conf): 241 | if conf == "UA": 242 | self.exec("chmod 644 /etc/sudoers", chroot=True) 243 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Config sudoers file" 244 | command = r"sed -i 's/^#\s*\(%wheel\s*ALL=(ALL:ALL)\s*ALL\)/\1/' /mnt/etc/sudoers" 245 | self.exec(command,description) 246 | elif conf == "UN": 247 | self.exec(r"sudo sed -i 's/^#\s*\(%wheel\s*ALL=(ALL:ALL)\s*NOPASSWD:\s*ALL\)/\1/' /etc/sudoers") 248 | elif conf == "CN": 249 | self.exec(r"sudo sed -i 's/^\s*\(%wheel\s*ALL=(ALL:ALL)\s*NOPASSWD:\s*ALL\)/# \1/' /etc/sudoers") 250 | 251 | def copyInstaller(self): 252 | self.exec(f"mkdir -p /mnt/home/{self.config['username']}/archinstaller") 253 | self.exec(f"cp -r . /mnt/home/{self.config['username']}/archinstaller") 254 | self.exec(f"chown -R {self.config['username']}:wheel /mnt/home/{self.config['username']}/archinstaller", chroot=True) 255 | 256 | def installDependencies(self): 257 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Install dependencies" 258 | depends = ['git', 'python-yaml', 'python-requests'] 259 | command = "pacstrap /mnt " + " ".join(depends) 260 | self.exec(command, description) 261 | 262 | def umount(self): 263 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Umount Partitions" 264 | command = "umount -R /mnt" 265 | self.exec(command, description) 266 | 267 | def reboot(self): 268 | print("====================================\nInstallation completed!!") 269 | input("Press enter to reboot") 270 | self.exec("reboot") 271 | 272 | def installAURHelper(self): 273 | self.exec(f"cd /tmp && git clone http://aur.archlinux.org/{self.config['aurhelper']}-bin.git") 274 | description = f"{TUI.OKGREEN}==>{TUI.ENDC} Install AUR helper" 275 | command = f"cd /tmp/{self.config['aurhelper']}-bin && makepkg -si --noconfirm" 276 | self.exec(command,description) 277 | 278 | def runUserScripts(self): 279 | print(f"{TUI.OKGREEN}==>{TUI.ENDC} Execute Your Scripts") 280 | for script in self.config["scripts"]: 281 | description = f" {TUI.OKBLUE}-->{TUI.ENDC} {script['name']}" 282 | if 'shell' in script: 283 | command = script['shell'] 284 | elif 'script' in script: 285 | self.exec(f"chmod +x {script['script']}") 286 | command = script["script"] 287 | else: 288 | description += " : Yaml file is incorret" 289 | command = "fakesudo :)" 290 | self.exec(command, description) 291 | 292 | def run(self): 293 | self.ascii() 294 | if os.environ.get('USER') == "root": 295 | self.setNtp() 296 | self.driveConfigurator() 297 | self.mount() 298 | self.installPackages() 299 | self.genFstab() 300 | self.setLocale() 301 | self.setTimeZone() 302 | self.installBootLoader() 303 | self.setHostName() 304 | self.setHosts() 305 | self.enableServices() 306 | self.setRootPassword() 307 | self.createUser() 308 | self.sudoersConf("UA") 309 | self.copyInstaller() 310 | self.installDependencies() 311 | self.umount() 312 | self.reboot() 313 | else: 314 | self.sudoersConf("UN") 315 | self.installAURHelper() 316 | self.installPackages() 317 | self.runUserScripts() 318 | self.enableServices() 319 | self.sudoersConf("CN") 320 | 321 | ArchInstaller().run() 322 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Amirhossein Alibabaei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Arch Linux Installer 4 | 5 | [![Contributors][contributors-shield]][contributors-url] 6 | [![Forks][forks-shield]][forks-url] 7 | [![Stargazers][stars-shield]][stars-url] 8 | [![Issues][issues-shield]][issues-url] 9 | [![MIT License][license-shield]][license-url] 10 | [![Telegram][telegram-shield]][telegram-url] 11 | 12 | 13 | 14 | ## Introduction 15 | 16 | Install archlinux and config based on a yaml configuration 17 | 18 | ## Getting Started 19 | 20 | ### Prerequrments 21 | 22 | - Just Learning yaml :) 23 | 24 | ### Installation 25 | 26 | Then we can clone this template or download script directly 27 | 28 | ```sh 29 | wget archinstaller.ml -O ArchInstaller 30 | # or git clone https://github.com/FakeSudo/ArchInstaller 31 | chmod +x ArchInstaller 32 | ./ArchInstaller # or ./ArchInstaller {url} 33 | ``` 34 | 35 | NOTE: before running Installer write config.yaml or pass a pastbin or git gist url to script 36 | 37 | ## Configuration 38 | 39 | Config will separate into user settings that run after Installation and root settings 40 | 41 | ### Root settings: 42 | 43 | - username 44 | 45 | - Note : defualt password is 'password' and you can change it after reboot 46 | 47 | - hostname 48 | 49 | - locale 50 | 51 | common: `en_US.UTF-8 UTF-8` 52 | 53 | - timezone 54 | 55 | Find your timezone with `timedatectl list-timezones` 56 | 57 | - shell 58 | 59 | Default user shell 60 | 61 | common: `/bin/bash` 62 | 63 | - drive 64 | 65 | - blk 66 | 67 | The hard drive you want to install arch on it 68 | 69 | example: `/dev/sda` 70 | 71 | - erase 72 | 73 | Format the hard drive don't enable this field unless you know what are you doing 74 | 75 | example: Boolean value 76 | 77 | - partitions 78 | 79 | List of partition to create 80 | Contains: 81 | 82 | - size 83 | in `K/M/G` format 84 | - filesystem 85 | 86 | type of the partition 87 | 88 | commons: `uefi/linux/swap & all fdisk types` 89 | 90 | - number 91 | 92 | the number of the primary partition 93 | 94 | for more information read `fdisk` manual 95 | 96 | - base packages 97 | 98 | Packages needed for booting the system 99 | 100 | - base daemons 101 | 102 | ### User settings contains: 103 | 104 | - custom script 105 | 106 | - aurhelper 107 | 108 | common: `yay/paru` 109 | 110 | - user packages 111 | 112 | - user daemon 113 | 114 | Note: if you configured user setting you should rerun installer command after reboot 115 | 116 | 117 | 118 | 119 | [contributors-shield]: https://img.shields.io/github/contributors/FakeSudo/ArchInstaller?style=for-the-badge 120 | [contributors-url]: https://github.com/FakeSudo/ArchInstaller/graphs/contributors 121 | [forks-shield]: https://img.shields.io/github/forks/FakeSudo/ArchInstaller?style=for-the-badge 122 | [forks-url]: https://github.com/FakeSudo/ArchInstaller/network/members 123 | [stars-shield]: https://img.shields.io/github/stars/FakeSudo/ArchInstaller?style=for-the-badge 124 | [stars-url]: https://github.com/FakeSudo/ArchInstaller/stargazers 125 | [issues-shield]: https://img.shields.io/github/issues/FakeSudo/ArchInstaller?style=for-the-badge 126 | [issues-url]: https://github.com/FakeSudo/ArchInstaller/issues 127 | [license-shield]: https://img.shields.io/github/license/FakeSudo/ArchInstaller?style=for-the-badge 128 | [license-url]: https://github.com/FakeSudo/ArchInstaller/blob/main/LICENSE.md 129 | [telegram-shield]: https://img.shields.io/badge/Telegram-blue.svg?style=for-the-badge&logo=telegram 130 | [telegram-url]: https://t.me/FakeSudo 131 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | root: 2 | username: fakesudo 3 | settings: 4 | ntp: True 5 | hostname: archlinux 6 | locale: 7 | - en_US.UTF-8 UTF-8 8 | - fa_IR UTF-8 9 | lang: en_US.UTF-8 10 | timezone: Asia/Tehran 11 | shell: /bin/bash 12 | drive: 13 | blk: /dev/sda 14 | erase: false 15 | partitions: # used fdisk conf 16 | - label: BOOT 17 | size: +512M 18 | filesystem: uefi 19 | number: 1 20 | 21 | - label: ROOT 22 | size: +5G 23 | filesystem: linux 24 | number: 2 25 | 26 | - label: HOME 27 | size: " " 28 | filesystem: linux 29 | number: 3 30 | 31 | packages: # official mirror 32 | # Base Packages 33 | - base 34 | - base-devel 35 | - linux 36 | - linux-firmware 37 | - linux-headers 38 | - grub 39 | - efibootmgr 40 | - networkmanager 41 | services: 42 | - NetworkManager 43 | 44 | user: 45 | scripts: 46 | - name: by file 47 | script: ./configs/simple.sh 48 | - name: inline shell 49 | shell: echo FakeSudo 50 | 51 | aurhelper: paru # or yay 52 | 53 | packages: # official & aur 54 | - pyright 55 | - docker 56 | 57 | services: 58 | - docker 59 | -------------------------------------------------------------------------------- /configs/simple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sleep 10 3 | --------------------------------------------------------------------------------