aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md103
-rw-r--r--collections/requirements.yml3
-rw-r--r--deploy.yml109
-rw-r--r--docs/sample-environment.yml25
-rw-r--r--iso.yml42
-rw-r--r--templates/bootcblade-iso.sh.j215
-rw-r--r--templates/bootcblade-rebuild.service.j26
-rw-r--r--templates/bootcblade-rebuild.timer.j210
-rw-r--r--templates/bootcblade.config.toml.j25
-rw-r--r--templates/bootcblade.containerfile.j224
-rw-r--r--templates/centos-bootc-deploy.sh.j29
-rw-r--r--update.yml15
13 files changed, 367 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8b5fba1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+inventories
diff --git a/README.md b/README.md
index 56a6f0f..e10cf9d 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,106 @@
Ansible automation for deploying a KVM hypervisor using bootc on CentOS Stream.
![BootcBlade](docs/images/logo.png)
+
+This Ansible automation uses bootc to create "the perfect" KVM hypervisor with ZFS, NFS + Samba, Cockpit, and Sanoid + Syncoid.
+
+## Usage - deploy on top of existing system
+1. Install a fresh CentOS Stream 9 to the desired host - use a minimal install to save disk space on the resulting deployed machine
+2. Install ```podman``` on the host
+3. Generate an SSH key
+4. Create inventory using the example in the ```docs``` folder
+5. Make sure you have passwordless sudo or root access to desired host using your new SSH key
+6. Install needed Ansible collections ```ansible-galaxy install -r collections/requirements.yml```
+7. Run ```ansible-playbook deploy.yml -i inventories/<your_inventory.yml>```
+
+## Usage - create ISO for automatic installation
+1. You will need an existing machine with Podman and Ansible
+2. The Ansible can be run locally or remotely - just be sure to have root or passwordless sudo working
+3. Create inventory for a local or remote run using the example in the ```docs``` folder
+4. Run ```ansible-playbook iso.yml -i inventories/<your_inventory.yml>```
+5. ISO will be created on the local or remote machine, ```/root/bootcblade-output/bootiso/install.iso```
+
+
+## Credentials
+### Deploy
+- The SSH key (for the user) is added for the root user and baked into the image generation - **the resulting BootcBlade image will include your SSH key**
+- The user, ssh key, and password (if defined) are configured after deployment via Ansible
+
+### ISO
+- The user, SSH key, and password (if defined) are baked into the ISO - **the resulting ISO will include your user, SSH key, and hashed password (if defined)**
+
+
+## How It Works
+### Deploy
+1. A new or existing system must exist. This system should be as small as possible because its filesystem will persist in the resulting deployed machine
+2. A "base" centos-bootc is used for the first deploy - the user SSH key is set for the root user now
+3. Once the base is deployed, we use that to build and ```bootc switch``` into the final BootcBlade image
+4. Ansible creates the user with (or without) the password and adds the SSH key
+
+### ISO
+1. An existing system must exist to build the ISO on, no changes to this system are made
+2. Running the Ansible will create the files necessary to generate the ISO - including the user with (or without) password and the SSH key
+3. Resulting ISO is stored in the /root directory
+4. Configuration files and all used container images are deleted
+
+## Updating
+Updates happen in two ways. When ```deploy.yml``` is used, there is a systemd unit created (```bootcblade-rebuild.service```) that is created and will run once a week.
+This service depends on ```/root/bootcblade.containerfile``` to exist, as the container is rebuilt. If the build is successful, then the new container is staged.
+The default update service ```bootc-fetch-apply-updates.service``` is masked as well so it will not run.
+
+If ```deploy.yml``` is not used (perhaps the system was installed using an ISO created by ```iso.yml```), then there is no automated update process
+and the default ```bootc-fetch-apply-updates.service``` is still enabled. If you want the automatic update method, then ```ansible-playbook deploy.yml --tags configure-bootcblade```
+will need to be run, either remotely or as localhost, and the required variables will need to be defined.
+
+## Troubleshooting
+### ```/root/bootcblade.containerfile``` is gone:
+You can use ```update.yml``` to recreate this, assuming you have the correct inventory.
+
+### BootcBlade will no longer build
+It is possible that the upstream ```centos-bootc``` project will change something (the kernel perhaps) that makes ZFS building no longer possible. You can go to [https://quay.io/repository/centos-bootc/centos-bootc?tab=tags](https://quay.io/repository/centos-bootc/centos-bootc?tab=tags) and try specifing an older tag using ```centos_bootc_tag```.
+
+Another possibility is to just wait, ususally these repo related problems work themselves out and the image will build again within a week.
+
+## Variable Usage
+This is a description of each variable, what it does, and a table to determine when it is needed.
+
+- ```create_user```: This user will be created during ```deploy.yml``` and ```iso.yml```
+- ```create_user_password```: This password will be used for the created user
+- ```create_user_ssh_pub```: This is a SSH pubkey that will be added to the created user during ```deploy.yml``` and ```iso.yml```, also it is applied to the root user in ```deploy.yml```
+- ```create_user_shell```: This shell setting will be used for the created user only during ```deploy.yml```
+- ```centos_bootc_tag```: Override the tag for centos-bootc source image for ```deploy.yml```, ```iso.yml```, and ```update.yml```
+- ```bootc_acknowledge```: This setting is only effective when setting it to ```false```, newer versions of ```bootc``` require an acknowledgment during ```deploy.yml``` but older versions break
+if this is defined - so this can override the default and remove that
+- ```ansible_user``` - This is an Ansible variable, useful for connecting to the initial machine with a different user during ```deploy.yml```
+- ```ansible_connection``` - This is an Ansible variable, useful when running Ansible locally with ```iso.yml``` and ```update.yml```
+
+### deploy.yml
+| Variable | Used | Required |
+| -------- | ---- | -------- |
+| create_user | X | - |
+| create_user_password | X | - |
+| create_user_ssh_pub | X | X |
+| create_user_shell | X | - |
+| centos_bootc_tag | X | - |
+| bootc_acknowledge | X | - |
+
+### iso.yml
+| Variable | Used | Required |
+| -------- | ---- | -------- |
+| create_user | X | X|
+| create_user_password | X | - |
+| create_user_ssh_pub | X | X |
+| create_user_shell | - | - |
+| centos_bootc_tag | X | - |
+| bootc_acknowledge | - | - |
+
+### update.yml
+| Variable | Used | Required |
+| -------- | ---- | -------- |
+| create_user | - | - |
+| create_user_password | - | - |
+| create_user_ssh_pub | - | - |
+| create_user_shell | - | - |
+| centos_bootc_tag | X | - |
+| bootc_acknowledge | - | - |
+
diff --git a/collections/requirements.yml b/collections/requirements.yml
new file mode 100644
index 0000000..1c4fcf4
--- /dev/null
+++ b/collections/requirements.yml
@@ -0,0 +1,3 @@
+# ansible-galaxy install -r collections/requirements.yml
+collections:
+ - name: ansible.posix
diff --git a/deploy.yml b/deploy.yml
new file mode 100644
index 0000000..f4ef4f9
--- /dev/null
+++ b/deploy.yml
@@ -0,0 +1,109 @@
+- hosts: all
+ become: true
+ gather_facts: false
+ vars:
+ ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
+
+ tasks:
+ - name: Block for deploy-base-bootc
+ block:
+ - name: Create /root/centos-bootc-deploy.sh
+ ansible.builtin.template:
+ src: "centos-bootc-deploy.sh.j2"
+ dest: "/root/centos-bootc-deploy.sh"
+
+ - name: Create root ssh authorized keys
+ ansible.posix.authorized_key:
+ user: "root"
+ key: "{{ create_user_ssh_pub }}"
+ state: present
+
+ - name: Run bootc deployment for basic centos-bootc environment
+ ansible.builtin.shell: "bash /root/centos-bootc-deploy.sh"
+
+ - name: Reboot into basic centos-bootc environment
+ ansible.builtin.reboot:
+ reboot_timeout: 1
+ ignore_errors: true
+ tags: deploy-base-bootc
+
+ - name: Block for deploy-bootcblade
+ block:
+ - name: Wait for connectivity to basic centos-bootc environment
+ ansible.builtin.wait_for_connection:
+
+ - name: Create /root/BootcBlade.containerfile
+ ansible.builtin.template:
+ src: "bootcblade.containerfile.j2"
+ dest: "/root/bootcblade.containerfile"
+
+ - name: Build BootcBlade container image
+ ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile"
+
+ - name: Run bootc-switch into BootcBlade image
+ ansible.builtin.shell: "bootc switch --transport containers-storage localhost/bootcblade:latest"
+
+ - name: Reboot into BootcBlade environment
+ ansible.builtin.reboot:
+ vars:
+ ansible_user: "root"
+ tags: deploy-bootcblade
+
+ - name: Block for configure-bootcblade
+ block:
+ - name: Create user
+ ansible.builtin.user:
+ name: "{{ create_user }}"
+ groups: "wheel"
+ append: true
+ shell: "{{ create_user_shell if create_user_shell is defined else '/bin/bash' }}"
+ when: create_user is defined and create_user_password is not defined
+
+ - name: Create user (with password)
+ ansible.builtin.user:
+ name: "{{ create_user }}"
+ groups: "wheel"
+ append: true
+ shell: "{{ create_user_shell if create_user_shell is defined else '/bin/bash' }}"
+ password: "{{ create_user_password | password_hash('sha512') }}"
+ when: create_user is defined and create_user_password is defined
+
+ - name: Create user ssh authorized keys
+ ansible.posix.authorized_key:
+ user: "{{ create_user }}"
+ key: "{{ create_user_ssh_pub }}"
+ state: present
+ when: (create_user is defined) and (create_user_ssh_pub is defined)
+
+ - name: Add bootcblade-rebuild.service and .timer files for automatic update
+ ansible.builtin.template:
+ src: "{{ item }}.j2"
+ dest: "/etc/systemd/system/{{ item }}"
+ loop:
+ - "bootcblade-rebuild.service"
+ - "bootcblade-rebuild.timer"
+
+ - name: Enable and start bootcblade-rebuild services
+ ansible.builtin.systemd_service:
+ name: "{{ item.name }}"
+ state: "{{ item.state }}"
+ enabled: "{{ item.enabled }}"
+ daemon-reload: true
+ loop:
+ - { name: "bootcblade-rebuild.service", state: "stopped", enabled: false }
+ - { name: "bootcblade-rebuild.timer", state: "started", enabled: true }
+
+ - name: Stop and disable (mask) bootc-fetch-apply-updates
+ ansible.builtin.systemd_service:
+ name: "{{ item }}"
+ state: "stopped"
+ enabled: false
+ masked: true
+ daemon-reload: true
+ loop:
+ - "bootc-fetch-apply-updates.timer"
+ - "bootc-fetch-apply-updates.service"
+ vars:
+ ansible_user: "root"
+ tags: configure-bootcblade
+
diff --git a/docs/sample-environment.yml b/docs/sample-environment.yml
new file mode 100644
index 0000000..18e65b0
--- /dev/null
+++ b/docs/sample-environment.yml
@@ -0,0 +1,25 @@
+# See README.md to determine which variables are needed for which activity.
+
+all:
+ hosts:
+ remote-machine:
+ create_user_ssh_pub: "ssh-rsa <ssh key>"
+# This is the bare minimum required - no users will be created, this key will be used for root only.
+
+ remote-machine2:
+ ansible_user: "root"
+ create_user: "spmfox"
+ create_user_password: "password"
+ create_user_shell: "/usr/bin/fish"
+ create_user_ssh_pub: "ssh-rsa <ssh key>"
+# This will create a user with a custom shell defined, and use the same SSH key for the user and root. User will also have a password.
+# Root account is used for the initial connection during deployment on an existing machine.
+
+ localhost:
+ ansible_connection: local
+ create_user: "spmfox"
+ create_user_ssh_pub: "ssh-rsa <ssh key>"
+ centos_bootc_tag: "stream9-1714747911"
+ bootc_acknowledge: false
+# This is a local run only, useful for creating ISOs and perhaps running update.yml. The centos-bootc:tag can be specified here, using an earlier version.
+# Because this version is before the need for an acknowledgement during bootc deployment, we can override and remove that from the command.
diff --git a/iso.yml b/iso.yml
new file mode 100644
index 0000000..52b49e7
--- /dev/null
+++ b/iso.yml
@@ -0,0 +1,42 @@
+- hosts: all
+ become: true
+ gather_facts: false
+
+ tasks:
+ - name: Create /root/bootcblade-output folder
+ ansible.builtin.file:
+ path: "/root/bootcblade-output"
+ state: directory
+ tags: setup
+
+ - name: Create files
+ ansible.builtin.template:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ loop:
+ - { src: "bootcblade.config.toml.j2", dest: "/root/bootcblade.config.toml" }
+ - { src: "bootcblade.containerfile.j2", dest: "/root/bootcblade.containerfile" }
+ - { src: "bootcblade-iso.sh.j2", dest: "/root/bootcblade-iso.sh" }
+ tags: setup
+
+ - name: Build BootcBlade container image
+ ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile"
+ tags: build-container
+
+ - name: Create BootcBlade ISO
+ ansible.builtin.shell: "bash /root/bootcblade-iso.sh"
+ tags: build-iso
+
+ - name: Cleanup files
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - "/root/bootcblade.config.toml"
+ - "/root/bootcblade.containerfile"
+ - "/root/bootcblade-iso.sh"
+ tags: cleanup
+
+ - name: Cleanup images
+ ansible.builtin.shell: "podman image rm localhost/bootcblade ; podman image rm quay.io/centos-bootc/bootc-image-builder ; podman image rm quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }} ; podman image prune -f"
+ tags: cleanup
diff --git a/templates/bootcblade-iso.sh.j2 b/templates/bootcblade-iso.sh.j2
new file mode 100644
index 0000000..0d276e9
--- /dev/null
+++ b/templates/bootcblade-iso.sh.j2
@@ -0,0 +1,15 @@
+#!/bin/bash
+podman run \
+ --rm \
+ -it \
+ --privileged \
+ --pull=newer \
+ --security-opt label=type:unconfined_t \
+ -v /root/bootcblade.config.toml:/config.toml \
+ -v /root/bootcblade-output:/output \
+ -v /var/lib/containers/storage:/var/lib/containers/storage \
+ quay.io/centos-bootc/bootc-image-builder:latest \
+ --type anaconda-iso \
+ --rootfs xfs \
+ --local \
+ localhost/bootcblade:latest
diff --git a/templates/bootcblade-rebuild.service.j2 b/templates/bootcblade-rebuild.service.j2
new file mode 100644
index 0000000..525978a
--- /dev/null
+++ b/templates/bootcblade-rebuild.service.j2
@@ -0,0 +1,6 @@
+[Unit]
+Description=BootcBlade rebuild service
+
+[Service]
+ExecStart=/usr/bin/podman build -t localhost/bootcblade -f /root/bootcblade.containerfile
+ExecStartPost=/usr/bin/bash -c "/usr/bin/sleep 10 ; /usr/bin/bootc update && /usr/bin/podman image prune -f"
diff --git a/templates/bootcblade-rebuild.timer.j2 b/templates/bootcblade-rebuild.timer.j2
new file mode 100644
index 0000000..cffd275
--- /dev/null
+++ b/templates/bootcblade-rebuild.timer.j2
@@ -0,0 +1,10 @@
+[Unit]
+Description=bootcblade-rebuild timer
+
+[Timer]
+OnBootSec=60min
+OnUnitActiveSec=1w
+
+
+[Install]
+WantedBy=timers.target
diff --git a/templates/bootcblade.config.toml.j2 b/templates/bootcblade.config.toml.j2
new file mode 100644
index 0000000..2087b70
--- /dev/null
+++ b/templates/bootcblade.config.toml.j2
@@ -0,0 +1,5 @@
+[[customizations.user]]
+name = "{{ create_user }}"
+{{ 'password = "' if create_user_password is defined else ''}}{{ create_user_password if create_user_password is defined else '' }}{{ '"' if create_user_password is defined else '' }}
+key = "{{ create_user_ssh_pub }}"
+groups = ["wheel"]
diff --git a/templates/bootcblade.containerfile.j2 b/templates/bootcblade.containerfile.j2
new file mode 100644
index 0000000..7e5ade6
--- /dev/null
+++ b/templates/bootcblade.containerfile.j2
@@ -0,0 +1,24 @@
+FROM quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }}
+RUN mkdir /var/roothome
+RUN echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel-passwordless-sudo
+RUN dnf -y install epel-release && \
+ dnf -y install https://zfsonlinux.org/epel/zfs-release-2-3$(rpm --eval "%{dist}").noarch.rpm && \
+ dnf -y install kernel-devel-$(ls /usr/lib/modules) && \
+ dnf -y install zfs && \
+ dkms build zfs/$(rpm -q --qf '%{VERSION}' zfs) -k $(ls /usr/lib/modules) && \
+ dkms install zfs/$(rpm -q --qf '%{VERSION}' zfs) -k $(ls /usr/lib/modules)
+RUN dnf -y install vim git podman fish ansible wget && \
+ echo "qemu:x:107:107:qemu user:/:/sbin/nologin" >> /etc/passwd && \
+ dnf -y install qemu-kvm libvirt virt-install virt-viewer && \
+ dnf -y install cockpit cockpit-bridge cockpit-file-sharing cockpit-machines cockpit-pcp cockpit-podman cockpit-storaged cockpit-system
+RUN git clone https://github.com/45drives/cockpit-zfs-manager.git /root/cockpit-zfs-manager && \
+ cp -r /root/cockpit-zfs-manager/zfs /usr/share/cockpit && \
+ rm -r /root/cockpit-zfs-manager
+RUN git clone https://github.com/jimsalterjrs/sanoid.git /root/sanoid && \
+ cd /root/sanoid && git checkout $(git tag | grep "^v" | tail -n 1) && cp sanoid syncoid findoid sleepymutex /usr/local/sbin && \
+ mkdir /etc/sanoid && cp sanoid.defaults.conf /etc/sanoid && touch /etc/sanoid/sanoid.conf && cp sanoid.conf /etc/sanoid/sanoid.example.conf && \
+ rm -r /root/sanoid
+RUN dnf -y install perl-Data-Dumper perl-Getopt-Long lzop mbuffer mhash pv && \
+ PERL_MM_USE_DEFAULT=1 cpan install Capture::Tiny && PERL_MM_USE_DEFAULT=1 cpan install Config::IniFiles
+RUN dnf -y install firewalld && \
+ systemctl enable firewalld
diff --git a/templates/centos-bootc-deploy.sh.j2 b/templates/centos-bootc-deploy.sh.j2
new file mode 100644
index 0000000..2083295
--- /dev/null
+++ b/templates/centos-bootc-deploy.sh.j2
@@ -0,0 +1,9 @@
+#!/bin/bash
+podman run --rm --privileged \
+ --pid=host --security-opt label=type:unconfined_t \
+ --volume /dev:/dev \
+ --volume /var/lib/containers:/var/lib/containers \
+ --volume /:/target \
+ --entrypoint bootc \
+ quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }} \
+ install to-filesystem --skip-fetch-check --replace=alongside /target --root-ssh-authorized-keys /target/root/.ssh/authorized_keys {{ '' if bootc_acknowledge is false else '--acknowledge-destructive' }}
diff --git a/update.yml b/update.yml
new file mode 100644
index 0000000..9d23f7a
--- /dev/null
+++ b/update.yml
@@ -0,0 +1,15 @@
+- hosts: all
+ become: true
+ gather_facts: false
+
+ tasks:
+ - name: Create /root/BootcBlade.containerfile
+ ansible.builtin.template:
+ src: "bootcblade.containerfile.j2"
+ dest: "/root/bootcblade.containerfile"
+
+ - name: Build BootcBlade container image
+ ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile"
+
+ - name: Run bootc-update to apply newly built image
+ ansible.builtin.shell: "bootc update"