{"id":13928890,"url":"https://github.com/midzelis/zquickinit","last_synced_at":"2025-04-09T17:14:36.576Z","repository":{"id":202444329,"uuid":"707485200","full_name":"midzelis/zquickinit","owner":"midzelis","description":"Batteries included ZFSBootMenu based bootloader and rescue environment; with a focus on secure remote unlocks over Tailscale SSH.","archived":false,"fork":false,"pushed_at":"2024-12-09T01:57:59.000Z","size":3071,"stargazers_count":39,"open_issues_count":1,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T17:14:30.764Z","etag":null,"topics":["boot-menu","bootloader","initramfs","installer","mkinitcpio","proxmox","rescue","tailscale","vpn","zfs","zfsbootmenu","zpool"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/midzelis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-20T02:19:38.000Z","updated_at":"2025-04-03T17:29:59.000Z","dependencies_parsed_at":"2023-11-14T02:40:53.543Z","dependency_job_id":"142dde71-7ab0-4982-b9f5-8616fddc5277","html_url":"https://github.com/midzelis/zquickinit","commit_stats":null,"previous_names":["midzelis/zquickinit"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/midzelis%2Fzquickinit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/midzelis%2Fzquickinit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/midzelis%2Fzquickinit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/midzelis%2Fzquickinit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/midzelis","download_url":"https://codeload.github.com/midzelis/zquickinit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248074924,"owners_count":21043490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boot-menu","bootloader","initramfs","installer","mkinitcpio","proxmox","rescue","tailscale","vpn","zfs","zfsbootmenu","zpool"],"created_at":"2024-08-07T18:01:58.525Z","updated_at":"2025-04-09T17:14:36.569Z","avatar_url":"https://github.com/midzelis.png","language":"Shell","readme":"# ZQuickInit - ZFS Quick Init, Rescue and Install initramfs customizeable image\n\nA distribution based on ZFSBootMenu to securely remote boot, rescue, and install Proxmox. This is an ***opinionated***, but secure, and easy to use [ZFSBootMenu](https://zfsbootmenu.org)-based distribution. (maybe the first?)\n\n### Too long, didn't read. \n\nFrom curl to VM Playgroud in 60 seconds...\n[![asciicast](https://asciinema.org/a/CRDW6ZLC2naPBWPn2qVjb7DAS.svg)](https://asciinema.org/a/CRDW6ZLC2naPBWPn2qVjb7DAS)\n\nInteractive customization...\n\u003ca href=\"https://asciinema.org/a/DSsDkrvLEO02lO5JNXrViG5Vq\" target=\"_blank\"\u003e\u003cimg src=\"https://asciinema.org/a/DSsDkrvLEO02lO5JNXrViG5Vq.svg\" /\u003e\u003c/a\u003e\n\nPartition, Encrypt and Install Proxmox...\n[![asciicast](https://asciinema.org/a/d7tSkr6pKpEX1NkjQ3rX5jOuN.svg)](https://asciinema.org/a/d7tSkr6pKpEX1NkjQ3rX5jOuN)\n\n\nFirst boot of Proxmox on encrypted partition, after install...\n[![asciicast](https://asciinema.org/a/Imru2MRFdKK4MxScGqNTxnNRj.svg)](https://asciinema.org/a/Imru2MRFdKK4MxScGqNTxnNRj)\n\nLet me try! \n```\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/midzelis/zquickinit/main/zquickinit.sh)\"\n```\n\n## Features\n### NAT-piercing remote unlocks using Tailscale\n`ZQuickInit` builds upon the foundations provided by `ZFSBootMenu` with missing functions. Noteably, being able to access a ZFS-based box that exists in an untrusted environment behind a NAT with no UPNP or NAT-PMP services. \n\nThe box will use an encrypted root filesystem. The box itself may be stolen, so the encryption keys should not be stored in SecureBoot or TPM. In order to descrypt the drive, `ZFSBootMenu` will prompt to load they key at startup. However, if this server has been restarted, it may be inconvient for the operator to enter the password. \n\nThis is solved by configuring the network interfaces and also adding Tailscale (SSH) to the `ZFSBootMenu` configuration. Tailscale will allow remote access and will allow accessing the box behind the NAT. Tailscale also allows us to totally lock down the network side of things as well - there will be no listening ports on the local network interfaces at all - all of server will be running on the internal Tailscale interface. \n\nHowever, `ZFSBootMenu` chainloads the selected rootfilesystem after prompting for the decryption key. It boots the decrypted target kernel/initramfs - but this environment no longer has the original decryption key - and you'd have to enter the encryption key again. \n\nReprompting may just be merely inconvenient if you were in front of the box. But it is doubly inconvenient since this secondary kernel/initramfs environment would not have tailscale installed or even any network configuration at all. So this will stall out the remote boot process. \n\n### Securily passing key from `ZQuickInit`/ZBM environment into chained kernel/initramfs\n`ZQuickInit` fixes this by 'injecting' the password into selected initramfs image. This takes advantage of the fact that Linux Kernel will continue to decompress initramfs images if it finds another initramfs image after the first one. So, to 'inject' the password, `ZQuickInit` literally just creates a new compressed initramfs image and concatenates it with the selected initramfs image, and then boots that using the kexec. \n\nUnfortunately, even with recent improvements to `ZFSBootMenu` (the loadkey.d and botsel hooks), I wasn't able to convincingly implement this behavior. `ZQuickInit` patches the entire `load_key` and `kexec_kernel` functions to implement this behavior. \n\n### Bare Metal Installs\n`ZFSBootMenu` is great because it is a fully featured (although significantly trimmed) linux distribution. However, I found it lacking for my purposes. \n\nInstead of ending with secure decryption of the root filesystem - I saw potential as the basis of a full rescue utility, and installer for Proxmox to help bootstrap new machines. \n\n`ZQuickInit` can be started with no pools at all. In fact, you can drop the EFI on any FAT32 formatted USB stick and boot from it. You will be guided through a series of questions that will help you partition, create an (encrypted) pool, create a root dataset, configure a ESP partition (with `ZQuickInit` installed on it) and finally a way to Install Proxmox into the new root dataset. \n\n`ZquickInit` can also be used to convert a previously unecrypted ZFS root partition into an encrypted one. \n\n### Rescue Functions\n`ZQuickInit` adds several useful programs that are useful when running a networked boot, specifally with disk/partitioning tasks in mind. This includes accessing LVM partitions, and ext3/4 and [ex]FAT[32] partitions. \n\nTo rescue or install, image a USB drive, or copy the EFI to a USB stick. Or, my favorite, use [Ventoy](https://www.ventoy.net/en/index.html) to boot the EFI. \n### `tmux` by default\nSince `ZQuickInit` is designed to be run with SSH, and the console may also be active, a shared `tmux` session is created between the local console and the remote SSH. \n\n### Quality of life improvements\nGetting `tmux` running and also making the Installer and the various prompts pretty required a lot of trial and error to figure out keyboard mappings, unicode support, serial console, QEMU configuration, and so so many things. I'm proud to say that beatiful, colorful, unicode interfaces are supported however you access the `ZQuickinit` - local console, SSH console, or via QEMU (vnc, or serial console). \n\n### Easy to use!\n`ZFSBootMenu` has a lot of documentation. A lot. Its generally useful but slow to get started. It took me weeks to figure everything out. I hope that `ZQuickInit` helps make ZFSBootMenu easier to try and use. \n\n#### mkinitcpio only\nTo support this goal, I make a lot of opinionated choices. Instead of supporting dracut and mkinitcpio, I choose to drop dracut. (I started with it at first, but it was hard to use, and slow, and cumbersome. Mkinitcpio is better in every single possible way - in my opinion!) \n\n#### OCI builds using Void Linux only\n`ZFSBootMenu` supports being built by almost any Linux distribution under the sun. I choose to drop all of these, and stick with just one: Void Linux. I never used this distro before, but I was pleasently surprised by it. Really, I chose it because it was the default distribution used by the Dockerfiles within `ZFSBootMenu`. `ZQuickInit` is exclusivesly build via Docker (or Podman, once they have support for `syntax=docker/dockerfile:1`)\n\n#### Recipes\nSo I structured all of my \"packages\" that built on top of `ZFSBootMenu` into a 'recipe' format. This is a simple convention-based package structure. First, it lives under `/recipes` in the source tree. \n\nIt contains an optional `recipe.yaml` metadata file. This file lists 2 things: \n1. the xbps dependencies\n2. help text\n\nIt contains an optional `setup.sh` file. This file is invoked while the `ZQuickInit` is being assembled/built. The main point of this file is to load additional data (configuration, secrets) into the image. \n\nThen it creates a directory called `initcpio` this folder contains all of the 'hooks' for `mkinitcpio` - most importantly you'll want to use `install` hook. The `run` hook is used by `ZFSBootMenu` itself, and probably shouldn't be used. Rather, you should use the existing hooks that `ZFSBootMenu` exposes to add functionality. \n\n# Getting started\n\nYou know how I said that the Linux Kernel will continue decompressing any cpio images found after the first one? Well, you can leverage this for `ZQuickInit` itself! \n\nThat right, you don't even need to build anything to use `ZQuickInit` - you can just download, and 'inject' your secrets of customizations directly into that image and then use it. \n\n## zquickinit.sh\nThe main script is runnable directly from curl/bash. However, if you want more customization, or just hate this trend of random github repos asking you to blindly execute scripts from weird URLs, you can also just clone this repo and run it that way. \n\n### Running from curl: \n```\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/midzelis/zquickinit/main/zquickinit.sh)\"\n```\n\nIt will ask you if you want to download the EFI image, or run a \"playground\" that will boot the image in a QEMU VM. \n\n### Building from source\n```\ngit clone https://github.com/midzelis/zquickinit.git\ncd zquickinit\n./zquickinit.sh # this will show help screen\n```\n\n### Help Manual\n#### zquickinit.sh\n    ZQuickInit image functions!\n\n####  Usage\n    zquickinit.sh initramfs [--noask]\n    zquickinit.sh inject [target_efi] [source_efi]\n    zquickinit.sh iso [target_iso] [source_efi]\n    zquickinit.sh playground [--sshonly]\n\n####  Advanced Usage\n    zquickinit.sh builder\n    zquickinit.sh tailscale\n\n####  Commands\n    initramfs     Build a zquickinit.efi image using docker or podman.\n    builder       Advanced: Create the OCI builder image for ZQuickInit\n    tailscale     Login to tailscale and save to tailscaled.conf\n    iso           Build an iso image (for playground, or to write to USB)\n    playground    Start a QEMU VM on the zquickinit.sh image in order to\n                  play around with zquickinit.efi\n\n####  Options\n    --noask       Do not ask any questions for building image.\n                  Just use files in the current directory, if present.\n    --release     Do not add QEMU debug\n    -d,--debug    Advanced: Turn on tracing\n    -e,--enter    Advanced: Do not build an image. Execute bash and\n                  enter the builder image.\n    source_efi    The zquickinit source image or download from GitHub image\n                  if not specified.\n    target_efi    Where the results of the image after injection will\n                  be stored. Default is zquickinit.efi in the current folder\n\n### How to build\n\n1. First, you need to build the builder, which is the Void Linux based Docker image used to run mkinicpio. \n```bash\n./zquickinit.sh builder\n```\n2. Then, you need to build ZQuickInit (this will be interactive), you can instead just use local files in the current directory instead, by passing `--noask`\n```bash\n./zquickinit.sh initramfs\n```\n3. When your ready to test, you can start the Playground in a QEMU VM. If you are going to be trying out the Proxmox installer (zquick_installer.sh) you can save bandwith by creating a folder `cache` in the top level source folder. This will be shared using 9p to the guest and will be used to cache downloaded apt packages. \n```bash\n./zquickinit.sh playground\n```\n\n## Architecture\n\n## Goals\n- Secure\n- Easy to build, customize\n- Remote access to enter/load encryption keys\n- Box must work behind NAT \n- Support Proxmox as a first class environment\n- Rescue/Install scenarios\n## Non-Goals\n- Speed\n  - it boots in less than \u003c5 seconds (in QEMU at least)\n    - it doesn't need to be faster\n- Size \n  - its currently producing images around ~80MB\n    - could be smaller, but its 2023. \n- Memory efficiency \n  - the initramfs is loaded into RAM at least twice\n    - but I don't care ;-)\n\n## Security Considerations\n**Important!**\n\nAnything you put in an initramfs image like `ZFSBootMenu` will be stored unencrypted. What this means is that you should treat these keys as untrusted. \n\nFor Tailscale, you should create a new node dedicated to `ZFSBootMenu`, and then configure ACLs to only allow incoming access to that node. Note: even tho this node won't be able to access the rest of your network, Tailscale will leak information about the status and tailnet IPs of the nodes on your network. \n\n### To create a new tailscale node: \n```bash\nzquickinit.sh tailscale\n```\n\nHere's an example ACL: \n```jsonc\n{\n\t\"nodeAttrs\": [\n\t\t{\n\t\t\t\"target\": [\"tag:allowfunnel\"],\n\t\t\t\"attr\":   [\"funnel\"],\n\t\t},\n\t],\n\t\"tagOwners\": {\n\t\t\"tag:incomingonly\": [\"autogroup:admin\"],\n\t\t\"tag:allaccess\":    [\"autogroup:admin\"],\n\t\t\"tag:allowfunnel\":  [\"autogroup:admin\"],\n\t},\n\t\"acls\": [\n\t\t# only allow nodes tagged with allaccess to egress\n\t\t{\"action\": \"accept\", \"src\": [\"tag:allaccess\"], \"dst\": [\"*:*\"]},\n\t\t# to allow webttyd ingress\n\t\t{\"action\": \"accept\", \"src\": [\"*\"], \"dst\": [\"*:80\"]}\n\t],\n\t\"ssh\": [\n\t\t{\n\t\t\t\"action\": \"accept\",\n\t\t\t\"src\":    [\"tag:allaccess\"],\n\t\t\t\"dst\":    [\"tag:incomingonly\"],\n\t\t\t\"users\":  [\"autogroup:nonroot\", \"root\"],\n\t\t},\n\t\t{\n\t\t\t\"action\": \"accept\",\n\t\t\t\"src\":    [\"tag:allaccess\"],\n\t\t\t\"dst\":    [\"tag:allaccess\"],\n\t\t\t\"users\":  [\"autogroup:nonroot\", \"root\"],\n\t\t},\n\t],\n}\n\n```\n## Recipes\n\n### zquick_core\nRequired dependencies, you can't remove this. \n\n### zquick_loadkey\n * Patches `zfsbootmenu-core.sh`\n   * Prompts to unlock encrypted ZFS dataset. \n   * Then injects the passphrase into the boot environment to prevent prompting twice.\n \n### zquick_net\nDefines Hooks: \n  * `/zquick/hooks/ifup.d`\n  * `/zquick/hooks/ifdown.d`\n\nUses Hooks: \n  * `/zquick/hooks/reboot.d`\n    * Brings down interfaces\n  * `/libexec/hooks/early-setup.d`\n    * Starts network interfaces/DHCP\n\n### zquick_tailscale\n  * Installs `tailscale` configured as SSH server\n\nUses Hooks: \n  * `/zquick/hooks/ifup.d`\n\t* Starts tailscale\n  * `/zquick/hooks/ifdown.d`\n\t* Kills ssh sessions\n\nConfig Files: \n* `tailscaled.state` -\u003e `/var/lib/tailscale/tailscaled.state`\n* `tailscaled.conf` -\u003e `/etc/tailscale/tailscaled.conf`\n\n### zquick_sshd\n  * Installs `sshd` configured as a server running on a local interface\n\nUses Hooks: \n  * `/zquick/hooks/ifup.d`\n\t* Starts sshd\n\nConfig Files: \n* `ssh_host_ed25519_key` -\u003e `/etc/ssh/ssh_host_ed25519_key`\n* `ssh_host_ecdsa_key` -\u003e `/etc/ssh/ssh_host_ecdsa_key`\n* `ssh_host_rsa_key` -\u003e `/etc/ssh/ssh_host_rsa_key`\n* `authorized_keys` -\u003e `/root/.ssh/authorized_keys`\n\n### zquick_tmux\n  * Patches `zfsbootmenu-preinit.sh` to run the entire `zfsbootmenu` script within a `tmux` session. \n  * The tmux status bar will display status like hostname, tailnet name, and webttyd status. \n\nUses Hooks: \n  * `/zquick/hooks/bash.d`\n    * Starting bash will automatically join the existing session (excluding nested sessions).\n \n### zquick_ttyd\n  * Runs a web-based `ttyd` over tailscale [funnel](https://tailscale.com/kb/1223/tailscale-funnel/). \n  * Wait, what? You serious??  \n\t* Its \"secure-enough\" in my opinion\n\t* Generates a random 32 character URL\n\t* Sets up `ttyd` to only respond to that URL\n\t* Send push notification to your phone using [Pushover](https://pushover.net/)\n\t* Notification has a link to random URL\n\t* `ttyd` server will be killed after 30 minutes\n\nUses Hooks: \n  * `/zquick/hooks/ifup.d`\n  * `/zquick/hooks/ifdown.d`\n\nConfig Properties (in zquickinit.conf) \n`\nPUSHOVER_APP_TOKEN=aabbccddaabbccddaabbccdd\nPUSHOVER_USER_KEY=aabbccddaabbccddaabbccdd\n`\n\n### zquick_installer\n  * Provides script `zquick_installer.sh` \n    * Partitioning\n    * Creating ZFS root pools/datasets\n    * Installs proxmox\n  * Encrypting existing uncrypted ZFS root pool/dataset\n\n### zquick_qemu\n  * Should only be used for testing. Not present in release image. \n  * Will mount `cache` and `.` to `/mnt/cache` and `/mnt/qemu-host` over 9p filesystem\n\n### zquick_reboot\nDefines Hooks: \n  * `/zquick/hooks/reboot.d`\n* Reboot command. \n  * Unmounts filesystems. \n  * Invokes hooks in `/zquick/hooks/reboot.d*`\n\n### zquick_hostname\nSets hostname.\n\nConfig files: \n  * `hostname` -\u003e `/etc/hostname`\n\n### zquick_editors\n  * vim\n  * nano\n\n### zquick_netextras\n  * ssh\n  * sshd\n  * curl\n  * wget\n  * nc\n  * w\n  * nmap\n  * ncat\n\n### zquick_consolefont\n  * Sets font to ter-v16b, local console only.\n\n### zquick_fsextras\nBunch of filesystem related utilities, including partitioning, efibootmgr, disk usage, etc. \n","funding_links":[],"categories":["others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmidzelis%2Fzquickinit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmidzelis%2Fzquickinit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmidzelis%2Fzquickinit/lists"}