From 50f2a543314d6480bfe855145b07933c04e75d5a Mon Sep 17 00:00:00 2001 From: BluemediaGER Date: Thu, 12 May 2022 23:15:51 +0200 Subject: [PATCH] Initial commit --- README.md | 17 ++++ metal/README.md | 6 ++ metal/ansible.cfg | 4 + metal/group_variables/all.yml | 3 + metal/install-os.yml | 10 +++ metal/inventories/lab.yml | 10 +++ metal/roles/pxe-server/defaults/main.yml | 11 +++ .../roles/pxe-server/files/data/os/.gitignore | 2 + .../pxe-server/files/data/preseed/.gitignore | 2 + .../files/data/pxe-config/.gitignore | 2 + .../pxe-server/files/data/source/.gitignore | 2 + metal/roles/pxe-server/files/dhcp/Dockerfile | 7 ++ .../roles/pxe-server/files/docker-compose.yml | 22 ++++++ .../roles/pxe-server/files/http/default.conf | 11 +++ metal/roles/pxe-server/files/tftp/Dockerfile | 7 ++ metal/roles/pxe-server/tasks/main.yml | 37 +++++++++ .../roles/pxe-server/templates/dhcpd.conf.j2 | 20 +++++ metal/roles/pxe-server/templates/grub.cfg.j2 | 10 +++ .../roles/pxe-server/templates/preseed.cfg.j2 | 78 +++++++++++++++++++ metal/roles/wol-wake/tasks/main.yml | 8 ++ scripts/pxe-log.sh | 7 ++ 21 files changed, 276 insertions(+) create mode 100644 README.md create mode 100644 metal/README.md create mode 100644 metal/ansible.cfg create mode 100644 metal/group_variables/all.yml create mode 100644 metal/install-os.yml create mode 100644 metal/inventories/lab.yml create mode 100644 metal/roles/pxe-server/defaults/main.yml create mode 100644 metal/roles/pxe-server/files/data/os/.gitignore create mode 100644 metal/roles/pxe-server/files/data/preseed/.gitignore create mode 100644 metal/roles/pxe-server/files/data/pxe-config/.gitignore create mode 100644 metal/roles/pxe-server/files/data/source/.gitignore create mode 100644 metal/roles/pxe-server/files/dhcp/Dockerfile create mode 100644 metal/roles/pxe-server/files/docker-compose.yml create mode 100644 metal/roles/pxe-server/files/http/default.conf create mode 100644 metal/roles/pxe-server/files/tftp/Dockerfile create mode 100644 metal/roles/pxe-server/tasks/main.yml create mode 100644 metal/roles/pxe-server/templates/dhcpd.conf.j2 create mode 100644 metal/roles/pxe-server/templates/grub.cfg.j2 create mode 100644 metal/roles/pxe-server/templates/preseed.cfg.j2 create mode 100644 metal/roles/wol-wake/tasks/main.yml create mode 100755 scripts/pxe-log.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..d63f8ec --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Homelab automation + +This project contains the ansible and docker based automation of my homelab. The structure is inspired by [Khue Doan's Homelab Project](https://github.com/khuedoan/homelab). +The repo is currently work in progress. Nothing is tested yet, so everything could burst into flames at any time :) + +## Hardware + +- 4 × Fujitsu Esprimo Q957: + - CPU: `Intel Core i5-7500T @ 2.70 GHz` + - RAM: `16 GB` + - NVMe SSD: `256 GB` + - SATA SSD: `128 GB` +- 1 × Raspberry Pi 4 (4 GB) + +## Current features + +- Fully automated bare metal provisioning of Debian Bullseye using PXE and installer preseed files diff --git a/metal/README.md b/metal/README.md new file mode 100644 index 0000000..9100205 --- /dev/null +++ b/metal/README.md @@ -0,0 +1,6 @@ +# Provision hardware + +- Download and extract Debian Bullseye netboot installer from the official repository. +- Render config files (PXE config, GRUB config, preseed files etc.) from their corresponding [templates](./roles/pxe-server/templates). +- Spin up an PXE environment (DHCP, TFTP and HTTP server) using Docker compose. +- Wake machines using WoL to install the OS via PXE. Machines will auto reboot into the finished preseeded OS after the installation is complete. \ No newline at end of file diff --git a/metal/ansible.cfg b/metal/ansible.cfg new file mode 100644 index 0000000..875abdd --- /dev/null +++ b/metal/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +host_key_checking=false +stdout_callback=debug +stderr_callback=debug \ No newline at end of file diff --git a/metal/group_variables/all.yml b/metal/group_variables/all.yml new file mode 100644 index 0000000..5de0838 --- /dev/null +++ b/metal/group_variables/all.yml @@ -0,0 +1,3 @@ +ansible_user: root +ansible_ssh_private_key_file: ~/.ssh/id_rsa +ssh_public_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" \ No newline at end of file diff --git a/metal/install-os.yml b/metal/install-os.yml new file mode 100644 index 0000000..d61cf0c --- /dev/null +++ b/metal/install-os.yml @@ -0,0 +1,10 @@ +- name: Build PXE environment + hosts: localhost + roles: + - pxe-server + +- name: Provision OS on machines + hosts: metal + gather_facts: false + roles: + - wol-wake \ No newline at end of file diff --git a/metal/inventories/lab.yml b/metal/inventories/lab.yml new file mode 100644 index 0000000..8ec9dfd --- /dev/null +++ b/metal/inventories/lab.yml @@ -0,0 +1,10 @@ +metal: + children: + masters: + hosts: + lab-mini-1: {ansible_host: 192.168.1.21, mac: '4c:52:62:1c:bf:6c', disk: '/dev/nvme0n1'} + lab-mini-2: {ansible_host: 192.168.1.22, mac: '4c:52:62:0f:09:6d', disk: '/dev/nvme0n1'} + lab-mini-3: {ansible_host: 192.168.1.23, mac: '4c:52:62:0f:0a:23', disk: '/dev/nvme0n1'} + workers: + hosts: + lab-mini-4: {ansible_host: 192.168.1.24, mac: '90:1b:0e:f8:e8:af', disk: '/dev/nvme0n1'} \ No newline at end of file diff --git a/metal/roles/pxe-server/defaults/main.yml b/metal/roles/pxe-server/defaults/main.yml new file mode 100644 index 0000000..839ab30 --- /dev/null +++ b/metal/roles/pxe-server/defaults/main.yml @@ -0,0 +1,11 @@ +os_download_url: "https://deb.debian.org/debian/dists/bullseye/main/installer-amd64/current/images/netboot/netboot.tar.gz" +os_download_checksum: "sha256:ec3b71964457f30a57061ea758c12394bf2b792b461c697e61cc2d47053c5878" + +user_fullname: "Lab User" +username: "lab" +domain: "lab.bluemedia.dev" + +subnet: "192.168.6.0" +netmask: "255.255.255.0" +gateway: "192.168.6.1" +nameserver: "192.168.6.1" \ No newline at end of file diff --git a/metal/roles/pxe-server/files/data/os/.gitignore b/metal/roles/pxe-server/files/data/os/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/metal/roles/pxe-server/files/data/os/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/metal/roles/pxe-server/files/data/preseed/.gitignore b/metal/roles/pxe-server/files/data/preseed/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/metal/roles/pxe-server/files/data/preseed/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/metal/roles/pxe-server/files/data/pxe-config/.gitignore b/metal/roles/pxe-server/files/data/pxe-config/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/metal/roles/pxe-server/files/data/pxe-config/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/metal/roles/pxe-server/files/data/source/.gitignore b/metal/roles/pxe-server/files/data/source/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/metal/roles/pxe-server/files/data/source/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/metal/roles/pxe-server/files/dhcp/Dockerfile b/metal/roles/pxe-server/files/dhcp/Dockerfile new file mode 100644 index 0000000..306f5cb --- /dev/null +++ b/metal/roles/pxe-server/files/dhcp/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:latest + +RUN apk add dhcp + +RUN touch /var/lib/dhcp/dhcpd.leases + +CMD [ "dhcpd", "-d", "-f", "-cf", "/etc/dhcp/dhcpd.conf" ] \ No newline at end of file diff --git a/metal/roles/pxe-server/files/docker-compose.yml b/metal/roles/pxe-server/files/docker-compose.yml new file mode 100644 index 0000000..de4915e --- /dev/null +++ b/metal/roles/pxe-server/files/docker-compose.yml @@ -0,0 +1,22 @@ +version: "3" + +services: + dhcp: + build: ./dhcp + volumes: + - ./data/pxe-config/dhcpd.conf:/etc/dhcp/dhcpd.conf + network_mode: host + tftp: + build: ./tftp + network_mode: host + volumes: + - ./data/pxe-config/grub.cfg:/var/lib/tftpboot/grub.cfg + - ./data/os/debian-installer/amd64/grubx64.efi:/var/lib/tftpboot/grubx64.efi + - ./data/os/debian-installer/amd64/initrd.gz:/var/lib/tftpboot/initrd.gz + - ./data/os/debian-installer/amd64/linux:/var/lib/tftpboot/linux + http: + image: nginx:latest + network_mode: host + volumes: + - ./data/os:/usr/share/nginx/html/os:ro + - ./data/preseed:/usr/share/nginx/html/preseed:ro \ No newline at end of file diff --git a/metal/roles/pxe-server/files/http/default.conf b/metal/roles/pxe-server/files/http/default.conf new file mode 100644 index 0000000..e6bd754 --- /dev/null +++ b/metal/roles/pxe-server/files/http/default.conf @@ -0,0 +1,11 @@ +server { + listen 80; + listen [::]:80; + + server_name default; + + root /var/www/html; + location / { + try_files $uri $uri/ =404; + } +} \ No newline at end of file diff --git a/metal/roles/pxe-server/files/tftp/Dockerfile b/metal/roles/pxe-server/files/tftp/Dockerfile new file mode 100644 index 0000000..eac67cc --- /dev/null +++ b/metal/roles/pxe-server/files/tftp/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:latest + +RUN apk add busybox tftp-hpa + +ENTRYPOINT [ "/bin/sh", "-c" ] + +CMD [ "busybox syslogd -n -O /dev/stdout & in.tftpd -vvv --foreground --secure /var/lib/tftpboot" ] \ No newline at end of file diff --git a/metal/roles/pxe-server/tasks/main.yml b/metal/roles/pxe-server/tasks/main.yml new file mode 100644 index 0000000..df99c1c --- /dev/null +++ b/metal/roles/pxe-server/tasks/main.yml @@ -0,0 +1,37 @@ +- name: Download boot image + get_url: + url: "{{ os_download_url }}" + dest: "{{ role_path }}/files/data/source/netboot.tar.gz" + checksum: "{{ os_download_checksum }}" + register: netboot_tar + +- name: Extract boot image + unarchive: + src: "{{ netboot_tar.dest }}" + dest: "{{ role_path }}/files/data/os" + +- name: Generate DHCP config + template: + src: dhcpd.conf.j2 + dest: "{{ role_path }}/files/data/pxe-config/dhcpd.conf" + mode: 0644 + +- name: Generate GRUB config + template: + src: grub.cfg.j2 + dest: "{{ role_path }}/files/data/pxe-config/grub.cfg" + mode: 0644 + +- name: Generate preseed file for each machine + template: + src: preseed.cfg.j2 + dest: "{{ role_path }}/files/data/preseed/{{ hostvars[item]['mac'] }}.cfg" + mode: 0644 + loop: "{{ groups['metal'] }}" + +- name: Start PXE stack + docker_compose: + project_src: "{{ role_path }}/files" + state: present + restarted: true + build: true \ No newline at end of file diff --git a/metal/roles/pxe-server/templates/dhcpd.conf.j2 b/metal/roles/pxe-server/templates/dhcpd.conf.j2 new file mode 100644 index 0000000..b455f5e --- /dev/null +++ b/metal/roles/pxe-server/templates/dhcpd.conf.j2 @@ -0,0 +1,20 @@ +option space pxelinux; +option pxelinux.magic code 208 = string; +option pxelinux.configfile code 209 = text; +option pxelinux.pathprefix code 210 = text; +option pxelinux.reboottime code 211 = unsigned integer 32; +option architecture-type code 93 = unsigned integer 16; + +subnet {{ subnet }} netmask {{ netmask }} { + option routers {{ gateway }}; + range {{ subnet | ansible.netcommon.ipmath(3) }} {{ subnet | ansible.netcommon.ipmath(254) }}; + + class "pxeclients" { + match if substring (option vendor-class-identifier, 0, 9) = "PXEClient"; + next-server {{ ansible_default_ipv4.address }}; + + if option architecture-type = 00:07 { + filename "grubx64.efi"; + } + } +} \ No newline at end of file diff --git a/metal/roles/pxe-server/templates/grub.cfg.j2 b/metal/roles/pxe-server/templates/grub.cfg.j2 new file mode 100644 index 0000000..7e42213 --- /dev/null +++ b/metal/roles/pxe-server/templates/grub.cfg.j2 @@ -0,0 +1,10 @@ +set timeout=1 + +menuentry 'Auto install Debian Bullseye (PXE)' { + set background_color=black + linux linux \ + vga=788 \ + url=http://{{ ansible_default_ipv4.address }}/preseed/${net_default_mac}.conf \ + --- auto quiet + initrd initrd.gz +} \ No newline at end of file diff --git a/metal/roles/pxe-server/templates/preseed.cfg.j2 b/metal/roles/pxe-server/templates/preseed.cfg.j2 new file mode 100644 index 0000000..b662a3d --- /dev/null +++ b/metal/roles/pxe-server/templates/preseed.cfg.j2 @@ -0,0 +1,78 @@ +# For documentation see: https://www.debian.org/releases/stable/example-preseed.txt + +# Set default locale and keyboard layout +d-i debian-installer/locale string {{ locale | default('en_US.UTF-8') }} +d-i keyboard-configuration/xkb-keymap select {{ keyboard_layout | default('de') }} + +# Set network interface used by default +d-i netcfg/choose_interface select auto + +# Static network config +d-i netcfg/disable_autoconfig boolean true +d-i netcfg/dhcp_failed note +d-i netcfg/dhcp_options select Configure network manually + +d-i netcfg/get_ipaddress string {{ hostvars[item]['ansible_host'] }} +d-i netcfg/get_netmask string {{ netmask }} +d-i netcfg/get_gateway string {{ gateway }} +d-i netcfg/get_nameservers string {{ nameserver }} +d-i netcfg/confirm_static boolean true + +# These values will be overwritten if set by dhcp, but the entries will get rid of the correscponding questions +d-i netcfg/get_hostname string {{ hostvars[item]['inventory_hostname'] | default('unassigned-hostname') }} +d-i netcfg/get_domain string {{ domain | default('unassigned-domain') }} + +# Force hostname regarding of value set by dhcp +d-i netcfg/hostname string {{ hostvars[item]['inventory_hostname'] | default('unassigned-hostname') }} + +# Load non-free firmware for hardware by default +d-i hw-detect/load_firmware boolean true + +# Setup package mirrror +d-i mirror/protocol string {{ mirror_proto | default('http') }} +d-i mirror/country string manual +d-i mirror/http/hostname string {{ mirror | default('deb.debian.org') }} +d-i mirror/http/directory string {{ mirror_dir | default('/debian') }} +d-i mirror/http/proxy string {{ mirror_proxy | default('') }} + +# Disable root user - normal user (see below) will have sudo permissions +d-i passwd/root-login boolean false + +# Create new user +d-i passwd/user-fullname string {{ user_fullname | default('Debian User') }} +d-i passwd/username string {{ username | default('debian') }} +d-i passwd/user-password password {{ password | default('insecure') }} +d-i passwd/user-password-again password {{ password | default('insecure') }} + +# Setup timezone and NTP server +d-i clock-setup/utc boolean true +d-i time/zone string {{ timezone | default('UTC') }} +d-i clock-setup/ntp-server string {{ ntp_server | default('de.pool.ntp.org') }} + +# Autoformat disk +d-i partman-auto/disk string {{ hostvars[item]['disk'] | default('/dev/sda') }} +d-i partman-auto/method string regular +d-i partman-auto/choose_recipe select atomic +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true + +# Remove install cd sources from /etc/sources.list +d-i apt-setup/cdrom/set-first boolean false +d-i apt-setup/disable-cdrom-entries boolean true + +# Install openssh-server and basic system tools +d-i pkgsel/run_tasksel boolean false +d-i pkgsel/include string openssh-server build-essential +d-i pkgsel/upgrade select safe-upgrade + +# Disable package reporting +popularity-contest popularity-contest/participate boolean false + +# Install grub to specified device +d-i grub-installer/only_debian boolean true +d-i grub-installer/bootdev string {{ hostvars[item]['disk'] | default('/dev/sda') }} + +# Reboot to installed system without confirmation +d-i finish-install/reboot_in_progress note \ No newline at end of file diff --git a/metal/roles/wol-wake/tasks/main.yml b/metal/roles/wol-wake/tasks/main.yml new file mode 100644 index 0000000..935f3f2 --- /dev/null +++ b/metal/roles/wol-wake/tasks/main.yml @@ -0,0 +1,8 @@ +- name: Send magic packets + community.general.wakeonlan: + mac: "{{ hostvars[inventory_hostname]['mac'] }}" + delegate_to: localhost + +- name: Wait for machines to come online with installed OS + wait_for_connection: + timeout: 600 \ No newline at end of file diff --git a/scripts/pxe-log.sh b/scripts/pxe-log.sh new file mode 100755 index 0000000..b99425b --- /dev/null +++ b/scripts/pxe-log.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +docker-compose \ + --project-directory ./metal/roles/pxe-server/files/ \ + logs \ + --f \ + ${@} \ No newline at end of file