Repository: cobbler/cobbler

Issue:
[Backport] Cobbler Import fails
### Original feature issue

- Issue: #2805
- PRs:
  - #2767
  - #2819

### Target release

- [ ] release33
- [x] release32
- [ ] release30

### Reason

Stabilization of 3.2.x for the community.



Discussion:
I have version 3.2.2 . I get a signature error when trying to import almalinux9.0. It's in the rhel9 breed. I did 'cobbler sgnature update' and it gets https://cobbler.github.io/signatures/3.0.x/latest.json. I then do 'cobbler signature reload' and it says 

There was an error loading the signature data in .
Please check the JSON file or run 'cobbler signature update'.

I copied the laest signature file from github to `/var/lib/cobbler/distro_signatures.json`. Again i get an error when doing 'cobbler signature reload' but when I restart cobblerd redhat9 now appears. i still can't import almalinux though. i get

```
No signature matched in /var/www/cobbler/distro_mirror/AlmaLinux9.0-x86_64-minimal
Exception occurred: <class 'cobbler.cexceptions.CX'>
Exception value: 'No signature matched in /var/www/cobbler/distro_mirror/AlmaLinux9.0-x86_64-minimal'
Exception Info:
  File "/usr/lib/python3.6/site-packages/cobbler/remote.py", line 98, in run
    rc = self._run(self)

  File "/usr/lib/python3.6/site-packages/cobbler/remote.py", line 286, in runner
    self.logger

  File "/usr/lib/python3.6/site-packages/cobbler/api.py", line 1476, in import_tree
    import_module.run(path, mirror_name, network_root, autoinstall_file, arch, breed, os_version)

  File "/usr/lib/python3.6/site-packages/cobbler/modules/managers/import_signatures.py", line 195, in run
    raise CX(error_msg)

!!! TASK FAILED !!!
```

I am using CentOS Linux release 8.5.2111 and Cobbler 3.2.2 is all I'm offered to install by yum. I don't mind building and installing from source but am worried that is going to break my setup.

@emyr666 You can normally see a more detailed error in /var/log/cobbler/cobbler.log
This is all that appears in /var/log/cobbler/cobbler.log

