From d12d62621ccea345797ba85be19d731ed011fa58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Karpi=C5=84ski?= Date: Fri, 14 Feb 2020 21:30:55 +0100 Subject: [PATCH] 1.0 First complete & working version. Not tested with multiple computers. --- .gitignore | 4 + Dockerfile | 45 +++++++++++ Readme.md | 79 +++++++++++++++++++ dhcp/Dockerfile | 8 -- linux/Makefile | 41 ++++++++++ linux/Readme.md | 18 +++++ linux/src/etc/fstab | 1 + linux/src/etc/initramfs-tools/modules | 1 + .../scripts/local-top/download.sh | 8 ++ linux/src/etc/systemd/logind.conf | 2 + linux/src/lib/systemd/system/getty@.service | 58 ++++++++++++++ linux/src/scripts/functions.sh | 41 ++++++++++ linux/src/scripts/menu.sh | 31 ++++++++ {dhcp => src}/dhcpd.conf | 3 +- {dhcp => src}/run.sh | 6 +- src/supervisor.conf | 17 ++++ src/tftpboot/pxelinux.cfg/default | 7 ++ 17 files changed, 360 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Readme.md delete mode 100644 dhcp/Dockerfile create mode 100644 linux/Makefile create mode 100644 linux/Readme.md create mode 100644 linux/src/etc/fstab create mode 100644 linux/src/etc/initramfs-tools/modules create mode 100644 linux/src/etc/initramfs-tools/scripts/local-top/download.sh create mode 100644 linux/src/etc/systemd/logind.conf create mode 100644 linux/src/lib/systemd/system/getty@.service create mode 100644 linux/src/scripts/functions.sh create mode 100644 linux/src/scripts/menu.sh rename {dhcp => src}/dhcpd.conf (64%) rename {dhcp => src}/run.sh (64%) create mode 100644 src/supervisor.conf create mode 100644 src/tftpboot/pxelinux.cfg/default diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8dcb472 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +linux/build/ +files/initrd.img +files/vmlinuz +files/root.squashfs diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3906bb4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +FROM debian:buster + +RUN apt update +RUN apt install -y make debootstrap supervisor isc-dhcp-server tftpd-hpa busybox squashfs-tools + +# Downloads base Debian 10 to /linux/build/cache. The Makefile in linux/ would +# download base debian system to linux/build/cache itself, but to speed up +# rebuilding this Docker container the download is run separately to avoid +# repeating it in future builds. +ADD linux /linux +RUN mkdir -p linux/build && debootstrap buster linux/build/cache + +# Copies src/tftpboot directory to /tftpboot +ADD src/tftpboot /tftpboot + +# Downloads Syslinux and moves ldlinux.c32 and pxelinux.0 files to /tftpboot +RUN wget https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz && \ + tar -xzvf syslinux-6.03.tar.gz && \ + cp syslinux-6.03/bios/core/pxelinux.0 /tftpboot/pxelinux.0 && \ + cp syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32 /tftpboot/ldlinux.c32 + +# Builds Linux with its Makefile +RUN cd /linux && make + +# Creates and populates /tftpboot directory. Files stored there will be served +# by TFTP and HTTP (on port 8014). +RUN cp /linux/build/rootfs/initrd.img /tftpboot/initrd.img && \ + cp /linux/build/rootfs/vmlinuz /tftpboot/vmlinuz && \ + mksquashfs /linux/build/rootfs /tftpboot/root.squashfs + +# Supervisor's config +COPY src/supervisor.conf /supervisor.conf + +# DHCP server config +RUN mkdir -p /var/lib/dhcp && touch /var/lib/dhcp/dhcpd.leases +COPY src/dhcpd.conf /etc/dhcp/dhcpd.conf + +# The run.sh script setups networking and starts supervisor. +COPY src/run.sh /run.sh +RUN chmod +x /run.sh + +# Interface on which the server will be listening. Set with `-e INTERFACE=eth1` +ENV INTERFACE=eth0 + +CMD ["/run.sh"] diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..14b9705 --- /dev/null +++ b/Readme.md @@ -0,0 +1,79 @@ +## Overview + +`hugo/images` is a portable, self contained system for distributing +disk images. +It allows to boot connected computers over the network and run a image distribution software on them. + +## Usage + +### Network setup + +This system is meant to be used in physically separated network, with all the computers (including the server) connected to a switch. + +### Building + +To build, just build the docker container from the `Dockerfile`, for example: + +``` +docker build . -t hugo/images +``` + +When run for the first time, the build might take some time because basic Debian image has to be downloaded. On subsequent builds the cache will be used making the build take much less time. + +### Running + +This container has some requirements to work properly: + +- `INTERFACE` environment variable should be set to an interface on which you want the server to operate. You can do this by appending `-e INETRFACE=whatever` to your `docker run` command. + +- The server has to be run in privileged mode, so `--privileged` flag has to be added to the command. + +- No network isolation of any kind should be enabled. You can connect the container directly to host's interface with `--net host` flag. + +An example run command might look like this: + +``` +docker run -e INETRFACE=eth42 --privileged --net host hugo/images +``` + +After starting the server, all PCs connected to the network, when in network boot mode, should boot specialised Linux for cloning and present the user with a menu. + +## Technical details + +The container runs three services: DHCP server, TFTP server and HTTP server (on port 8014, to avoid collisions with HTTP server that can be already running on host system). DHCP server is used to assign IP addresses and point to PXE binary which will continue the boot process, TFTP server is used, as specified in PXE standard, to send basic files needed to boot, and HTTP, as it is much faster than TFTP, is used to transfer whole root filesystem. + +### Boot process + +The boot process of a computer connected to the server consists of the following steps: + +1. The computer searches for DHCP server and asks for an IP address. The server responds (as configured in `src/dhcpd.conf`) with an IP address and the filename of PXE binary. + +2. The PXE binary is downloaded over TFTP. When run, it searches (by trying many names, including `pxelinux.cfg/default`) for PXE config file and downloads it over TFTP. + +3. This config file (`src/pxelinux.cfg/default`) contains filenames of the linux kernel and initial ramdisk, which are downloaded over TFTP. + +4. A network is configured in initrd and full filesystem (as squashfs) is downloaded over HTTP (port 8014) and mounted read-only as `/dev/loop0`. See `linux/src/initramfs-tools/scripts/local-top/download.sh` for more details. + +5. As specified in `linux/src/etc/fstab`, a `tmpfs` is mounted in `/tmp`. + +6. `root` is automatically logged in and it's default shell, the image distribution software (see `linux/scripts`), is started. + +### Linux + +The Linux system being server is based on minimal Debian 10 Buster and boots to a menu allowing to send/receive: +1. MBR of `/dev/sda` (raw copy of first 512 bytes, made by `dd`) +2. Partition table (in `sfdisk` format) +3. Image of `/dev/sda1` partition (in `partimage` format) + +Both sending and receiving is done with UDP broadcast with `udpsender`/`udpreceiver`. Any custom action can be performed with root shell. See `linux/scripts` for more details. + +### Transferring images to/from the server + +You can use `udpreceiver`/`udpsender` to receive or send data to/from your server. For example: +``` +udp-receiver --interface eth0 > save_it_here +``` + +``` +udp-sender --interface eth0 < file_to_send +``` diff --git a/dhcp/Dockerfile b/dhcp/Dockerfile deleted file mode 100644 index 7962d2e..0000000 --- a/dhcp/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM debian:buster -RUN apt update && apt install -y isc-dhcp-server -RUN mkdir -p /var/lib/dhcp && touch /var/lib/dhcp/dhcpd.leases -COPY dhcpd.conf /etc/dhcp/dhcpd.conf -COPY run.sh /run.sh -RUN chmod +x /run.sh -ENV INTERFACE=eth0 -CMD ["/run.sh"] diff --git a/linux/Makefile b/linux/Makefile new file mode 100644 index 0000000..e39fc07 --- /dev/null +++ b/linux/Makefile @@ -0,0 +1,41 @@ +CHROOT_CMD := chroot build/rootfs /bin/bash -c + +FILES = $(shell find src/ -type f -print | sed -e 's|^src/|build/rootfs/|') +FILES_SRC = $(shell find src/ -type f -print) + +all: $(FILES) menu images build/rootfs/initrd.img + +clean: + rm -rf build/rootfs + +menu: build/rootfs | build + $(CHROOT_CMD) "apt install dialog" + $(CHROOT_CMD) "chmod +x /scripts/menu.sh" + $(CHROOT_CMD) "usermod -s /scripts/menu.sh root" + +images: build/rootfs | build + $(CHROOT_CMD) "apt install -y udpcast" + $(CHROOT_CMD) "apt install -y partclone" + $(CHROOT_CMD) "apt install -y lzop strace" + $(CHROOT_CMD) "touch /etc/mtab" + +build/rootfs/initrd.img: build/rootfs $(FILES) | build + chmod +x build/rootfs/etc/initramfs-tools/scripts/local-top/download.sh + $(CHROOT_CMD) "apt update && apt install -y linux-image-4.19.0-*-amd64" + $(CHROOT_CMD) "update-initramfs -c -kall" + +$(FILES): $(FILES_SRC) build/rootfs | build + mkdir -p $(@D) + cp $(shell echo $@ | sed -e 's|^build/rootfs/|src/|') $@ + +build/rootfs: build/cache | build + rm -rf build/rootfs + cp -r build/cache build/rootfs + rm -rf $(FILES) + +build/cache: | build + rm -rf build/cache || true + debootstrap buster build/cache + +build: + mkdir -p build diff --git a/linux/Readme.md b/linux/Readme.md new file mode 100644 index 0000000..18e9f1f --- /dev/null +++ b/linux/Readme.md @@ -0,0 +1,18 @@ +# Linux for cloning + +## About +`images/linux` is Debian 10 system adapted for network booting and sending/receiving disk images via UDP. It is designed to work together with `images/server`. + +On boot, full system image will be downloaded, mounted read only and will become new root (see `src/etc/initramfs-tools`). User will be presented with menu allowing sending/receiving images and root shell access (see `src/scripts`). Whole environment will be read-only, except `/tmp` where writeable `tmpfs` will be mounted. + +## Installation +To build just run: +``` +sudo make +``` +This will download base system to `build/cache`, copy it to `build/rootfs` and continue build there. After successful completion of this command, a fully functional linux root filesystem will be present in `build/rootfs`. + +`debootstrap` is required to download base Linux system. When more advanced (encrypted home etc.) partition setup is used, one might need to remount the partition on which the build is taking place with `exec` and `dev` before running `make`: +``` +sudo mount -i -o remount,exec,dev /home/whatever +``` diff --git a/linux/src/etc/fstab b/linux/src/etc/fstab new file mode 100644 index 0000000..b59f154 --- /dev/null +++ b/linux/src/etc/fstab @@ -0,0 +1 @@ +tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=256M 0 0 diff --git a/linux/src/etc/initramfs-tools/modules b/linux/src/etc/initramfs-tools/modules new file mode 100644 index 0000000..7c1157f --- /dev/null +++ b/linux/src/etc/initramfs-tools/modules @@ -0,0 +1 @@ +squashfs diff --git a/linux/src/etc/initramfs-tools/scripts/local-top/download.sh b/linux/src/etc/initramfs-tools/scripts/local-top/download.sh new file mode 100644 index 0000000..205fad4 --- /dev/null +++ b/linux/src/etc/initramfs-tools/scripts/local-top/download.sh @@ -0,0 +1,8 @@ +#!/bin/sh +. /scripts/functions +configure_networking +wget http://192.168.14.2:8014/root.squashfs +mkdir /mnt +modprobe loop +losetup /dev/loop0 +losetup -r /dev/loop0 /root.squashfs diff --git a/linux/src/etc/systemd/logind.conf b/linux/src/etc/systemd/logind.conf new file mode 100644 index 0000000..736338d --- /dev/null +++ b/linux/src/etc/systemd/logind.conf @@ -0,0 +1,2 @@ +[Login] +NAutoVTs=1 diff --git a/linux/src/lib/systemd/system/getty@.service b/linux/src/lib/systemd/system/getty@.service new file mode 100644 index 0000000..3936fdc --- /dev/null +++ b/linux/src/lib/systemd/system/getty@.service @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Getty on %I +Documentation=man:agetty(8) man:systemd-getty-generator(8) +Documentation=http://0pointer.de/blog/projects/serial-console.html +After=systemd-user-sessions.service plymouth-quit-wait.service getty-pre.target +After=rc-local.service + +# If additional gettys are spawned during boot then we should make +# sure that this is synchronized before getty.target, even though +# getty.target didn't actually pull it in. +Before=getty.target +IgnoreOnIsolate=yes + +# IgnoreOnIsolate causes issues with sulogin, if someone isolates +# rescue.target or starts rescue.service from multi-user.target or +# graphical.target. +Conflicts=rescue.service +Before=rescue.service + +# On systems without virtual consoles, don't start any getty. Note +# that serial gettys are covered by serial-getty@.service, not this +# unit. +ConditionPathExists=/dev/tty0 + +[Service] +# the VT is cleared by TTYVTDisallocate +# The '-o' option value tells agetty to replace 'login' arguments with an +# option to preserve environment (-p), followed by '--' for safety, and then +# the entered username. +ExecStart=-/sbin/agetty --autologin root --noclear %I 38400 linux +Type=idle +Restart=always +RestartSec=0 +UtmpIdentifier=%I +TTYPath=/dev/%I +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +KillMode=process +IgnoreSIGPIPE=no +SendSIGHUP=yes + +# Unset locale for the console getty since the console has problems +# displaying some internationalized messages. +UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION + +[Install] +WantedBy=getty.target +DefaultInstance=tty1 diff --git a/linux/src/scripts/functions.sh b/linux/src/scripts/functions.sh new file mode 100644 index 0000000..911714d --- /dev/null +++ b/linux/src/scripts/functions.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +INTERFACE=eth0 + +RECEIVER="udp-receiver --interface $INTERFACE" +SENDER="udp-sender --interface $INTERFACE" + +pause () { + echo + read -p "Press [Enter] to continue..." + clear +} + +shell () { + clear + bash +} + +recv_dd () { + $RECEIVER | dd of=$1 bs=512 count=1 +} + +recv_sfd () { + $RECEIVER | sfdisk $1 +} + +recv_pc () { + $RECEIVER | lzop -d | partclone.extfs -r -s - -o $1 --logfile /tmp/partclone.log +} + +send_dd () { + dd if=$1 bs=512 count=1 | $SENDER +} + +send_pc () { + partclone.extfs -s $1 -c --logfile /tmp/partclone.log 2>/dev/null | lzop | $SENDER +} + +send_sfd () { + sfdisk -d $1 | $SENDER +} diff --git a/linux/src/scripts/menu.sh b/linux/src/scripts/menu.sh new file mode 100644 index 0000000..edba46a --- /dev/null +++ b/linux/src/scripts/menu.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +dmesg -n 1 + +source $( dirname "${BASH_SOURCE[0]}" )/functions.sh + +dialog --menu "Welcome to hugo/images!" 20 80 10 \ + 1 "Receive image for /dev/sda1 (partclone+lzop)" \ + 2 "Receive partition table for /dev/sda (sfdisk)" \ + 3 "Receive MBR for /dev/sda (dd)" \ + 4 "Send /dev/sda1 image (partclone+lzop)" \ + 5 "Send /dev/sda partition table (sfdisk)" \ + 6 "Send /dev/sda MBR (dd)" \ + 7 "Run root shell" \ + 8 "Reboot" \ + 9 "Poweroff" \ + 2>/tmp/menu + +if [ "$?" != "0" ]; then CHOICE=7; else CHOICE=$(cat /tmp/menu); fi + +case $CHOICE in + 1) recv_pc /dev/sda1; pause ;; + 2) recv_sfd /dev/sda; pause ;; + 3) recv_dd /dev/sda; pause ;; + 4) send_pc /dev/sda1; pause ;; + 5) send_sfd /dev/sda; pause ;; + 6) send_dd /dev/sda; pause ;; + 7) shell ;; + 8) reboot ;; + 9) poweroff ;; +esac diff --git a/dhcp/dhcpd.conf b/src/dhcpd.conf similarity index 64% rename from dhcp/dhcpd.conf rename to src/dhcpd.conf index 3785235..be6f5c8 100644 --- a/dhcp/dhcpd.conf +++ b/src/dhcpd.conf @@ -4,5 +4,6 @@ default-lease-time 600; max-lease-time 7200; subnet 192.168.14.0 netmask 255.255.255.0 { -range 192.168.14.10 192.168.14.250; + range 192.168.14.10 192.168.14.250; + filename "pxelinux.0"; } diff --git a/dhcp/run.sh b/src/run.sh similarity index 64% rename from dhcp/run.sh rename to src/run.sh index dc86f18..303c094 100644 --- a/dhcp/run.sh +++ b/src/run.sh @@ -1,6 +1,10 @@ #!/bin/bash set -e + +# Inteface setup ip addr flush dev $INTERFACE ip addr add 192.168.14.2/24 dev $INTERFACE ip link set dev $INTERFACE up -dhcpd -d --no-pid $INTERFACE + +# Supervisor +supervisord -n -c /supervisor.conf diff --git a/src/supervisor.conf b/src/supervisor.conf new file mode 100644 index 0000000..12e7556 --- /dev/null +++ b/src/supervisor.conf @@ -0,0 +1,17 @@ +[supervisord] +loglevel = DEBUG + +[program:dhcp] +command = dhcpd -d --no-pid %(ENV_INTERFACE)s +autostart = true +autorestart = true + +[program:tftp] +command = in.tftpd -L -s /tftpboot +autostart = true +autorestart = true + +[program:http] +command = busybox httpd -f -p 8014 -h /tftpboot +autostart = true +autorestart = true diff --git a/src/tftpboot/pxelinux.cfg/default b/src/tftpboot/pxelinux.cfg/default new file mode 100644 index 0000000..04007f3 --- /dev/null +++ b/src/tftpboot/pxelinux.cfg/default @@ -0,0 +1,7 @@ +DEFAULT linux + +LABEL linux + kernel vmlinuz + append initrd=initrd.img root=/dev/loop0 net.ifnames=0 biosdevname=0 +PROMPT 0 +TIIMEOUT 0