diff --git a/.gitignore b/.gitignore index 2a7412f..fe28f31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.iso -*.swp \ No newline at end of file +*.swp +*.iso.sha256sum \ No newline at end of file diff --git a/README.md b/README.md index e84dd36..be64014 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,13 @@ # Potos iso building -To create your own Linux Client based on Potos, you need to create a [config](config) directory according to the scaffolding in this repo (and the explanation in section [Config](#Config)). To build the iso yourself, you can choose between one of the following possibilities: - * [Plain Docker](#Build iso - Docker plain) - * [Docker Compose](#Build iso - Docker compose) +## Structure +This repo contains the iso builder for Potos. + * [`container`](container) contains the source for the docker image `ghcr.io/projectpotos/potos-iso-builder` + * [`config`](container) contains an example config + * [`output`](output) is the directory where using the default commands the final iso is stored into -## Build iso - Docker plain -1. Clone this repository with `git clone` or download & unzip it. -2. Adjust the files in [`config`](./config) to your client -3. Run `docker run -it -v $(pwd)/config:/config -v $(pwd)/output:/output ghcr.io/projectpotos/potos-iso-builder:latest` - -## Build iso - Docker compose - -1. Clone this repository with `git clone` or download & unzip it. -2. Adjust the files in [`config`](./config) to your client -3. Run `docker-compose up` in the main directory - -## Build iso - Github Workflow -1. Create a new repo containing the `config` directory adjusted for your client -2. Create the following file as workflow file under `.github/workflows/isobuild.yml` -``` -name: Build Iso -on: - workflow_dispatch: - pull_request: -jobs: - build: - name: Build Iso - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - name: Run the build process with Docker - uses: addnab/docker-run-action@v3 - with: - image: ghcr.io/projectpotos/potos-iso-builder:latest - run: ./build-iso - options: -v ${{ github.workspace }}:/config -v /output:/output - - name: Save iso - uses: actions/upload-artifact@v3 - with: - name: potos-iso - path: /output/*.iso - retention-days: 1 -``` - -## Build your own container image - -If you wnat to build the build container image yourself, all the container relevant files are in [`container`](./container) -1. Execute `docker build container/` to build the container -2. Execute `docker build container/ -t iso-build` to build the container and assign a tag - -Then to build the client, adjust the image to be used in the commands above. - -## Config - -### logo.png -Place your client logo in this file, if you want some customization during the setup dialogs - -### config.yml -The iso build is configured using [YAML](https://en.wikipedia.org/wiki/YAML) based `config.yml` file. The file has to be located within the configuration directory mounted as volume into the build container. - -> **CONVENTION** -> The *dot-notation* of a config-key like `client_name.long` means `long` property within the `client_name` section. All *dot-notation* references are absolut. - -| Variable | Type | Default | Comment | -|---|---|---|---| -| client_name.long | *string* | Potos Linux Client | Define the Name of your Linux Client, e.g. "My Linux Client". | -| client_name.short | *string, lowercase, short, regex `^([0-9a-z]{1,32})$`* | potos | Define a short name of your Linux Client. Use lowercase. Will be used for example for the log folder /var/log/$POTOS_CLIENT_SHORTNAME | -| disk_encryption.enable | *boolean --> `true`\|`false`* | false | To enable autoinstall feature with disk encryption (except: /boot). You have to enter the defined password at first boot after the installation. | -| disk_encryption.init_password | *string* | install | The autoinstall feature with disk encryption (except: /boot) needs a predefined decryption password. You have to enter this password at first boot after the installation. | -| specs.url | *string, URL, trailing slash* | https://github.com/projectpotos/ | The URL to your Git Account that holds your own Potos Specs Repository. Make sure you have the trailing slash included. | -| specs.repo | *string, part of the URL* | ansible-specs-potos | The name of your own Potos Git Specs Repository, without *.git* at the End. | -| specs.branch | *string* | main | Define the branch of your specs.repo. Typical values are `main`, `master`, `develop` | -| initial_hostname | *string* | potoshostname01 | Your Linux Client based on Potos will use this predefined hostname at the installation and first boot. | -| initial_user.username | *string* | admin | An initial username is required. Will have full sudo (root) permission. Can be removed later on. | -| initial_user.password | *string* | admin *hashed* | The password in form of a hash. Create your own with `echo -n yourpasswordhere \| mkpasswd --method=SHA-512 --stdin` . | -| environment | *string* | production | Possible values are `production` and `develop`. The installation in `develop` mode is more verbose. | -| first_boot_ansible.runtype | *string* | setup | Run type of the first ansible run | -| full_unattended_install | *boolean --> `true`\|`false`* | false | Disable security question before overwrite of disk and user input from iso side to allow a fully unattended installation | -| input.iso_filename | *string* | ubuntu-22.04.1-live-server-amd64.iso | Name of the local iso file (needs to correspond with content of the SHA256SUMS file) | -| input.iso_url | *string* | https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso | Where to download the iso file if it doesn't exist locally | -| input.sha256_filename | *string* | SHA256SUMS | Name of the SHA256SUMS file | -| input.sha256_url | *string* | https://releases.ubuntu.com/22.04/SHA256SUMS | Where to download the SHA256SUMS file if it doesn't exist locally | -| output.version | *string* | current date in yyyymmddd | What string should be used as Version identifier | -| output.filename | *string* | `client_name.short`-installer-`environment`.iso | How the iso in the output directory should be named | -| preinstall_packages | *list of strings* | - python3-virtualenv
- linux-generic-hwe-22.04
- ubuntu-desktop
- plymouth-theme-ubuntu-logo
- ldap-utils
- yad | What packages should be installed with autoinstall. * `python3-virtualenv`: python with virtualenv is required to install ansible within it * `linux-generic-hwe-22.04`: install hwe kernel * `ubuntu-desktop`: install gnome desktop * `plymouth-theme-ubuntu-logo`: install plymouth-theme * `ldap-utils`: ldap utils used for all the ldap integration things * `yad`: used for graphical dialogs during setup | - -# Potos iso installation - -Boot from the previously generated Potos .iso image in your virtual or physical hardware and follow the instruction. The client is entirely defined by the `specs` repo, +# Documentation +To see the entire documentation go to [potos.dev](https://potos.dev) + * [How to build an ISO](https://potos.dev/guide/iso-build/how-to-build.html) + * [ISO build config](https://potos.dev/guide/iso-build/config.html) diff --git a/config/config.yml b/config/config.yml index 3272a30..7cb0a25 100644 --- a/config/config.yml +++ b/config/config.yml @@ -19,18 +19,7 @@ environment: "production" first_boot_ansible: runtype: "setup" full_unattended_install: false -input: - iso_filename: "ubuntu-22.04.1-live-server-amd64.iso" - iso_url: "https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso" - iso_sha256_filename: "SHA256SUMS" - iso_sha256_url: "https://releases.ubuntu.com/22.04/SHA256SUMS" +os: "jammy" output: - version: "22.04" + version: "%Y%m%d" iso_filename: "potos-installer.iso" -preinstall_packages: - - python3-virtualenv - - linux-generic-hwe-22.04 - - ubuntu-desktop - - plymouth-theme-ubuntu-logo - - ldap-utils - - yad diff --git a/container/Dockerfile b/container/Dockerfile index 3ab0a11..456fbaa 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -3,13 +3,13 @@ FROM ubuntu:22.04 WORKDIR /potos-iso # Install ISO creation depencies -RUN apt update && apt install -y gfxboot p7zip-full xorriso wget curl libhtml-parser-perl cpio whois python3 python3-pip fdisk -RUN pip3 install j2cli -RUN wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && chmod +x /usr/local/bin/yq +RUN apt update && apt install -y gfxboot p7zip-full xorriso wget curl libhtml-parser-perl cpio whois python3 python3-pip fdisk squashfs-tools +COPY requirements.txt . +RUN pip3 install -r requirements.txt # Create config directory RUN mkdir /config ADD . /potos-iso -CMD ["./build-iso"] +CMD ["/usr/bin/python3", "build-iso.py"] diff --git a/container/autoinstall-user-data.j2 b/container/autoinstall-user-data.j2 index 6fa74fe..08ce3f9 100644 --- a/container/autoinstall-user-data.j2 +++ b/container/autoinstall-user-data.j2 @@ -7,9 +7,9 @@ autoinstall: - arches: [default] uri: "http://archive.ubuntu.com/ubuntu" identity: - hostname: {{ POTOS_INITIAL_HOSTNAME | default('potoshostname01') }} - password: {{ POTOS_INITIAL_PASSWORD_HASH | default('$6$L36BiUuVCSipvlO8$oGI0C.LXZegkbftFkVDXXaasTM6zs9LM71BkqZToKw5aOZ7Yr70pkzH3P9Xz5R.n0ULJ0Zf8v5ZQ/eH8flDR7/') }} - username: {{ POTOS_INITIAL_USERNAME | default('admin') }} + hostname: {{ config['initial_hostname'] | default('potoshostname01') }} + password: {{ config['initial_user']['password'] | default('$6$L36BiUuVCSipvlO8$oGI0C.LXZegkbftFkVDXXaasTM6zs9LM71BkqZToKw5aOZ7Yr70pkzH3P9Xz5R.n0ULJ0Zf8v5ZQ/eH8flDR7/') }} + username: {{ config['initial_user']['username'] | default('admin') }} ssh: allow-pw: true authorized-keys: [] @@ -53,7 +53,7 @@ autoinstall: size: 1GB preserve: false number: 2 -{% if POTOS_DISK_ENCRYPTION_ENABLE is defined and POTOS_DISK_ENCRYPTION_ENABLE == 'true' %} +{% if config['disk_encryption']['enable'] is defined and config['disk_encryption']['enable'] == 'true' %} - id: partition_crypt type: partition device: disk_primary @@ -62,7 +62,7 @@ autoinstall: number: 3 - id: dm-crypt_0 volume: partition_crypt - key: {{ POTOS_DISK_ENCRYPTION_INITIAL_PASSWORD | default('install') }} + key: {{ config['disk_encryption']['init_password'] | default('install') }} preserve: false type: dm_crypt - id: lvm_volgroup_0 @@ -132,8 +132,7 @@ autoinstall: path: / type: mount packages: -{% set packages = POTOS_PRE_INSTALL_PACKAGES.split('\n') %} -{% for package in packages %} +{% for package in config['packages']['preinstall'] %} - {{ package }} {% endfor %} late-commands: @@ -150,10 +149,10 @@ autoinstall: - cp /cdrom/setup/default-netplan.yml /target/etc/netplan/01-network-manager-all.yaml - cp /cdrom/setup/gnome-sudo /target/etc/sudoers.d/01_gnome-initial-setup - mkdir -p /target/etc/potos/ && chown 0:0 /target/etc/potos/ && chmod 0700 /target/etc/potos/ -{% if POTOS_GIT_SPECS_SSH_KEY != "" %} +{% if config['specs']['ssh_key'] != "" %} - cp /cdrom/setup/specs_key /target/etc/potos/specs_key && chown 0:0 /target/etc/potos/specs_key && chmod 0400 /target/etc/potos/specs_key {% endif %} -{% if POTOS_GIT_SPECS_ANSIBLE_VAULT != "" %} +{% if config['specs']['ansible_vault_key_file'] != "" %} - cp /cdrom/setup/ansible_vault_key /target/etc/potos/ansible_vault_key && chown 0:0 /target/etc/potos/ansible_vault_key && chmod 0500 /target/etc/potos/ansible_vault_key {% endif %} - cp -r /cdrom/setup /target/setup diff --git a/container/build-iso.py b/container/build-iso.py new file mode 100755 index 0000000..022984c --- /dev/null +++ b/container/build-iso.py @@ -0,0 +1,373 @@ +#!/usr/bin/python3 + +import yaml +import os +import shutil +import jinja2 +from datetime import date +import subprocess +from pprint import pprint + +# define jinja2 environment +j2 = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(os.path.abspath(__file__))) +) + +# get config inkl default values +config = {} +with open("/config/config.yml", "r") as f: + try: + ymlconfig = yaml.safe_load(f) + config['client_name'] = {} + config['client_name']['long'] = ymlconfig.get("client_name",{}).get('long',"Potos Linux Client") + config['client_name']['short'] = ymlconfig.get("client_name",{}).get('short',"potos") + config['disk_encryption'] = {} + config['disk_encryption']['enable'] = ymlconfig.get("disk_encryption",{}).get('enable',False) + config['disk_encryption']['init_password'] = ymlconfig.get("disk_encryption",{}).get('init_password',"install") + config['specs'] = {} + config['specs']['url'] = ymlconfig.get("specs",{}).get('url',"https://github.com/projectpotos/") + config['specs']['repo'] = ymlconfig.get("specs",{}).get('repo',"ansible-specs-potos") + config['specs']['branch'] = ymlconfig.get("specs",{}).get('branch',"main") + config['specs']['ssh_key'] = ymlconfig.get("specs",{}).get('ssh_key',"") + config['specs']['ansible_vault_key_file'] = ymlconfig.get("specs",{}).get('ansible_vault_key_file',"") + config['initial_hostname'] = ymlconfig.get("initial_hostname","potoshostname01") + config['initial_user'] = {} + config['initial_user']['username'] = ymlconfig.get("initial_user",{}).get('username',"admin") + config['initial_user']['password'] = ymlconfig.get("initial_user",{}).get('password',"$6$L36BiUuVCSipvlO8$oGI0C.LXZegkbftFkVDXXaasTM6zs9LM71BkqZToKw5aOZ7Yr70pkzH3P9Xz5R.n0ULJ0Zf8v5ZQ/eH8flDR7/") + config['environment'] = ymlconfig.get("environment","production") + config['first_boot_ansible'] = {} + config['first_boot_ansible']['runtype'] = ymlconfig.get("first_boot_ansible",{}).get('runtype',"setup") + config['full_unattended_install'] = ymlconfig.get("full_unattended_install", False) + config['os'] = ymlconfig.get("os","jammy") + config['output'] = {} + config['output']['version'] = date.today().strftime(ymlconfig.get("output",{}).get('version',"%Y%m%d")) + config['output']['iso_filename'] = ymlconfig.get("output",{}).get('iso_filename',"%s-installer.iso"%(config['client_name']['short'])) + config['isolinux'] = {} + config['isolinux']['txtBackgroundColor'] = ymlconfig.get("isolinux",{}).get('txtBackgroundColor', "0xCCCCCC") + config['isolinux']['txtForegroundColor'] = ymlconfig.get("isolinux",{}).get('txtForegroundColor', "0xFFFFFF") + config['isolinux']['screenColor'] = ymlconfig.get("isolinux",{}).get('screenColor', "0x161B21") + except yaml.YAMLError as e: + print(e) + exit(1) + +TMP_DIR = "iso" +REQIREMENTS = ["7z", "gfxboot", "xorriso", "wget", "curl", "sha256sum"] + +# switch iso by selected os +if config['os'] == "jammy": + config['input'] = {} + config['input']['iso_filename'] = "ubuntu-22.04.1-live-server-amd64.iso" + config['input']['iso_url'] = ( + "https://releases.ubuntu.com/22.04/ubuntu-22.04.1-live-server-amd64.iso" + ) + config['input']['sha256_filename'] = "SHA256SUMS" + config['input']['sha256_url'] = "https://releases.ubuntu.com/22.04/SHA256SUMS" + config['packages'] = {} + config['packages']['preinstall'] = [ + "python3-virtualenv", + "linux-generic-hwe-22.04", + "ubuntu-desktop", + "plymouth-theme-ubuntu-logo", + "ldap-utils", + "yad", + ] +elif config['os'] == "focal": + config['input'] = {} + config['input']['iso_filename'] = "ubuntu-20.04.5-live-server-amd64.iso" + config['input']['iso_url'] = ( + "https://releases.ubuntu.com/20.04/ubuntu-20.04.5-live-server-amd64.iso" + ) + config['input']['sha256_filename'] = "SHA256SUMS" + config['input']['sha256_url'] = "https://releases.ubuntu.com/20.04/SHA256SUMS" + config['packages'] = {} + config['packages']['preinstall'] = [ + "python3-virtualenv", + "linux-generic-hwe-20.04", + "ubuntu-desktop", + "plymouth-theme-ubuntu-logo", + "ldap-utils", + "yad", + ] +else: + print("Invalid base os: %s"%(config['os'])) + print("Currently supported os are:") + print(" * Ubuntu: 'jammy', 'focal'") + exit(1) + +###### +# Start with iso build +###### + +# Print config info +if config['environment'] == "develop": + print( + "*** config.environment is %s, going to print some more informations for you:"%( + config['environment'], + ) + ) + pprint(config) + +print( + "*** Going to build an ISO for %s (%s)"%(config['client_name']['long'], config['environment']) +) + +# Check if required software is installed else install +for r in REQIREMENTS: + if os.system("which %s > /dev/null"%(r)) != 0: + print("%s is missing!"%(r)) + exit(1) + +# If iso dir somehow exists, remove it +if os.path.exists(TMP_DIR) and os.path.isdir(TMP_DIR): + shutil.rmtree(TMP_DIR) +elif os.path.exists(TMP_DIR): + print("ERROR: %s exists but is not a directory"%(TMP_DIR)) + exit(1) + +# If iso and checksum not exist, download them +if not os.path.exists(config['input']['iso_filename']): + if ( + os.system( + "wget -nv --output-document='%s' %s"%( + config['input']['iso_filename'], + config['input']['iso_url'], + ) + ) + != 0 + ): + print("ERROR: %s could not be downloaded!"%(config['input']['iso_url'])) + +if not os.path.exists(config['input']['sha256_filename']): + if ( + os.system( + "wget -nv --output-document='%s' %s"%( + config['input']['sha256_filename'], + config['input']['sha256_url'], + ) + ) + != 0 + ): + print("ERROR: %s could not be downloaded!"%(config['input']['sha256_url'])) + +# Fail if no checksum exists +if ( + os.system("sha256sum --ignore-missing --quiet -c %s"%(config['input']['sha256_filename'])) + != 0 +): + print("ERROR: sha256sum check failed!") + exit(1) + +# extract iso into temporary directory +if os.system("7z x '%s' -o'%s'"%(config['input']['iso_filename'], TMP_DIR)) != 0: + print("ERROR: could not extract iso file") + exit(2) + +# remove no longer needed stuff +if os.path.exists(os.path.join(TMP_DIR, "preseed")): + shutil.rmtree(os.path.join(TMP_DIR, "preseed")) + +# make directory for files during autoinstall +os.mkdir(os.path.join(TMP_DIR, "setup")) + +# copy Netplan into iso +shutil.copy("default-netplan.yml", os.path.join(TMP_DIR, "setup/default-netplan.yml")) + +# copy Gnome initial setup sudoers rule into iso +shutil.copy("gnome-sudo", os.path.join(TMP_DIR, "setup/gnome-sudo")) + +# template autoinstall files +os.mkdir(os.path.join(TMP_DIR, "nocloud-uefi")) +with open(os.path.join(TMP_DIR, "nocloud-uefi/meta-data"), "w") as f: + f.write( + j2.get_template("autoinstall-meta-data.j2").render( + config=config, autoinstall_type="uefi" + ) + ) +with open(os.path.join(TMP_DIR, "nocloud-uefi/user-data"), "w") as f: + f.write( + j2.get_template("autoinstall-user-data.j2").render( + config=config, autoinstall_type="uefi" + ) + ) + +os.mkdir(os.path.join(TMP_DIR, "nocloud-bios")) +with open(os.path.join(TMP_DIR, "nocloud-bios/meta-data"), "w") as f: + f.write( + j2.get_template("autoinstall-meta-data.j2").render( + config=config, autoinstall_type="bios" + ) + ) +with open(os.path.join(TMP_DIR, "nocloud-bios/user-data"), "w") as f: + f.write( + j2.get_template("autoinstall-user-data.j2").render( + config=config, autoinstall_type="bios" + ) + ) + +# create version file +with open( + os.path.join(TMP_DIR, "setup/%s-version" % (config['client_name']['short'])), "w" +) as f: + f.write( + "%s %s (%s)\n"%( + config['client_name']['short'], + config['output']['version'] , + date.today().strftime("%Y%m%d-%H%M"), + ) + ) + +# remove boot config +shutil.rmtree(os.path.join(TMP_DIR, "[BOOT]")) + +# template grub config +if not os.path.exists(os.path.join(TMP_DIR, "boot/grub")): + os.makedir(os.path.join(TMP_DIR, "boot/grub")) +with open(os.path.join(TMP_DIR, "boot/grub/grub.cfg"), "w") as f: + f.write(j2.get_template("grub.cfg.j2").render(config=config)) + +# copy logo into iso +if os.path.exists("/config/logo.png"): + shutil.copy("/config/logo.png", os.path.join(TMP_DIR, "setup/logo.png")) +else: + shutil.copy("default/logo.png", os.path.join(TMP_DIR, "setup/logo.png")) + +# copy ssh deploy key for specs repo into image +if config['specs']['ssh_key'] != "" and os.path.exists(os.path.join("/config/", config['specs']['ssh_key'])): + shutil.copy( + os.path.join("/config/", config['specs']['ssh_key']), + os.path.join(TMP_DIR, "setup/specs_key"), + ) + +# copy ansible-vault key for specs repo into image +if config['specs']['ansible_vault_key_file'] != "" and os.path.exists( + os.path.join("/config/", config['specs']['ansible_vault_key_file']) +): + shutil.copy( + os.path.join("/config/", config['specs']['ansible_vault_key_file']), + os.path.join(TMP_DIR, "setup/ansible_vault_key"), + ) + +# template diverse files for firstboot + +with open(os.path.join(TMP_DIR, "setup/firstboot-gui.sh"), 'w') as f: + f.write(j2.get_template("firstboot-gui.sh.j2").render(config=config)) +os.chmod(os.path.join(TMP_DIR, "setup/firstboot-gui.sh"), 0o555) +with open(os.path.join(TMP_DIR, "setup/finish.sh"), 'w') as f: + f.write(j2.get_template("finish.sh.j2").render(config=config)) +os.chmod(os.path.join(TMP_DIR, "setup/finish.sh"), 0o555) +with open(os.path.join(TMP_DIR, "setup/change-keyboard-layout"), 'w') as f: + f.write(j2.get_template("change-keyboard-layout.j2").render(config=config)) +os.chmod(os.path.join(TMP_DIR, "setup/change-keyboard-layout"), 0o555) + + + +# adjust md5sum checks +os.system("sed -i 's|$%s/|./|g' '%s/md5sum.txt'" % (TMP_DIR, TMP_DIR)) + +# Get efi partition infos +iso_bs = subprocess.check_output( + "fdisk -l '%s' | grep 'Sector size' | tr -s ' ' | cut -d ' ' -f4" + % (config['input']['iso_filename']), shell=True +).decode("utf-8").rstrip() +iso_efi_skip = subprocess.check_output( + "fdisk -l '%s' | grep 'EFI' | tr -s ' ' | cut -d ' ' -f2" % (config['input']['iso_filename']), shell=True +).decode("utf-8").rstrip() +iso_efi_size = subprocess.check_output( + "fdisk -l '%s' | grep 'EFI' | tr -s ' ' | cut -d ' ' -f4" % (config['input']['iso_filename']), shell=True +).decode("utf-8").rstrip() + +# define MBR +MBR_FILE = "boot_hybrid.img" + +# write original MBR to MBR file +os.system("dd if='%s' bs=1 count=432 of='%s'" % (config['input']['iso_filename'], MBR_FILE)) + +# write original efi.img to file +os.system( + "dd if='%s' bs='%s' skip='%s' count='%s' of=efi.img" + % (config['input']['iso_filename'], iso_bs, iso_efi_skip, iso_efi_size) +) + +# as default try to use eltorito boot image +boot_image = "/boot/grub/i386-pc/eltorito.img" + +# if eltorito is not available in the iso, fall back to isolinux +if not os.path.exists(os.path.join(TMP_DIR, boot_image)): + with open(os.path.join(TMP_DIR, "isolinux/txt.cfg"), "w") as f: + f.write(j2.get_template("isolinux.cfg.j2").render(config=config)) + if os.path.exists("/config/splash.pcx"): + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --add-files /config/splash.pcx" + % (TMP_DIR) + ) + else: + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --add-files default/splash.pcx" + % (TMP_DIR) + ) + if os.path.exists("/config/access.pcx"): + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --add-files /config/access.pcx" + % (TMP_DIR) + ) + else: + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --add-files default/access.pcx" + % (TMP_DIR) + ) + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --change-config background='%s'" + % (TMP_DIR, config['isolinux']['txtBackgroundColor']) + ) + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --change-config foreground='%s'" + % (TMP_DIR, config['isolinux']['txtForegroundColor']) + ) + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --change-config screen-colour='%s'" + % (TMP_DIR, config['isolinux']['screenColor']) + ) + os.system("gfxboot -a '%s/isolinux/bootlogo' --default-language en_US" % (TMP_DIR)) + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --rm-config hidden-timeout" % (TMP_DIR) + ) + os.system( + "gfxboot -a '%s/isolinux/bootlogo' --change-config hidden-timeout=1" % (TMP_DIR) + ) + boot_image = "isolinux/isolinux.bin" + +os.system( + """xorriso -as mkisofs -r -V '%s' \ +-o 'output.iso' \ +--grub2-mbr '%s' \ +-partition_offset 16 \ +--mbr-force-bootable \ +-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b efi.img \ +-appended_part_as_gpt \ +-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \ +-c '/boot.catalog' \ +-b '%s' \ +-no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info \ +-eltorito-alt-boot \ +-e '--interval:appended_partition_2:::' \ +-no-emul-boot \ +'%s'""" + % (config['client_name']['short'], MBR_FILE, boot_image, TMP_DIR) +) + +# remove temp directory +shutil.rmtree(TMP_DIR) + +# remove mbr +os.remove(MBR_FILE) + +# Move iso to output directory +shutil.move("output.iso", os.path.join("/output/", config['output']['iso_filename'])) + +# Generate checksum +with open("/output/%s.sha256sum" % (config['output']['iso_filename']), "w") as f: + f.write( + subprocess.check_output("sha256sum '/output/%s'" % (config['output']['iso_filename']), shell=True).decode("utf-8").rstrip() + ) + +print("Done!") diff --git a/container/change-keyboard-layout.j2 b/container/change-keyboard-layout.j2 index 39a3d9f..c95000e 100644 --- a/container/change-keyboard-layout.j2 +++ b/container/change-keyboard-layout.j2 @@ -22,4 +22,4 @@ gsettings set org.gnome.desktop.input-sources mru-sources "[('xkb', '${KEYBOARD_ # Write this settings permamently, keep them after reboot, as root user sudo sed -i 's/XKBLAYOUT=\".*"/XKBLAYOUT=\"'${KEYBOARD_LAYOUT}'\"/g' /etc/default/keyboard -echo "${KEYBOARD_LAYOUT}" > /tmp/{{ POTOS_CLIENT_SHORTNAME }}_keyboardlayout +echo "${KEYBOARD_LAYOUT}" > /tmp/{{ config['client_name']['short'] }}_keyboardlayout diff --git a/container/default/access.pcx b/container/default/access.pcx new file mode 100644 index 0000000..9e93dc0 Binary files /dev/null and b/container/default/access.pcx differ diff --git a/container/logo.png b/container/default/logo.png similarity index 100% rename from container/logo.png rename to container/default/logo.png diff --git a/container/default/splash.pcx b/container/default/splash.pcx new file mode 100644 index 0000000..b5cd4d9 Binary files /dev/null and b/container/default/splash.pcx differ diff --git a/container/finish.sh.j2 b/container/finish.sh.j2 index eb29492..1463a2e 100755 --- a/container/finish.sh.j2 +++ b/container/finish.sh.j2 @@ -12,17 +12,17 @@ ANSIBLE_GIT_URL='https://github.com/projectpotos/ansible-plays-potos.git' ANSIBLE_GIT_BRANCH='main' mkdir -p "/etc/potos" -mkdir -p "/var/log/{{ POTOS_CLIENT_SHORTNAME }}" +mkdir -p "/var/log/{{ config['client_name']['short']}}" cat > /etc/potos/specs_repo.yml < /var/log/{{ POTOS_CLIENT_SHORTNAME }}/setup.log" & +sudo mkdir -m 755 -p /var/log/{{ config['client_name']['short'] }} +sudo touch /var/log/{{ config['client_name']['short'] }}/setup.log +sudo -E bash -c "/setup/finish.sh &> /var/log/{{ config['client_name']['short'] }}/setup.log" & FINISH_CMD_PID=${!} sudo chown gnome-initial-setup /dev/tty2 -sudo tail -f /var/log/{{ POTOS_CLIENT_SHORTNAME }}/setup.log | tee /dev/tty2 | yad --fullscreen --no-buttons --title "{{ POTOS_CLIENT_NAME }} Setup" \ +sudo tail -f /var/log/{{ config['client_name']['short'] }}/setup.log | tee /dev/tty2 | yad --fullscreen --no-buttons --title "{{ config['client_name']['long'] }} Setup" \ --progress --enable-log --log-expanded --log-on-top --log-height 500 \ --text 'Please wait until the setup is finished' & @@ -91,7 +82,7 @@ if [[ ${FINISH_CMD_RC} -ne 0 ]]; then esac fi -sudo cat /var/log/{{ POTOS_CLIENT_SHORTNAME }}/setup.log | yad --fullscreen --title "{{ POTOS_CLIENT_NAME }} Setup" \ +sudo cat /var/log/{{ config['client_name']['short'] }}/setup.log | yad --fullscreen --title "{{ config['client_name']['long'] }} Setup" \ --borders 20 --align center --button gtk-ok \ --button "Shutdown":"sudo systemctl halt" \ --text-info --tail \ diff --git a/container/grub.cfg.j2 b/container/grub.cfg.j2 index 5db1c54..c67c679 100644 --- a/container/grub.cfg.j2 +++ b/container/grub.cfg.j2 @@ -4,14 +4,15 @@ loadfont unicode set menu_color_normal=white/black set menu_color_highlight=black/light-gray +set gfxmode=640x480 grub_platform if [ "$grub_platform" = "efi" ]; then -menuentry "Install {{ POTOS_CLIENT_NAME }}" { +menuentry "Install {{ config['client_name']['long'] }}" { set gfxpayload=keep linux /casper/vmlinuz fsck.mode=skip autoinstall ds=nocloud\;s=/cdrom/nocloud-uefi/ --- initrd /casper/initrd - {% if POTOS_FULL_UNATTENDED is not defined or POTOS_FULL_UNATTENDED != 'true' -%} + {% if config['full_unattended_install'] is not defined or config['full_unattended_install'] != 'true' -%} echo WARNING: This will erase your hard drive. Press Enter to confirm. read dummy {%- endif %} @@ -24,11 +25,11 @@ menuentry 'UEFI Firmware Settings' { } #grub_platform set to bios expected else -menuentry "Install {{ POTOS_CLIENT_NAME }}" { +menuentry "Install {{ config['client_name']['long'] }}" { set gfxpayload=keep linux /casper/vmlinuz fsck.mode=skip autoinstall ds=nocloud\;s=/cdrom/nocloud-bios/ --- initrd /casper/initrd - {% if POTOS_FULL_UNATTENDED is not defined or POTOS_FULL_UNATTENDED != 'true' -%} + {% if config['full_unattended_install'] is not defined or config['full_unattended_install'] != 'true' -%} echo WARNING: This will erase your hard drive. Press Enter to confirm. read dummy {%- endif %} diff --git a/container/isolinux.cfg.j2 b/container/isolinux.cfg.j2 new file mode 100644 index 0000000..4d40bc8 --- /dev/null +++ b/container/isolinux.cfg.j2 @@ -0,0 +1,8 @@ +default prod +label prod + menu label ^Install {{ config['client_name']['long'] }} + kernel /casper/vmlinuz + append initrd=/casper/initrd quiet fsck.mode=skip autoinstall ds=nocloud;s=/cdrom/nocloud-bios/ --- +label hd + menu label ^Boot from first hard disk + localboot 0x80 diff --git a/container/requirements.txt b/container/requirements.txt new file mode 100644 index 0000000..9c893db --- /dev/null +++ b/container/requirements.txt @@ -0,0 +1,2 @@ +pyyaml +Jinja2