```
2022-12-10T10:49:52 - DEBUG | REMOTE CLI Authorized; user(?)
2022-12-10T10:49:52 - INFO | REMOTE start_task(Media import); event_id(2022-12-10_104952_import); user(?)
2022-12-10T10:49:53 - INFO | import_tree; ['/mnt/iso', 'AlmaLinux9.0-x86_64-minimal', None, None, None]
2022-12-10T10:49:53 - INFO | importing from a network location, running rsync to fetch the files first
2022-12-10T10:49:53 - INFO | running: rsync -a  '/mnt/iso/' /var/www/cobbler/distro_mirror/AlmaLinux9.0-x86_64-minimal --progress
2022-12-10T10:49:53 - INFO | received on stdout: sending incremental file list

2022-12-10T10:49:53 - DEBUG | received on stderr: 
```
Got to the bottom of it. The signature file needs to have "Minimal" added to the rhel9 signatures list since the minimal iso for Alma has the Packages directory under a folder called Minimal not one called BaseOS.

      "rhel9": {
        "signatures": [
          "BaseOS",
          "Minimal"
        ],
I believe importing the minimal versions of an ISO is not yet supported:
https://github.com/cobbler/cobbler/issues/2623

Have you tried the full image? 
No I have not tried the full image because I only want the minimal one. Anyway, with the change I suggest above it works fine.
Looking at that bug report, I think the exact same fix will solve it. I.e just add 'Minimal' to the signatures list.
#### Cobbler Server Details:
~~~
Rocky Linux release 8.9 (Green Obsidian)
~~~

~~~
[root@localhost rhel9-x86_64]# cobbler --version
Cobbler 3.2.2
  source: ?, ?
  build time: Wed Apr  5 15:54:53 2023
[root@localhost rhel9-x86_64]# 
~~~
 
* OK, so after some investigation identify that, 
* cobbler import is failing due to 
* not having the proper dir structure. 

~~~
[root@localhost distro_mirror]# cobbler import --arch=x86_64  --path=/mnt --name=rhel9
task started: 2024-03-08_075202_import
task started (id=Media import, time=Fri Mar  8 07:52:02 2024)
running python triggers from /var/lib/cobbler/triggers/task/import/pre/*
running shell triggers from /var/lib/cobbler/triggers/task/import/pre/*
shell triggers finished successfully
Found a candidate signature: breed=suse, version=opensuse15.0
Found a candidate signature: breed=suse, version=opensuse15.1
Found a candidate signature: breed=suse, version=opensuse15.2
Found a candidate signature: breed=suse, version=opensuse15.3
Found a candidate signature: breed=suse, version=opensuse15.4
Found a candidate signature: breed=suse, version=sles15generic
No signature matched in /var/www/cobbler/distro_mirror/rhel9-x86_64
Exception occurred: <class 'cobbler.cexceptions.CX'>
Exception value: 'No signature matched in /var/www/cobbler/distro_mirror/rhel9-x86_64'
Exception Info:
  File "/usr/lib/python3.6/site-packages/cobbler/remote.py", line 98, in run
    rc = self._run(self)

  File "/usr/lib/python3.6/site-packages/cobbler/remote.py", line 286, in runner
    self.logger

  File "/usr/lib/python3.6/site-packages/cobbler/api.py", line 1476, in import_tree
    import_module.run(path, mirror_name, network_root, autoinstall_file, arch, breed, os_version)

  File "/usr/lib/python3.6/site-packages/cobbler/modules/managers/import_signatures.py", line 195, in run
    raise CX(error_msg)

!!! TASK FAILED !!!
[root@localhost distro_mirror]# 
~~~


~~~
[root@localhost distro_mirror]# ls -lhrt /var/www/cobbler/distro_mirror/rhel9-x86_64/
total 4.0K
drwxrwxr-x 2 root root 4.0K Apr 13  2023 isolinux
drwxrwxr-x 3 root root   18 Apr 13  2023 EFI
drwxrwxr-x 3 root root   59 Apr 13  2023 images
[root@localhost distro_mirror]# 
~~~

* Checking the signature

~~~
      "rhel9": {
        "signatures": [
          "BaseOS"
~~~

* Do not see BaseOS dir in the distros. 

* RHEL-9 do not have minimal iso
* RHEL-9 comes with 2 iso:- 
  * boot.iso
  * dvd.iso
* boot.iso do not have BaseOS or Minimal dir structure. 

#### WORKAROUND

* Mount DVD and Copy 
* BaseOS and Minimal dir. 
* Import should work 

~~~
[root@localhost rhel9-x86_64]# ls
BaseOS  EFI  images  isolinux  minimal
[root@localhost rhel9-x86_64]# 
~~~

~~~
[root@localhost rhel9-x86_64]# cobbler import --arch=x86_64  --path=/mnt --name=rhel9
task started: 2024-03-08_080347_import
task started (id=Media import, time=Fri Mar  8 08:03:47 2024)
running python triggers from /var/lib/cobbler/triggers/task/import/pre/*
running shell triggers from /var/lib/cobbler/triggers/task/import/pre/*
shell triggers finished successfully
Found a candidate signature: breed=redhat, version=rhel8
Found a candidate signature: breed=redhat, version=rhel9
Found a matching signature: breed=redhat, version=rhel9
Adding distros from path /var/www/cobbler/distro_mirror/rhel9-x86_64:
creating new distro: rhel9-x86_64
trying symlink: /var/www/cobbler/distro_mirror/rhel9-x86_64 -> /var/www/cobbler/links/rhel9-x86_64
creating new profile: rhel9-x86_64
associating repos
checking for rsync repo(s)
checking for rhn repo(s)
checking for yum repo(s)
starting descent into /var/www/cobbler/distro_mirror/rhel9-x86_64 for rhel9-x86_64
processing repo at : /var/www/cobbler/distro_mirror/rhel9-x86_64/BaseOS
directory /var/www/cobbler/distro_mirror/rhel9-x86_64/BaseOS is missing xml comps file, skipping
processing repo at : /var/www/cobbler/distro_mirror/rhel9-x86_64/minimal
directory /var/www/cobbler/distro_mirror/rhel9-x86_64/minimal is missing xml comps file, skipping
*** TASK COMPLETE ***
[root@localhost rhel9-x86_64]#
~~~

@NileshChandekar I'm assuming your tests were with the RHEL9 boot ISO, which is simply not supported (yet?) in cobbler.  I would suggest filing a separate request for that.  I'll just note that after playing with this myself some yesterday, I'm not sure it really possible to make use of an imported RHEL boot ISO.  Install will fail because there is no .treeinfo or repositories defined.  I was able to hack that up manually as well, but I'm not sure that is something that cobbler really should support itself.

Resolving patch:
diff --git a/cobbler/modules/managers/import_signatures.py b/cobbler/modules/managers/import_signatures.py
--- a/cobbler/modules/managers/import_signatures.py
+++ b/cobbler/modules/managers/import_signatures.py
@@ -19,7 +19,7 @@
 02110-1301  USA
 """
 
-from typing import Dict, List, Callable, Any, Optional
+from typing import Dict, List, Callable, Any, Optional, Tuple
 
 import glob
 import gzip
@@ -126,7 +126,11 @@ def get_file_lines(self, filename: str):
                     return f.readlines()
             except:
                 pass
-        if ftype.mime_type == "application/x-ms-wim":
+        if (
+            ftype.mime_type == "application/x-ms-wim"
+            or self.file_version < (5, 37)
+            and filename.lower().endswith(".wim")
+        ):
             cmd = "/usr/bin/wiminfo"
             if os.path.exists(cmd):
                 cmd = "%s %s" % (cmd, filename)
@@ -143,6 +147,30 @@ def get_file_lines(self, filename: str):
             )
         return []
 
+    def get_file_version(self) -> Tuple[int, int]:
+        """
+        This calls file and asks for the version number.
+
+        :return: The major file version number.
+        """
+        cmd = "/usr/bin/file"
+        if os.path.exists(cmd):
+            cmd = f"{cmd} -v"
+            version_list = utils.subprocess_get(cmd).splitlines()
+            if len(version_list) < 1:
+                return (0, 0)
+            version_list = version_list[0].split("-")
+            if len(version_list) != 2 or version_list[0] != "file":
+                return (0, 0)
+            version_list = version_list[1].split(".")
+            if len(version_list) < 2:
+                return (0, 0)
+            version: List[int] = []
+            for v in version_list:
+                version.append(int(v))
+            return (version[0], version[1])
+        return (0, 0)
+
     def run(self, path: str, name: str, network_root=None, autoinstall_file=None, arch: Optional[str] = None,
             breed=None, os_version=None):
         """
@@ -163,6 +191,7 @@ def run(self, path: str, name: str, network_root=None, autoinstall_file=None, ar
         self.arch = arch
         self.breed = breed
         self.os_version = os_version
+        self.file_version: Tuple[int, int] = self.get_file_version()
 
         self.path = path
         self.rootdir = path
@@ -195,79 +224,12 @@ def run(self, path: str, name: str, network_root=None, autoinstall_file=None, ar
 
         # now walk the filesystem looking for distributions that match certain patterns
         self.logger.info("Adding distros from path %s:", self.path)
+        if self.breed == "windows":
+            self.import_winpe()
+
         distros_added = []
         import_walker(self.path, self.distro_adder, distros_added)
 
-        if len(distros_added) == 0:
-            if self.breed == "windows":
-                cmd_path = "/usr/bin/wimexport"
-                bootwim_path = os.path.join(self.path, "sources", "boot.wim")
-                dest_path = os.path.join(self.path, "boot")
-                if os.path.exists(cmd_path) and os.path.exists(bootwim_path):
-                    winpe_path = os.path.join(dest_path, "winpe.wim")
-                    if not os.path.exists(dest_path):
-                        utils.mkdir(dest_path)
-                    rc = utils.subprocess_call([cmd_path, bootwim_path, "1",
-                                               winpe_path, "--boot"], shell=False)
-                    if rc == 0:
-                        cmd = ["/usr/bin/wimdir %s 1 | /usr/bin/grep -i '^/Windows/Boot/PXE$'" % winpe_path]
-                        pxe_path = utils.subprocess_get(cmd, shell=True)[0:-1]
-                        cmd = ["/usr/bin/wimdir %s 1 | /usr/bin/grep -i '^/Windows/System32/config/SOFTWARE$'"
-                               % winpe_path]
-                        config_path = utils.subprocess_get(cmd, shell=True)[0:-1]
-                        cmd_path = "/usr/bin/wimextract"
-                        rc = utils.subprocess_call([cmd_path, bootwim_path, "1",
-                                                    "%s/pxeboot.n12" % pxe_path,
-                                                    "%s/bootmgr.exe" % pxe_path,
-                                                    config_path,
-                                                    "--dest-dir=%s" % dest_path,
-                                                    "--no-acls", "--no-attributes"], shell=False)
-                        if rc == 0:
-                            if HAS_HIVEX:
-                                software = os.path.join(dest_path, os.path.basename(config_path))
-                                h = hivex.Hivex(software, write=True)
-                                root = h.root()
-                                nodes: List[Any] = [root]
-                                pat = "X:\\$windows.~bt"
-
-                                while len(nodes) > 0:
-                                    n = nodes.pop()
-                                    nodes.extend(h.node_children(n))
-
-                                    new_values = []
-                                    update_flag = False
-                                    key_vals = h.node_values(n)
-                                    for key_val in key_vals:
-                                        key = h.value_key(key_val)
-                                        val = h.node_get_value(n, key)
-                                        val_type, val_value = h.value_value(val)
-                                        if pat in key:
-                                            key = key.replace(pat, "X:")
-                                            update_flag = True
-                                        if val_type in (REG_SZ, REG_EXPAND_SZ):
-                                            val_string = h.value_string(val)
-                                            if pat in val_string:
-                                                val_string = val_string.replace(pat, "X:")
-                                                val_value = (val_string + "\0").encode(encoding="utf-16le")
-                                                update_flag = True
-                                        new_val = {"key": key, "t": val_type, "value": val_value,}
-                                        new_values.append(new_val)
-
-                                    if update_flag:
-                                        h.node_set_values(n, new_values)
-                                h.commit(software)
-
-                                cmd_path = "/usr/bin/wimupdate"
-                                rc = utils.subprocess_call([cmd_path, winpe_path, "--command=add %s %s"
-                                                           % (software, config_path)], shell=False)
-                                os.remove(software)
-                            else:
-                                self.logger.info(
-                                    "python3-hivex not found. If you need Automatic Windows "
-                                    "Installation support, please install."
-                                )
-                            import_walker(self.path, self.distro_adder, distros_added)
-
         if len(distros_added) == 0:
             self.logger.warning("No distros imported, bailing out")
             return
@@ -394,6 +356,8 @@ def distro_adder(self, distros_added, dirname: str, fnames):
                     kernel = os.path.join(dirname, x)
                 else:
                     pae_kernel = os.path.join(dirname, x)
+            elif self.breed == "windows" and "wimboot" in self.signature["kernel_file"]:
+                kernel = os.path.join(self.settings.tftpboot_location, "wimboot")
 
             # if we've collected a matching kernel and initrd pair, turn them in and add them to the list
             if initrd is not None and kernel is not None:
@@ -504,21 +468,30 @@ def add_entry(self, dirname: str, kernel, initrd):
             else:
                 new_profile.virt_type = enums.VirtType.KVM
 
-            if self.breed == "windows":
+            if self.breed == "windows":  # type: ignore
                 dest_path = os.path.join(self.path, "boot")
                 kernel_path = f"http://@@http_server@@/images/{name}/wimboot"
                 if new_distro.os_version in ("xp", "2003"):
                     kernel_path = "pxeboot.0"
-                bootmgr_path = os.path.join(dest_path, "bootmgr.exe")
+                bootmgr = "bootmgr.exe"
+                if "wimboot" in kernel:
+                    bootmgr = "bootmgr.efi"
+                bootmgr_path = os.path.join(dest_path, bootmgr)
                 bcd_path = os.path.join(dest_path, "bcd")
                 winpe_path = os.path.join(dest_path, "winpe.wim")
-                if os.path.exists(bootmgr_path) and os.path.exists(bcd_path) and os.path.exists(winpe_path):
-                    new_profile.autoinstall_meta = {"kernel": kernel_path,
-                                                    "bootmgr": "bootmgr.exe",
-                                                    "bcd": "bcd",
-                                                    "winpe": "winpe.wim",
-                                                    "answerfile": "autounattended.xml",
-                                                    "post_install_script": "post_install.cmd"}
+                if (
+                    os.path.exists(bootmgr_path)
+                    and os.path.exists(bcd_path)
+                    and os.path.exists(winpe_path)
+                ):
+                    new_profile.autoinstall_meta = {
+                        "kernel": kernel_path,
+                        "bootmgr": bootmgr,
+                        "bcd": "bcd",
+                        "winpe": "winpe.wim",
+                        "answerfile": "autounattended.xml",
+                        "post_install_script": "post_install.cmd",
+                    }
 
             self.api.add_profile(new_profile, save=True)
 
@@ -914,6 +887,200 @@ def rsync_repo_adder(self, distribution: distro.Distro):
         """
         return
 
+    # ==========================================================================
+    # windows-specific
+
+    def import_winpe(self) -> None:
+        """
+        Preparing a Windows distro for network boot.
+        """
+        winpe_path = self.extract_winpe(self.path)
+
+        # For Windows, file paths are case-insensitive within a WinPE image, but for wimlib-utils
+        # they are not. And wimlib-utils does not provide options for case-insensitive search
+        # and extraction of files from this image other than setting the WIMLIB_IMAGEX_IGNORE_CASE
+        # environment variable.
+        wimdir_result = utils.subprocess_get(
+            ["/usr/bin/wimdir", winpe_path, "1"], shell=False
+        )
+        wim_file_list = wimdir_result.split("\n")
+        file_dict = {
+            "/windows/boot/pxe/pxeboot.n12": "",
+            "/windows/boot/pxe/bootmgr.exe": "",
+            "/windows/boot/efi/bootmgr.efi": "",
+            "/windows/system32/config/software": "",
+        }
+        for wim_file in wim_file_list:
+            if wim_file.lower() in file_dict:
+                file_dict[wim_file.lower()] = wim_file
+
+        file_list = [
+            file_dict["/windows/boot/efi/bootmgr.efi"],
+            file_dict["/windows/system32/config/software"],
+        ]
+        if "wimboot" not in self.signature["kernel_file"]:
+            file_list.extend(
+                [
+                    file_dict["/windows/boot/pxe/pxeboot.n12"],
+                    file_dict["/windows/boot/pxe/bootmgr.exe"],
+                ]
+            )
+
+        self.extract_files_from_wim(winpe_path, file_list)
+        self.update_winpe(winpe_path, file_dict["/windows/system32/config/software"])
+
+    def extract_winpe(self, distro_path: str) -> str:
+        """
+        Extracting winpe.wim from boot.win.
+
+        :param distro_path: The directory where the Windows distro is located.
+        :return: The path to the extracted WinPE image.
+        :raises CX
+        """
+        bootwim_path = os.path.join(distro_path, "sources", "boot.wim")
+        dest_path = os.path.join(distro_path, "boot")
+
+        if not os.path.exists(bootwim_path):
+            error_msg = f"{bootwim_path} not found!"
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        winpe_path = os.path.join(dest_path, "winpe.wim")
+        if not os.path.exists(dest_path):
+            utils.mkdir(dest_path)
+        if os.path.exists(winpe_path):
+            utils.rmfile(winpe_path)
+        if (
+            utils.subprocess_call(
+                ["/usr/bin/wimexport", bootwim_path, "1", winpe_path, "--boot"],
+                shell=False,
+            )
+            != 0
+        ):
+            error_msg = f"Cannot extract {winpe_path} from {bootwim_path}!"
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        return winpe_path
+
+    def update_winpe(self, winpe_path: str, win_registry: str) -> None:
+        """
+        Update WinPE image for network boot.
+
+        :param winpe_path: The path to WinPE image.
+        :param win_registry: The path to the Windows registry in the WinPE image.
+        :raises CX
+        """
+        if not HAS_HIVEX:
+            error_msg = (
+                "python3-hivex not found. If you need Automatic Windows "
+                "Installation support, please install."
+            )
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        software = os.path.join(
+            os.path.dirname(winpe_path), os.path.basename(win_registry).lower()
+        )
+        hivex_obj = hivex.Hivex(software, write=True)  # type: ignore
+        nodes: List[Any] = [hivex_obj.root()]  # type: ignore
+
+        while len(nodes) > 0:
+            node = nodes.pop()
+            nodes.extend(hivex_obj.node_children(node))  # type: ignore
+            self.reg_node_update(hivex_obj, node)
+
+        hivex_obj.commit(software)  # type: ignore
+
+        utils.subprocess_call(
+            [
+                "/usr/bin/wimupdate",
+                winpe_path,
+                f"--command=add {software} {win_registry}",
+            ],
+            shell=False,
+        )
+        os.remove(software)
+
+    def reg_node_update(self, hivex_obj: Any, node: Any) -> None:
+        """
+        Replacement of the substring X:\\$windows.~bt with X: in the Windows registry node.
+
+        :param hivex_obj: Hivex object.
+        :param node: The registry node.
+        """
+        new_values: List[Optional[Dict[str, Any]]] = []
+        update_flag = False
+        key_vals: List[Any] = hivex_obj.node_values(node)  # type: ignore
+        pat = "X:\\$windows.~bt"
+
+        for key_val in key_vals:
+            key: str = hivex_obj.value_key(key_val)  # type: ignore
+            val = hivex_obj.node_get_value(node, key)  # type: ignore
+            val_type: int
+            val_value: bytes
+            val_type, val_value = hivex_obj.value_value(val)  # type: ignore
+            if pat in key:
+                key = key.replace(pat, "X:")
+                update_flag = True
+            if val_type in (REG_SZ, REG_EXPAND_SZ):
+                val_string: str = hivex_obj.value_string(val)  # type: ignore
+                if pat in val_string:
+                    val_string = val_string.replace(pat, "X:")
+                    val_value = (val_string + "\0").encode(encoding="utf-16le")
+                    update_flag = True
+            new_values.append(
+                {
+                    "key": key,
+                    "t": val_type,
+                    "value": val_value,
+                }
+            )
+
+        if update_flag:
+            hivex_obj.node_set_values(node, new_values)  # type: ignore
+
+    def extract_files_from_wim(self, winpe_path: str, wim_files: List[str]) -> None:
+        """
+        Extracting files from winpe.win.
+
+        :param winpe_path: The path to the WinPE image.
+        :param wim_files: The list of files to extract.
+        :raises CX
+        """
+        dest_path = os.path.dirname(winpe_path)
+        cmd_args = [
+            "/usr/bin/wimextract",
+            winpe_path,
+            "1",
+        ]
+        cmd_args.extend(wim_files)
+        cmd_args.extend(
+            [
+                f"--dest-dir={dest_path}",
+                "--no-acls",
+                "--no-attributes",
+            ]
+        )
+        if (
+            utils.subprocess_call(
+                cmd_args,
+                shell=False,
+            )
+            != 0
+        ):
+            error_msg = f'Cannot extract "{wim_files}" files from {winpe_path}!'
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        for wim_file_path in wim_files:
+            wim_file = os.path.basename(wim_file_path)
+            if wim_file != wim_file.lower():
+                os.rename(
+                    os.path.join(dest_path, wim_file),
+                    os.path.join(dest_path, wim_file.lower()),
+                )
+
 
 # ==========================================================================
 
diff --git a/cobbler/tftpgen.py b/cobbler/tftpgen.py
--- a/cobbler/tftpgen.py
+++ b/cobbler/tftpgen.py
@@ -1131,7 +1131,11 @@ def generate_ipxe(self, what: str, name: str) -> str:
             return ""
 
         result = self.write_pxe_file(None, system, profile, distro, arch, image, format='ipxe')
-        return "" if not result else result
+        if not result:
+            return ""
+        result_split = result.splitlines(True)
+        result_split[0] = "#!ipxe\n"
+        return "".join(result_split)
 
     def generate_bootcfg(self, what: str, name: str) -> str:
         """


Test patch:
diff --git a/system-tests/tests/svc-ipxe-profile b/system-tests/tests/svc-ipxe-profile
--- a/system-tests/tests/svc-ipxe-profile
+++ b/system-tests/tests/svc-ipxe-profile
@@ -13,7 +13,7 @@ cobbler profile add --name fake --distro fake
 cobbler system add --name testbed --profile fake --image fakeimage
 # Prepare expected result
 cat >${tmp}/a <<-EOF
-:fake
+#!ipxe
 kernel /images/fake/vmlinuz   initrd=initrd.magic
 initrd /images/fake/initramfs
 boot