├── 01-disk-wipe ├── Dockerfile └── wipe.sh ├── 03-install-root-fs ├── Dockerfile └── root-fs.sh ├── 05-cloud-init ├── Dockerfile └── cloud-init.sh ├── 06-post-install ├── Dockerfile └── post-install.sh ├── 02-disk-partition ├── Dockerfile ├── partition.sh └── cpr.sh ├── 07-cleanup ├── Dockerfile ├── grub.default └── cleanup.sh ├── 04-install-grub ├── Dockerfile ├── grub.default ├── install.sh_original ├── install.sh ├── grub-installer.sh └── repos.sh ├── generate.sh ├── create_images.sh ├── 00-base ├── get-package-list.sh ├── Dockerfile ├── requirements.txt └── functions.sh ├── ubuntu.tmpl ├── hardware.json ├── hw1.json ├── LICENSE └── README.md /01-disk-wipe/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY wipe.sh / 4 | ENTRYPOINT ["./wipe.sh"] 5 | 6 | -------------------------------------------------------------------------------- /03-install-root-fs/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY root-fs.sh / 4 | ENTRYPOINT ["./root-fs.sh"] 5 | 6 | -------------------------------------------------------------------------------- /05-cloud-init/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY cloud-init.sh / 4 | ENTRYPOINT ["./cloud-init.sh"] 5 | 6 | -------------------------------------------------------------------------------- /06-post-install/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY post-install.sh / 4 | ENTRYPOINT ["./post-install.sh"] 5 | 6 | -------------------------------------------------------------------------------- /02-disk-partition/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY cpr.sh / 4 | COPY partition.sh / 5 | ENTRYPOINT ["./partition.sh"] 6 | 7 | -------------------------------------------------------------------------------- /07-cleanup/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY grub.default /tmp/ 4 | COPY cleanup.sh / 5 | ENTRYPOINT ["./cleanup.sh"] 6 | 7 | -------------------------------------------------------------------------------- /04-install-grub/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY 2 | FROM $REGISTRY/ubuntu:base 3 | COPY repos.sh / 4 | COPY grub.default / 5 | COPY grub-installer.sh / 6 | COPY install.sh / 7 | ENTRYPOINT ["./install.sh"] 8 | 9 | -------------------------------------------------------------------------------- /generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export UUID=$(uuidgen|tr "[:upper:]" "[:lower:]") 4 | export MAC=b8:59:9f:e0:f6:8c 5 | cat hardware.json | envsubst > hw1.json 6 | 7 | echo wrote hw1.json - $UUID 8 | 9 | 10 | -------------------------------------------------------------------------------- /07-cleanup/grub.default: -------------------------------------------------------------------------------- 1 | GRUB_DEFAULT=0 2 | GRUB_HIDDEN_TIMEOUT=0 3 | GRUB_HIDDEN_TIMEOUT_QUIET=true 4 | GRUB_TIMEOUT=10 5 | GRUB_DISTRIBUTOR=$GRUB_DISTRIBUTOR 6 | GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX" 7 | GRUB_TERMINAL=serial 8 | GRUB_SERIAL_COMMAND="$GRUB_SERIAL_COMMAND" 9 | -------------------------------------------------------------------------------- /04-install-grub/grub.default: -------------------------------------------------------------------------------- 1 | GRUB_DEFAULT=0 2 | GRUB_HIDDEN_TIMEOUT=0 3 | GRUB_HIDDEN_TIMEOUT_QUIET=true 4 | GRUB_TIMEOUT=10 5 | GRUB_DISTRIBUTOR=$GRUB_DISTRIBUTOR 6 | GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX" 7 | GRUB_TERMINAL=serial 8 | GRUB_SERIAL_COMMAND="$GRUB_SERIAL_COMMAND" 9 | -------------------------------------------------------------------------------- /07-cleanup/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cprout=/statedir/cpr.json 4 | rootuuid=$(jq -r .rootuuid $cprout) 5 | [[ -n $rootuuid ]] 6 | cmdline=$(sed -nr 's|GRUB_CMDLINE_LINUX='\''(.*)'\''|\1|p' /tmp/grub.default) 7 | 8 | kexec -l ./kernel --initrd=./initrd --command-line="BOOT_IMAGE=/boot/vmlinuz root=UUID=$rootuuid ro $cmdline" || reboot 9 | kexec -e || reboot 10 | 11 | -------------------------------------------------------------------------------- /create_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t 192.168.1.1/ubuntu:base 00-base/ 4 | docker push 192.168.1.1/ubuntu:base 5 | 6 | docker build -t 192.168.1.1/disk-wipe:v1 01-disk-wipe/ --build-arg REGISTRY=192.168.1.1 7 | docker push 192.168.1.1/disk-wipe:v1 8 | 9 | docker build -t 192.168.1.1/disk-partition:v1 02-disk-partition/ --build-arg REGISTRY=192.168.1.1 10 | docker push 192.168.1.1/disk-partition:v1 11 | 12 | docker build -t 192.168.1.1/install-root-fs:v1 03-install-root-fs/ --build-arg REGISTRY=192.168.1.1 13 | docker push 192.168.1.1/install-root-fs:v1 14 | 15 | docker build -t 192.168.1.1/install-grub:v1 04-install-grub/ --build-arg REGISTRY=192.168.1.1 16 | docker push 192.168.1.1/install-grub:v1 17 | 18 | docker build -t 192.168.1.1/cloud-init:v1 05-cloud-init/ --build-arg REGISTRY=192.168.1.1 19 | docker push 192.168.1.1/cloud-init:v1 20 | -------------------------------------------------------------------------------- /06-post-install/post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO: ephemeral data or environment 4 | target="/mnt/target" 5 | BASEURL='http://192.168.1.2/misc/osie/current' 6 | 7 | # Adjust failsafe delays for first boot delay 8 | if [[ -f $target/etc/init/failsafe.conf ]]; then 9 | sed -i 's/sleep 59/sleep 10/g' $target/etc/init/failsafe.conf 10 | sed -i 's/Waiting up to 60/Waiting up to 10/g' $target/etc/init/failsafe.conf 11 | fi 12 | 13 | wget $BASEURL/ubuntu_18_04/packet-block-storage-attach -P / 14 | wget $BASEURL/ubuntu_18_04/packet-block-storage-detach -P / 15 | 16 | echo -e "${GREEN}#### Run misc post-install tasks${NC}" 17 | install -m755 -o root -g root /packet-block-storage-* $target/usr/bin 18 | if [ -f $target/usr/sbin/policy-rc.d ]; then 19 | echo "Removing policy-rc.d from target OS." 20 | rm -f $target/usr/sbin/policy-rc.d 21 | fi 22 | 23 | -------------------------------------------------------------------------------- /00-base/get-package-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case $(uname -m) in 4 | aarch64) grub_efi_arch=arm64 ;; 5 | x86_64) grub_efi_arch=amd64 ;; 6 | *) echo "unknown arch" && exit 1 ;; 7 | esac 8 | 9 | # shellcheck disable=SC2206 10 | packages=( 11 | binutils 12 | curl 13 | dmidecode 14 | dosfstools 15 | efibootmgr 16 | ethtool 17 | file 18 | gdisk 19 | git 20 | grub-efi-$grub_efi_arch-bin 21 | grub2-common 22 | hdparm 23 | inetutils-ping 24 | ipmitool 25 | iproute2 26 | jq 27 | mdadm 28 | mstflint 29 | parted 30 | pciutils 31 | pv 32 | python3 33 | sg3-utils 34 | smartmontools 35 | unzip 36 | vim 37 | wget 38 | xmlstarlet 39 | ) 40 | echo "${packages[@]}" 41 | 42 | [[ $(uname -m) == 'x86_64' ]] && echo 'grub-pc-bin' 43 | 44 | -------------------------------------------------------------------------------- /ubuntu.tmpl: -------------------------------------------------------------------------------- 1 | version: '0.1' 2 | name: ubuntu_provisioning 3 | global_timeout: 6000 4 | tasks: 5 | - name: "os-installation" 6 | worker: "{{.device_1}}" 7 | volumes: 8 | - /dev:/dev 9 | - /dev/console:/dev/console 10 | - /lib/firmware:/lib/firmware:ro 11 | actions: 12 | - name: "disk-wipe" 13 | image: disk-wipe:v1 14 | timeout: 90 15 | - name: "disk-partition" 16 | image: disk-partition:v1 17 | timeout: 180 18 | environment: 19 | MIRROR_HOST: 192.168.1.1 20 | volumes: 21 | - /statedir:/statedir 22 | - name: "install-root-fs" 23 | image: install-root-fs:v1 24 | timeout: 600 25 | environment: 26 | MIRROR_HOST: 192.168.1.2 27 | - name: "install-grub" 28 | image: install-grub:v1 29 | timeout: 600 30 | environment: 31 | MIRROR_HOST: 192.168.1.2 32 | volumes: 33 | - /statedir:/statedir 34 | - name: "cloud-init" 35 | image: cloud-init:v1 36 | timeout: 600 37 | environment: 38 | MIRROR_HOST: 192.168.1.2 39 | volumes: 40 | - /statedir:/statedir 41 | -------------------------------------------------------------------------------- /00-base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | COPY functions.sh / 3 | COPY requirements.txt /tmp/osie/ 4 | COPY get-package-list.sh git-lfs-linux-*-v2.5.1-4-g2f166e02 /tmp/osie/ 5 | RUN apt-get update -y && \ 6 | apt-get install -y $(/tmp/osie/get-package-list.sh) && \ 7 | apt-get -qy autoremove && \ 8 | apt-get -qy clean 9 | 10 | RUN apt-get -qy update && \ 11 | apt-get install -y \ 12 | build-essential \ 13 | libxml2-dev \ 14 | libxslt1-dev \ 15 | python3-dev \ 16 | python3-pip \ 17 | python3-setuptools \ 18 | python3-wheel \ 19 | systemd \ 20 | zlib1g-dev && \ 21 | pip3 install -r /tmp/osie/requirements.txt && \ 22 | pip3 install --upgrade setuptools && \ 23 | apt-get remove -y \ 24 | build-essential \ 25 | libxml2-dev \ 26 | libxslt1-dev \ 27 | python3-dev \ 28 | python3-pip \ 29 | python3-setuptools \ 30 | python3-wheel \ 31 | zlib1g-dev && \ 32 | apt-get -qy autoremove && \ 33 | apt-get -qy clean && \ 34 | rm -rf /var/lib/apt/lists/* /tmp/osie ~/.cache/pip* 35 | -------------------------------------------------------------------------------- /03-install-root-fs/root-fs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | ephemeral=/workflow/data.json 7 | os=$(jq -r .os "$ephemeral") 8 | tag=$(jq -r .tag "$ephemeral") 9 | 10 | target="/mnt/target" 11 | OS=$os${tag:+:$tag} 12 | 13 | if ! [[ -f /statedir/disks-partioned-image-extracted ]]; then 14 | ## Fetch install assets via git 15 | assetdir=/tmp/assets 16 | mkdir $assetdir 17 | echo -e "${GREEN}#### Fetching image for ${OS//_(arm|image)} root fs ${NC}" 18 | OS=${OS%%:*} 19 | 20 | # Image rootfs 21 | image="$assetdir/image.tar.gz" 22 | 23 | # TODO: should come as ENV 24 | BASEURL="http://$MIRROR_HOST/misc/osie/current" 25 | 26 | # custom 27 | wget "$BASEURL/$os/image.tar.gz" -P $assetdir 28 | 29 | mkdir -p $target 30 | mount -t ext4 /dev/sda3 $target 31 | echo -e "${GREEN}#### Retrieving image and installing to target $target ${NC}" 32 | tar --xattrs --acls --selinux --numeric-owner --same-owner --warning=no-timestamp -zxpf "$image" -C $target 33 | echo -e "${GREEN}#### Success installing root fs ${NC}" 34 | fi 35 | echo "####restore fstab and double check fstab content" 36 | cp /mnt/target/etc/fstab_backup /mnt/target/etc/fstab 37 | echo "$(cat /mnt/target/etc/fstab)" 38 | -------------------------------------------------------------------------------- /01-disk-wipe/wipe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | # defaults 7 | # shellcheck disable=SC2207 8 | disks=($(lsblk -dno name -e1,7,11 | sed 's|^|/dev/|' | sort)) 9 | 10 | stimer=$(date +%s) 11 | 12 | # Look for active MD arrays 13 | # shellcheck disable=SC2207 14 | mdarrays=($(awk '/md/ {print $4}' /proc/partitions)) 15 | if ((${#mdarrays[*]} != 0)); then 16 | for mdarray in "${mdarrays[@]}"; do 17 | echo "MD array: $mdarray" 18 | mdadm --stop "/dev/$mdarray" 19 | # sometimes --remove fails, according to manpages seems we 20 | # don't need it / are doing it wrong 21 | mdadm --remove "/dev/$mdarray" || : 22 | done 23 | fi 24 | 25 | # Wipe the filesystem and clear block on each block device 26 | for bd in "${disks[@]}"; do 27 | sgdisk -Z "$bd" & 28 | done 29 | for bd in "${disks[@]}"; do 30 | # -n is so that wait will return on any job finished, returning it's exit status. 31 | # Without the -n, wait will only report exit status of last exited process 32 | wait -n 33 | done 34 | 35 | if [[ -d /sys/firmware/efi ]]; then 36 | for bootnum in $(efibootmgr | sed -n '/^Boot[0-9A-F]/ s|Boot\([0-9A-F]\{4\}\).*|\1|p'); do 37 | efibootmgr -Bb "$bootnum" 38 | done 39 | fi 40 | 41 | echo "Disk wipe finished." 42 | 43 | ## End installation 44 | etimer=$(date +%s) 45 | echo -e "${BYELLOW}Clean time: $((etimer - stimer))${NC}" 46 | -------------------------------------------------------------------------------- /00-base/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --generate-hashes --output-file requirements.txt requirements.in 6 | # 7 | click==6.7 \ 8 | --hash=sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d \ 9 | --hash=sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b 10 | jinja2==2.9.6 \ 11 | --hash=sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054 \ 12 | --hash=sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff 13 | jsonpickle==0.9.5 \ 14 | --hash=sha256:cc25dc79571d4ad7db59d05ddb7de0d76a8d598cf6136e1dbeaa9361ebcfe749 15 | lxml==3.5.0 \ 16 | --hash=sha256:2d46192109bf662413febb54fe3a3354d564bc274dd5454755b39d6a8f1f0697 \ 17 | --hash=sha256:3178c8fbe9a7b5356edf7d351fc97cd653527e2f90fe717ba42bdce24746137d \ 18 | --hash=sha256:349f93e3a4b09cc59418854ab8013d027d246757c51744bf20069bc89016f578 \ 19 | --hash=sha256:3caadf90ba3f0442f4f75e8b6c5601b70bf17f94d41ded92232757de257ed084 \ 20 | --hash=sha256:f1ee626db1365fc789dc5b3807cb7ea50a0d004d60404288a97e9424daa45cfd \ 21 | --hash=sha256:f4253563021feb84aa2d4c06443418099b07356c59a3f4a77fd7eaf36d54642e 22 | markupsafe==1.0 \ 23 | --hash=sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665 \ 24 | # via jinja2 25 | six==1.10.0 \ 26 | --hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \ 27 | --hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a \ 28 | # via structlog 29 | structlog==17.2.0 \ 30 | --hash=sha256:6980001045abd235fa12582222627c19b89109e58b85eb77d5a5abc778df6e20 \ 31 | --hash=sha256:9947d12d904529e6133384e52091ce92d0541472a722460cfcb4bcc8d02340b0 32 | urllib3==1.25 \ 33 | --hash=sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c \ 34 | --hash=sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266 35 | -------------------------------------------------------------------------------- /hardware.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"$UUID", 3 | "network":{ 4 | "interfaces":[ 5 | { 6 | "dhcp":{ 7 | "mac":"$MAC", 8 | "ip":{ 9 | "address":"192.168.1.5", 10 | "netmask":"255.255.255.248", 11 | "gateway":"192.168.1.1" 12 | }, 13 | "arch":"x86_64", 14 | "uefi":false 15 | }, 16 | "netboot":{ 17 | "allow_pxe":true, 18 | "allow_workflow":true 19 | } 20 | } 21 | ] 22 | }, 23 | "metadata":{ 24 | "state":"", 25 | "instance": { 26 | "operating_system_version": { 27 | "distro": "ubuntu", 28 | "image_tag": "base", 29 | "os_slug": "ubuntu_18_04", 30 | "slug": "ubuntu", 31 | "version": "18.04" 32 | }, 33 | "storage": { 34 | "disks": [ 35 | { 36 | "device": "/dev/sda", 37 | "wipeTable": true, 38 | "partitions": [ 39 | { 40 | "size": 4096, 41 | "label": "BIOS", 42 | "number": 1 43 | }, 44 | { 45 | "size": 3993600, 46 | "label": "SWAP", 47 | "number": 2 48 | }, 49 | { 50 | "size": 0, 51 | "label": "ROOT", 52 | "number": 3 53 | } 54 | ] 55 | } 56 | ], 57 | "filesystems": [ 58 | { 59 | "mount": { 60 | "point": "/", 61 | "create": { 62 | "options": [ 63 | "-L", 64 | "ROOT" 65 | ] 66 | }, 67 | "device": "/dev/sda3", 68 | "format": "ext4" 69 | } 70 | }, 71 | { 72 | "mount": { 73 | "point": "none", 74 | "create": { 75 | "options": [ 76 | "-L", 77 | "SWAP" 78 | ] 79 | }, 80 | "device": "/dev/sda2", 81 | "format": "swap" 82 | } 83 | } 84 | ] 85 | } 86 | }, 87 | "facility":{ 88 | "plan_slug":"c3.small.x86", 89 | "facility_code":"onprem" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /hw1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"7e3ff5ef-70d5-41e1-b274-0c53733280dc", 3 | "network":{ 4 | "interfaces":[ 5 | { 6 | "dhcp":{ 7 | "mac":"b8:59:9f:e0:f6:8c", 8 | "ip":{ 9 | "address":"192.168.1.5", 10 | "netmask":"255.255.255.248", 11 | "gateway":"192.168.1.1" 12 | }, 13 | "arch":"x86_64", 14 | "uefi":false 15 | }, 16 | "netboot":{ 17 | "allow_pxe":true, 18 | "allow_workflow":true 19 | } 20 | } 21 | ] 22 | }, 23 | "metadata":{ 24 | "state":"", 25 | "instance": { 26 | "operating_system_version": { 27 | "distro": "ubuntu", 28 | "image_tag": "base", 29 | "os_slug": "ubuntu_18_04", 30 | "slug": "ubuntu", 31 | "version": "18.04" 32 | }, 33 | "storage": { 34 | "disks": [ 35 | { 36 | "device": "/dev/sda", 37 | "wipeTable": true, 38 | "partitions": [ 39 | { 40 | "size": 4096, 41 | "label": "BIOS", 42 | "number": 1 43 | }, 44 | { 45 | "size": 3993600, 46 | "label": "SWAP", 47 | "number": 2 48 | }, 49 | { 50 | "size": 0, 51 | "label": "ROOT", 52 | "number": 3 53 | } 54 | ] 55 | } 56 | ], 57 | "filesystems": [ 58 | { 59 | "mount": { 60 | "point": "/", 61 | "create": { 62 | "options": [ 63 | "-L", 64 | "ROOT" 65 | ] 66 | }, 67 | "device": "/dev/sda3", 68 | "format": "ext4" 69 | } 70 | }, 71 | { 72 | "mount": { 73 | "point": "none", 74 | "create": { 75 | "options": [ 76 | "-L", 77 | "SWAP" 78 | ] 79 | }, 80 | "device": "/dev/sda2", 81 | "format": "swap" 82 | } 83 | } 84 | ] 85 | } 86 | }, 87 | "facility":{ 88 | "plan_slug":"c3.small.x86", 89 | "facility_code":"onprem" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /05-cloud-init/cloud-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | 7 | target="/mnt/target" 8 | BASEURL='http://192.168.1.2/misc/osie/current' 9 | 10 | mkdir -p $target 11 | mount -t ext4 /dev/sda3 $target 12 | 13 | ephemeral=/workflow/data.json 14 | OS=$(jq -r .os "$ephemeral") 15 | 16 | 17 | echo -e "${GREEN}#### Configuring cloud-init for Packet${NC}" 18 | if [ -f $target/etc/cloud/cloud.cfg ]; then 19 | case ${OS} in 20 | centos* | rhel* | scientific*) repo_module=yum-add-repo ;; 21 | debian* | ubuntu*) repo_module=apt-configure ;; 22 | esac 23 | 24 | cat <<-EOF >$target/etc/cloud/cloud.cfg 25 | apt: 26 | preserve_sources_list: true 27 | disable_root: 0 28 | package_reboot_if_required: false 29 | package_update: false 30 | package_upgrade: false 31 | hostname: kw-tf-worker 32 | users: 33 | - default 34 | - name: ubuntu 35 | gecos: Default user 36 | groups: admin 37 | sudo: true 38 | shell: /bin/bash 39 | chpasswd: 40 | list: | 41 | ubuntu:ubuntu 42 | bootcmd: 43 | - echo 192.168.1.1 kw-tf-provisioner | tee -a /etc/hosts 44 | - systemctl disable network-online.target 45 | - systemctl mask network-online.target 46 | runcmd: 47 | - touch /etc/cloud/cloud-init.disabled 48 | ssh_genkeytypes: ['rsa', 'dsa', 'ecdsa', 'ed25519'] 49 | ssh_pwauth: True 50 | ssh_authorized_keys: 51 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8TlZp6SMhZ3OCKxWbRAwOsuk8alXapXb7GQV4DPwZ+ug1AtkDCSSzPGZI6PP3rFILfobQdw6/t/GT3TKwQ1HY2vYqikWXG7YjT6r5IlsaaZ6y3KAuestYx2lG8I+MCbLmvcjo4k2qeJuf2yj331izRkeNRlRx/VWFUAtoCw2Kr2oZK+LbV8Ewv+x6jMVn9+NgxmMj+fHj9ajVtDacVvyJ8cStmRmOyIGd+rPKDb8txJT4FYXIsy5URhioni7QQuJcXN/qqy4TSY+EaYkGUo2j91MuDJZbdQYniOV4ODS8At/a/Ua51x+ia6Y51pCHMvPsm7DFhK13EQUXhIGdPVY3 root@tf-provisioner 52 | cloud_init_modules: 53 | - migrator 54 | - bootcmd 55 | - write-files 56 | - growpart 57 | - resizefs 58 | - update_hostname 59 | - update_etc_hosts 60 | - users-groups 61 | - rsyslog 62 | - ssh 63 | cloud_config_modules: 64 | - mounts 65 | - locale 66 | - set-passwords 67 | ${repo_module:+- $repo_module} 68 | - package-update-upgrade-install 69 | - timezone 70 | - puppet 71 | - chef 72 | - salt-minion 73 | - mcollective 74 | - runcmd 75 | cloud_final_modules: 76 | - phone-home 77 | - scripts-per-once 78 | - scripts-per-boot 79 | - scripts-per-instance 80 | - scripts-user 81 | - ssh-authkey-fingerprints 82 | - keys-to-console 83 | - final-message 84 | EOF 85 | echo "Disabling cloud-init based network config via cloud.cfg.d include" 86 | echo "network: {config: disabled}" >$target/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg 87 | echo "WARNING: Removing /var/lib/cloud/*" 88 | rm -rf $target/var/lib/cloud/* 89 | else 90 | echo "Cloud-init post-install - default cloud.cfg does not exist!" 91 | fi 92 | 93 | if [ -f $target/etc/init/cloud-init-nonet.conf ]; then 94 | sed -i 's/dowait 120/dowait 1/g' $target/etc/init/cloud-init-nonet.conf 95 | sed -i 's/dowait 10/dowait 1/g' $target/etc/init/cloud-init-nonet.conf 96 | else 97 | echo "Cloud-init post-install - cloud-init-nonet does not exist. skipping edit" 98 | fi 99 | 100 | cat <$target/etc/cloud/cloud.cfg.d/90_dpkg.cfg 101 | datasource_list: [ NoCloud ] 102 | EOF 103 | 104 | ## Note 105 | 106 | # Change enp1s0f0 to eno1 if working with a local on-premises machine. 107 | 108 | cat <$target/etc/network/interfaces 109 | auto lo 110 | iface lo inet loopback 111 | # 112 | auto enp1s0f0 113 | iface enp1s0f0 inet dhcp 114 | EOF 115 | 116 | cat <$target/etc/resolv.conf 117 | nameserver 1.1.1.1 118 | EOF 119 | 120 | cat <$target/etc/netplan/01-netcfg.yaml 121 | network: 122 | version: 2 123 | renderer: networkd 124 | ethernets: 125 | enp1s0f0: 126 | dhcp4: yes 127 | nameservers: 128 | addresses: [1.1.1.1] 129 | EOF 130 | -------------------------------------------------------------------------------- /04-install-grub/install.sh_original: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | # defaults 7 | # shellcheck disable=SC2207 8 | 9 | #metadata=/metadata 10 | #curl -sSL --connect-timeout 60 https://metadata.packet.net/metadata > $metadata 11 | #check_required_arg "$metadata" 'metadata file' '-M' 12 | #declare class && set_from_metadata class 'class' <"$metadata" 13 | #declare facility && set_from_metadata facility 'facility' <"$metadata" 14 | #declare os && set_from_metadata os 'operating_system.slug' <"$metadata" 15 | #declare tag && set_from_metadata tag 'operating_system.image_tag' <"$metadata" || tag="" 16 | 17 | ephemeral=/workflow/data.json 18 | class=$(jq -r .class "$ephemeral") 19 | facility=$(jq -r .facility "$ephemeral") 20 | os=$(jq -r .os "$ephemeral") 21 | tag=$(jq -r .tag "$ephemeral") 22 | 23 | OS=$os${tag:+:$tag} 24 | arch=$(uname -m) 25 | custom_image=false 26 | target="/mnt/target" 27 | cprout=/statedir/cpr.json 28 | 29 | # custom 30 | mkdir -p $target 31 | mkdir -p $target/boot 32 | mount -t efivarfs efivarfs /sys/firmware/efi/efivars 33 | mount -t ext4 /dev/sda3 $target 34 | mkdir -p $target/boot/efi 35 | 36 | if ! [[ -f /statedir/disks-partioned-image-extracted ]]; then 37 | assetdir=/tmp/assets 38 | mkdir $assetdir 39 | OS=${OS%%:*} 40 | 41 | # Grub config 42 | BASEURL="http://$MIRROR_HOST/misc/osie/current" 43 | 44 | # Ensure critical OS dirs 45 | mkdir -p $target/{dev,proc,sys} 46 | mkdir -p $target/etc/mdadm 47 | 48 | if [[ $class != "t1.small.x86" ]]; then 49 | echo -e "${GREEN}#### Updating MD RAID config file ${NC}" 50 | mdadm --examine --scan >>$target/etc/mdadm/mdadm.conf 51 | fi 52 | 53 | # ensure unique dbus/systemd machine-id 54 | echo -e "${GREEN}#### Setting machine-id${NC}" 55 | rm -f $target/etc/machine-id $target/var/lib/dbus/machine-id 56 | 57 | systemd-machine-id-setup --root=$target 58 | cat $target/etc/machine-id 59 | [[ -d $target/var/lib/dbus ]] && ln -nsf /etc/machine-id $target/var/lib/dbus/machine-id 60 | 61 | # Install kernel and initrd 62 | # Kernel to throw on the target 63 | kernel="$assetdir/kernel.tar.gz" 64 | # Initrd to throw on the target 65 | initrd="$assetdir/initrd.tar.gz" 66 | # Modules to throw on the target 67 | modules="$assetdir/modules.tar.gz" 68 | echo -e "${GREEN}#### Fetching and copying kernel, modules, and initrd to target $target ${NC}" 69 | wget "$BASEURL/$os/kernel.tar.gz" -P $assetdir 70 | wget "$BASEURL/$os/initrd.tar.gz" -P $assetdir 71 | wget "$BASEURL/$os/modules.tar.gz" -P $assetdir 72 | tar --warning=no-timestamp -zxf "$kernel" -C $target/boot 73 | kversion=$(vmlinuz_version $target/boot/vmlinuz) 74 | if [[ -z $kversion ]]; then 75 | echo 'unable to extract kernel version' >&2 76 | exit 1 77 | fi 78 | 79 | kernelname="vmlinuz-$kversion" 80 | if [[ ${os} =~ ^centos ]] || [[ ${os} =~ ^rhel ]]; then 81 | initrdname=initramfs-$kversion.img 82 | modulesdest=usr 83 | else 84 | initrdname=initrd.img-$kversion 85 | modulesdest= 86 | fi 87 | 88 | mv $target/boot/vmlinuz "$target/boot/$kernelname" && ln -nsf "$kernelname" $target/boot/vmlinuz 89 | tar --warning=no-timestamp -zxf "$initrd" && mv initrd "$target/boot/$initrdname" && ln -nsf "$initrdname" $target/boot/initrd 90 | tar --warning=no-timestamp -zxf "$modules" -C "$target/$modulesdest" 91 | cp "$target/boot/$kernelname" /statedir/kernel 92 | cp "$target/boot/$initrdname" /statedir/initrd 93 | 94 | # Install grub 95 | #grub="$BASEURL/grub/${OS//_(arm|image)//}/$class/grub.template" 96 | grub="$BASEURL/grub/$os/$class/grub.template" 97 | echo -e "${GREEN}#### Installing GRUB2${NC}" 98 | 99 | wget "$grub" -O /tmp/grub.template 100 | wget "${grub}.default" -O /tmp/grub.default 101 | 102 | mkfs.vfat -c -F 32 /dev/sda1 103 | mount -t vfat /dev/sda1 $target/boot/efi 104 | ./grub-installer.sh -v -p "$class" -t "$target" -C "$cprout" -D /tmp/grub.default -T /tmp/grub.template 105 | 106 | rootuuid=$(jq -r .rootuuid $cprout) 107 | [[ -n $rootuuid ]] 108 | cmdline=$(sed -nr 's|GRUB_CMDLINE_LINUX='\''(.*)'\''|\1|p' /tmp/grub.default) 109 | 110 | echo -e "${GREEN}#### Clearing init overrides to enable TTY${NC}" 111 | rm -rf $target/etc/init/*.override 112 | 113 | if [[ $custom_image == false ]]; then 114 | echo -e "${GREEN}#### Setting up package repos${NC}" 115 | ./repos.sh -a "$arch" -t $target -f "$facility" -M "$ephemeral" 116 | fi 117 | fi 118 | 119 | -------------------------------------------------------------------------------- /02-disk-partition/partition.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | # defaults 7 | # shellcheck disable=SC2207 8 | disks=($(lsblk -dno name -e1,7,11 | sed 's|^|/dev/|' | sort)) 9 | userdata='/dev/null' 10 | 11 | arch=$(uname -m) 12 | 13 | metadata=/metadata 14 | curl --connect-timeout 60 http://$MIRROR_HOST:50061/metadata > $metadata 15 | check_required_arg "$metadata" 'metadata file' '-M' 16 | 17 | declare class && set_from_metadata class 'plan_slug' <"$metadata" 18 | declare facility && set_from_metadata facility 'facility_code' <"$metadata" 19 | declare os && set_from_metadata os 'instance.operating_system_version.os_slug' <"$metadata" 20 | declare preserve_data && set_from_metadata preserve_data 'preserve_data' false <"$metadata" 21 | 22 | # declare pwhash && set_from_metadata pwhash 'password_hash' <"$metadata" 23 | # declare state && set_from_metadata state 'state' <"$metadata" 24 | declare pwhash="5f4dcc3b5aa765d61d8327deb882cf99" 25 | declare state="provisioning" 26 | 27 | declare tag && set_from_metadata tag 'instance.operating_system_version.image_tag' <"$metadata" || tag="" 28 | #declare tinkerbell && set_from_metadata tinkerbell 'phone_home_url' <"$metadata" 29 | declare deprovision_fast && set_from_metadata deprovision_fast 'deprovision_fast' false <"$metadata" 30 | 31 | OS=$os${tag:+:$tag} 32 | echo "Number of drives found: ${#disks[*]}" 33 | if ((${#disks[*]} != 0)); then 34 | echo "Disk candidate check successful" 35 | fi 36 | 37 | ephemeral=/workflow/data.json 38 | echo "{}" > $ephemeral 39 | echo $(jq ". + {\"arch\": \"$arch\"}" <<< cat $ephemeral) > $ephemeral 40 | echo $(jq ". + {\"class\": \"$class\"}" <<< cat $ephemeral) > $ephemeral 41 | echo $(jq ". + {\"facility\": \"$facility\"}" <<< cat $ephemeral) > $ephemeral 42 | echo $(jq ". + {\"os\": \"$os\"}" <<< cat $ephemeral) > $ephemeral 43 | echo $(jq ". + {\"preserve_data\": \"$preserve_data\"}" <<< cat $ephemeral) > $ephemeral 44 | echo $(jq ". + {\"pwhash\": \"$pwhash\"}" <<< cat $ephemeral) > $ephemeral 45 | echo $(jq ". + {\"state\": \"$state\"}" <<< cat $ephemeral) > $ephemeral 46 | echo $(jq ". + {\"tag\": \"$tag\"}" <<< cat $ephemeral) > $ephemeral 47 | #echo $(jq ". + {\"tinkerbell\": \"$tinkerbell\"}" <<< cat $ephemeral) > $ephemeral 48 | echo $(jq ". + {\"deprovision_fast\": \"$deprovision_fast\"}" <<< cat $ephemeral) > $ephemeral 49 | 50 | jq . $ephemeral 51 | 52 | custom_image=false 53 | target="/mnt/target" 54 | cprconfig=/tmp/config.cpr 55 | cprout=/statedir/cpr.json 56 | 57 | # custom 58 | #mkdir -p /statedir && touch /statedir/cpr.json 59 | #touch /statedir/cpr.json 60 | 61 | echo -e "${GREEN}#### Checking userdata for custom cpr_url...${NC}" 62 | cpr_url=$(sed -nr 's|.*\bcpr_url=(\S+).*|\1|p' "$userdata") 63 | 64 | if [[ -z ${cpr_url} ]]; then 65 | echo "Using default image since no cpr_url provided" 66 | jq -c '.instance.storage' "$metadata" >$cprconfig 67 | else 68 | echo "NOTICE: Custom CPR url found!" 69 | echo "Overriding default CPR location with custom cpr_url" 70 | if ! curl "$cpr_url" | jq . >$cprconfig; then 71 | phone_home "${tinkerbell}" '{"instance_id":"'"$(jq -r .id "$metadata")"'"}' 72 | echo "$0: CPR URL unavailable: $cpr_url" >&2 73 | exit 1 74 | fi 75 | fi 76 | 77 | if ! [[ -f /statedir/disks-partioned-image-extracted ]]; then 78 | OS=${OS%%:*} 79 | jq . $cprconfig 80 | 81 | # make sure the disks are ok to use 82 | assert_block_or_loop_devs "${disks[@]}" 83 | assert_same_type_devs "${disks[@]}" 84 | 85 | is_uefi && uefi=true || uefi=false 86 | 87 | if [[ $deprovision_fast == false ]] && [[ $preserve_data == false ]]; then 88 | echo -e "${GREEN}Checking disks for existing partitions...${NC}" 89 | if fdisk -l "${disks[@]}" 2>/dev/null | grep Disklabel >/dev/null; then 90 | echo -e "${RED}Critical: Found pre-exsting partitions on a disk. Aborting install...${NC}" 91 | fdisk -l "${disks[@]}" 92 | exit 1 93 | fi 94 | fi 95 | 96 | echo "Disk candidates are ready for partitioning." 97 | 98 | echo -e "${GREEN}#### Running CPR disk config${NC}" 99 | UEFI=$uefi ./cpr.sh $cprconfig "$target" "$preserve_data" "$deprovision_fast" | tee $cprout 100 | 101 | mount | grep $target 102 | 103 | # dump cpr provided fstab into $target 104 | mkdir -p /mnt/target/etc 105 | touch /mnt/target/etc/fstab 106 | jq -r .fstab "$cprout" >$target/etc/fstab 107 | 108 | echo "backup /etc/fstab file to restore it in after step install-root-fs cause install-root-fs will override the fstab content" 109 | cp /mnt/target/etc/fstab /mnt/target/etc/fstab_backup 110 | echo "$(cat /mnt/target/etc/fstab)" 111 | echo -e "${GREEN}#### CPR disk config complete ${NC}" 112 | fi 113 | -------------------------------------------------------------------------------- /04-install-grub/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | set -o nounset 5 | 6 | # defaults 7 | # shellcheck disable=SC2207 8 | 9 | #metadata=/metadata 10 | #curl -sSL --connect-timeout 60 https://metadata.packet.net/metadata > $metadata 11 | #check_required_arg "$metadata" 'metadata file' '-M' 12 | #declare class && set_from_metadata class 'class' <"$metadata" 13 | #declare facility && set_from_metadata facility 'facility' <"$metadata" 14 | #declare os && set_from_metadata os 'operating_system.slug' <"$metadata" 15 | #declare tag && set_from_metadata tag 'operating_system.image_tag' <"$metadata" || tag="" 16 | 17 | ephemeral=/workflow/data.json 18 | class=$(jq -r .class "$ephemeral") 19 | facility=$(jq -r .facility "$ephemeral") 20 | os=$(jq -r .os "$ephemeral") 21 | tag=$(jq -r .tag "$ephemeral") 22 | 23 | OS=$os${tag:+:$tag} 24 | arch=$(uname -m) 25 | custom_image=false 26 | target="/mnt/target" 27 | cprout=/statedir/cpr.json 28 | 29 | is_uefi && uefi=true || uefi=false 30 | 31 | # custom 32 | mkdir -p $target 33 | mkdir -p $target/boot 34 | #mount -t efivarfs efivarfs /sys/firmware/efi/efivars 35 | mount -t ext4 /dev/sda3 $target 36 | #mkdir -p $target/boot/grub2 37 | #mkdir -p /mnt/target/boot/grub2 38 | 39 | if $uefi; then 40 | mount -t efivarfs efivarfs /sys/firmware/efi/efivars 41 | mkdir -p $target/boot/efi 42 | else 43 | mkdir -p $target/boot/grub2 44 | mkdir -p /mnt/target/boot/grub2 45 | fi 46 | 47 | if ! [[ -f /statedir/disks-partioned-image-extracted ]]; then 48 | assetdir=/tmp/assets 49 | mkdir $assetdir 50 | OS=${OS%%:*} 51 | 52 | # Grub config 53 | BASEURL="http://$MIRROR_HOST/misc/osie/current" 54 | 55 | # Ensure critical OS dirs 56 | mkdir -p $target/{dev,proc,sys} 57 | mkdir -p $target/etc/mdadm 58 | 59 | if [[ $class != "t1.small.x86" ]]; then 60 | echo -e "${GREEN}#### Updating MD RAID config file ${NC}" 61 | mdadm --examine --scan >>$target/etc/mdadm/mdadm.conf 62 | fi 63 | 64 | # ensure unique dbus/systemd machine-id 65 | echo -e "${GREEN}#### Setting machine-id${NC}" 66 | rm -f $target/etc/machine-id $target/var/lib/dbus/machine-id 67 | 68 | systemd-machine-id-setup --root=$target 69 | cat $target/etc/machine-id 70 | [[ -d $target/var/lib/dbus ]] && ln -nsf /etc/machine-id $target/var/lib/dbus/machine-id 71 | 72 | # Install kernel and initrd 73 | # Kernel to throw on the target 74 | kernel="$assetdir/kernel.tar.gz" 75 | # Initrd to throw on the target 76 | initrd="$assetdir/initrd.tar.gz" 77 | # Modules to throw on the target 78 | modules="$assetdir/modules.tar.gz" 79 | echo -e "${GREEN}#### Fetching and copying kernel, modules, and initrd to target $target ${NC}" 80 | wget "$BASEURL/$os/kernel.tar.gz" -P $assetdir 81 | wget "$BASEURL/$os/initrd.tar.gz" -P $assetdir 82 | wget "$BASEURL/$os/modules.tar.gz" -P $assetdir 83 | tar --warning=no-timestamp -zxf "$kernel" -C $target/boot 84 | kversion=$(vmlinuz_version $target/boot/vmlinuz) 85 | if [[ -z $kversion ]]; then 86 | echo 'unable to extract kernel version' >&2 87 | exit 1 88 | fi 89 | 90 | kernelname="vmlinuz-$kversion" 91 | if [[ ${os} =~ ^centos ]] || [[ ${os} =~ ^rhel ]]; then 92 | initrdname=initramfs-$kversion.img 93 | modulesdest=usr 94 | else 95 | initrdname=initrd.img-$kversion 96 | modulesdest= 97 | fi 98 | 99 | mv $target/boot/vmlinuz "$target/boot/$kernelname" && ln -nsf "$kernelname" $target/boot/vmlinuz 100 | tar --warning=no-timestamp -zxf "$initrd" && mv initrd "$target/boot/$initrdname" && ln -nsf "$initrdname" $target/boot/initrd 101 | tar --warning=no-timestamp -zxf "$modules" -C "$target/$modulesdest" 102 | cp "$target/boot/$kernelname" /statedir/kernel 103 | cp "$target/boot/$initrdname" /statedir/initrd 104 | 105 | # Install grub 106 | #grub="$BASEURL/grub/${OS//_(arm|image)//}/$class/grub.template" 107 | grub="$BASEURL/grub/$os/$class/grub.template" 108 | echo -e "${GREEN}#### Installing GRUB2${NC}" 109 | 110 | wget "$grub" -O /tmp/grub.template 111 | wget "${grub}.default" -O /tmp/grub.default 112 | 113 | if $uefi; then 114 | mkfs.vfat -c -F 32 /dev/sda1 115 | mount -t vfat /dev/sda1 $target/boot/efi 116 | fi 117 | 118 | ./grub-installer.sh -v -p "$class" -t "$target" -C "$cprout" -D /tmp/grub.default -T /tmp/grub.template 119 | 120 | rootuuid=$(jq -r .rootuuid $cprout) 121 | [[ -n $rootuuid ]] 122 | cmdline=$(sed -nr 's|GRUB_CMDLINE_LINUX='\''(.*)'\''|\1|p' /tmp/grub.default) 123 | 124 | echo -e "${GREEN}#### Clearing init overrides to enable TTY${NC}" 125 | rm -rf $target/etc/init/*.override 126 | 127 | if [[ $custom_image == false ]]; then 128 | echo -e "${GREEN}#### Setting up package repos${NC}" 129 | ./repos.sh -a "$arch" -t $target -f "$facility" -M "$ephemeral" 130 | fi 131 | fi 132 | -------------------------------------------------------------------------------- /04-install-grub/grub-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | 5 | USAGE="Usage: $0 -t /mnt/target -C /path/to/cprout.json 6 | Required Arguments: 7 | -p plan Server plan (ex: t1.small.x86) 8 | -t target Target mount point to write configs to 9 | -C path Path to file containing cpr.sh output json 10 | -D path Path to grub.default template 11 | -T path Path to grub.cfg template 12 | 13 | Options: 14 | -h This help message 15 | -v Turn on verbose messages for debugging 16 | 17 | Description: This script will configure grub for the target distro 18 | " 19 | while getopts "p:t:C:D:T:hv" OPTION; do 20 | case $OPTION in 21 | p) plan=$OPTARG ;; 22 | t) target="$OPTARG" ;; 23 | C) cprout=$OPTARG ;; 24 | D) default_path=$OPTARG ;; 25 | T) template_path=$OPTARG ;; 26 | h) echo "$USAGE" && exit 0 ;; 27 | v) set -x ;; 28 | *) echo "$USAGE" && exit 1 ;; 29 | esac 30 | done 31 | 32 | assert_all_args_consumed "$OPTIND" "$@" 33 | 34 | # Make sure target provided is mounted 35 | if grep -qs "$target" /proc/mounts; then 36 | echo "Target is mounted... good." 37 | else 38 | echo "Error: Target $target is not mounted" 39 | exit 1 40 | fi 41 | 42 | rm -rf "$target/boot/grub" 43 | [[ -d $target/boot/grub2 ]] || mkdir -p "$target/boot/grub2" 44 | ln -nsfT grub2 "$target/boot/grub" 45 | 46 | rootuuid=$(jq -r .rootuuid "$cprout") 47 | [[ -n $rootuuid ]] 48 | sed "s/PACKET_ROOT_UUID/$rootuuid/g" "$template_path" >"$target/boot/grub2/grub.cfg" 49 | 50 | cmdline=$(sed -nr 's|GRUB_CMDLINE_LINUX='\''(.*)'\''|\1|p' "$default_path") 51 | echo -e "${BYELLOW}Detected cmdline: ${cmdline}${NC}" 52 | ( 53 | sed -i 's|^|export |' "$default_path" 54 | # shellcheck disable=SC1090 55 | # shellcheck disable=SC1091 56 | source "$default_path" 57 | GRUB_DISTRIBUTOR=$(detect_os "$target" | awk '{print $1}') envsubst "$target/etc/default/grub" 58 | ) 59 | 60 | is_uefi && uefi=true || uefi=false 61 | arch=$(uname -m) 62 | os_ver=$(detect_os "$target") 63 | 64 | # shellcheck disable=SC2086 65 | set -- $os_ver 66 | DOS=$1 67 | DVER=$2 68 | echo "#### Detected OS on mounted target $target" 69 | echo "OS: $DOS ARCH: $arch VER: $DVER" 70 | 71 | chroot_install=false 72 | 73 | if [[ $DOS == "RedHatEnterpriseServer" ]] && [[ $arch == "aarch64" ]] || [[ $plan == "c3.medium.x86" ]]; then 74 | chroot_install=true 75 | fi 76 | 77 | install_grub_chroot() { 78 | echo "Attempting to install Grub on $disk" 79 | mount --bind /dev "$target/dev" 80 | mount --bind /tmp "$target/tmp" 81 | mount --bind /proc "$target/proc" 82 | mount --bind /sys "$target/sys" 83 | chroot "$target" /bin/bash -xe <&2 27 | } 28 | 29 | add_to_fstab() { 30 | local UUID=${1} MOUNTPOINT=${2} FSFORMAT=${3} options=errors=remount-ro fsck=2 31 | if grep -qs "${UUID}" $fstab; then 32 | msg "WARNING: Not adding ${UUID} to fstab again (it's already there!)" 33 | return 34 | fi 35 | 36 | if [[ ${FSFORMAT} == swap ]]; then 37 | fsck=0 38 | options=none 39 | fi 40 | [[ ${MOUNTPOINT} == '/' ]] && fsck=1 41 | echo -e "UUID=${UUID}\\t${MOUNTPOINT}\\t${FSFORMAT}\\t$options\\t0\\t$fsck" | tee -a $fstab >&2 42 | } 43 | 44 | setup_disks() { 45 | #shellcheck disable=SC2207 46 | disks=($(stormeta 'disks[].device')) 47 | msg '########## setting raids #########' 48 | raids=('hack') 49 | #shellcheck disable=SC2207 50 | fsdevs=($(stormeta 'filesystems[].mount.device')) 51 | if stormeta 'raid[0].devices[]' &>/dev/null; then 52 | #shellcheck disable=SC2207 53 | raids=($(stormeta 'raid[].name')) 54 | msg "RAID devs: ${raids[*]}" 55 | else 56 | msg "No raid defined in config json - skipping raid config..." 57 | fi 58 | 59 | msg "Disks: ${disks[*]}" 60 | 61 | ## Create partitions on each disk 62 | diskcnt=0 63 | for disk in "${disks[@]}"; do 64 | msg "Writing disk config for $disk" 65 | #TODO _maybe_ consider wipe/zap if wipeTable is defined for disk 66 | ## Possibly key off just disks[0] to ensure symetrical layout for raid, but only if the bd is a raid member 67 | if [[ $preserve_data == true ]] || [[ $deprovision_fast == true ]]; then 68 | msg "Wiping disk partition table due to preserve_data and/or deprovision_fast" 69 | fast_wipe "$disk" 70 | fi 71 | 72 | #shellcheck disable=SC2207 73 | parts=($(stormeta "disks[$diskcnt].partitions[].number")) 74 | for partcnt in $(seq 0 $((${#parts[@]} - 1))); do 75 | partlabel=$(stormeta "disks[$diskcnt].partitions[$partcnt].label") 76 | partnum=$(stormeta "disks[$diskcnt].partitions[$partcnt].number") 77 | partsize=$(stormeta "disks[$diskcnt].partitions[$partcnt].size") 78 | parttype=8300 79 | msg "Working on $disk part $partnum aka label $partlabel with size $partsize" 80 | 81 | if [[ $partlabel =~ "BIOS" ]]; then 82 | bootdevs+=("$disk") 83 | case ${UEFI:-} in 84 | true) 85 | humantype="EFI system" 86 | parttype=ef00 87 | ;; 88 | *) 89 | humantype="BIOS boot partition" 90 | parttype=ef02 91 | ;; 92 | esac 93 | msg "label contains BIOS, setting partition type as $humantype" 94 | fi 95 | 96 | if [[ $partsize =~ "pct" ]]; then 97 | msg "Part size contains pct. Lets do some math" 98 | # all maths is done on sector basis 99 | disksize=$(blockdev --getsz "$disk") 100 | partpct=${partsize//pct/} 101 | npartsize=$((disksize * partpct / 100)) 102 | 103 | msg "Creating a partition $partpct% the size of disk $disk ($disksize) resulting in partition size $npartsize sectors" 104 | partsize=+$npartsize 105 | elif ((partsize == 0)); then 106 | msg "Partsize is zero. This means use the rest of the disk." 107 | else 108 | partsize="+$partsize" 109 | fi 110 | sgdisk -n "$partnum:0:$partsize" -c "$partnum:$partlabel" -t "$partnum:$parttype" "$disk" >&2 111 | done 112 | diskcnt=$((diskcnt + 1)) 113 | done 114 | 115 | ## Create RAID array(s) 116 | raidcnt=0 117 | for raid in "${raids[@]}"; do 118 | # older versions of bash treat empty arrays as unset :arghfist: 119 | [[ $raid == 'hack' ]] && break 120 | 121 | msg "Writing RAID config for array $raid" 122 | # shellcheck disable=SC2207 123 | raiddevs=($(stormeta "raid[$raidcnt].devices[]")) 124 | raiddevscnt=${#raiddevs[@]} 125 | raidlevel=$(stormeta "raid[$raidcnt].level") 126 | msg "RAID dev list: ${raiddevs[*]}" 127 | msg "level: $raidlevel raid-devices: $raiddevscnt" 128 | mdadm --create "$raid" --force --run --level="$raidlevel" --raid-devices="$raiddevscnt" "${raiddevs[@]}" >&2 129 | 130 | raidcnt=$((raidcnt + 1)) 131 | done 132 | 133 | ## Create filesystems and update fstab 134 | fscnt=0 135 | for fsdev in "${fsdevs[@]}"; do 136 | msg "Writing filesystem for $fsdev" 137 | format=$(stormeta "filesystems[$fscnt].mount.format") 138 | mpoint=$(stormeta "filesystems[$fscnt].mount.point") 139 | # shellcheck disable=SC2207 140 | fsopts=($(stormeta "filesystems[$fscnt].mount.create.options[]")) 141 | fscnt=$((fscnt + 1)) 142 | 143 | msg "$fsdev: format=$format fsopts=\"${fsopts[*]}\" mpoint=\"$mpoint\"" 144 | if [[ $format == 'bios' ]]; then 145 | continue 146 | elif [[ $format == 'swap' ]]; then 147 | mkswap "$fsdev" >&2 148 | else 149 | "mkfs.$format" -F "${fsopts[@]}" "$fsdev" >&2 150 | fi 151 | 152 | thisdevuuid=$(blkid -s UUID -o value "$fsdev") 153 | if [[ ${mpoint} == / ]]; then 154 | rootuuid=$thisdevuuid 155 | 156 | fi 157 | 158 | add_to_fstab "${thisdevuuid}" "${mpoint}" "${format}" 159 | 160 | done 161 | 162 | # if this succeeds then we are guaranteed that bootdevs and rootuuid are set 163 | cat <<-EOF | python3 164 | import json 165 | d = { 166 | 'fstab': open("$fstab").read(), 167 | 'rootuuid': '$rootuuid', 168 | 'bootdevs': '${bootdevs[*]}'.split(), 169 | } 170 | print(json.dumps(d)) 171 | EOF 172 | } 173 | 174 | setup_remount() { 175 | jq -r .fstab "$1" | tee $fstab 176 | } 177 | 178 | case $# in 179 | 4) setup_disks ;; 180 | 6) 181 | [[ $5 != 'mount' ]] && echo "cpr.sh: expected 5th arg to be 'mount'" && exit 1 182 | setup_remount "$6" 183 | ;; 184 | *) echo "cpr.sh called incorrectly" && exit 1 ;; 185 | esac 186 | 187 | target=/mnt/target 188 | fstab=/tmp/fstab.tmpl 189 | 190 | msg "${GREEN}#### Mounting filesystems in fstab to $target" 191 | sed -i 's|\(\S\)\t/|\1\t'"$target"'/|' $fstab 192 | while read -r mnt; do 193 | #msg "$mnt" 194 | mkdir -p "$mnt" 195 | mount --fstab "$fstab" "$mnt" 196 | done < <(awk '/\/mnt\// {print $2}' $fstab | sort) 197 | -------------------------------------------------------------------------------- /04-install-grub/repos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source functions.sh && init 4 | 5 | #defaults 6 | 7 | USAGE="Usage: $0 -t /mnt/target 8 | Required Arguments: 9 | -a arch System architecture {aarch64|x86_64} 10 | -M metadata File containing instance metadata 11 | -t target Target mount point to write repos to 12 | 13 | Options: 14 | -f facility Facility to use to reach artifacts server 15 | -m url Address to embed into OS for package repository (advanced usage, default http://mirror.\$facility.packet.net) 16 | -h This help message 17 | -v Turn on verbose messages for debugging 18 | 19 | Description: This script will configure the package repositories based upon LSB properties located on a target mount point. 20 | " 21 | while getopts "a:M:t:f:m:hv" OPTION; do 22 | case $OPTION in 23 | a) arch=$OPTARG ;; 24 | M) metadata=$OPTARG ;; 25 | t) export TARGET="$OPTARG" ;; 26 | f) facility="$OPTARG" ;; 27 | m) mirror="$OPTARG" ;; 28 | h) echo "$USAGE" && exit 0 ;; 29 | v) set -x ;; 30 | *) echo "$USAGE" && exit 1 ;; 31 | esac 32 | done 33 | 34 | check_required_arg "$arch" "arch" "-a" 35 | check_required_arg "$metadata" 'metadata file' '-M' 36 | check_required_arg "$TARGET" 'target mount point' '-t' 37 | assert_all_args_consumed "$OPTIND" "$@" 38 | 39 | # if $mirror is not empty then the user specifically passed in the mirror 40 | # location, we should not trample it 41 | mirror=${mirror:-http://mirror.$facility.packet.net} 42 | 43 | # Make sure target provided is mounted 44 | if grep -qs "$TARGET" /proc/mounts; then 45 | echo "Target is mounted... good." 46 | else 47 | echo "Error: Target $TARGET is not mounted" 48 | exit 1 49 | fi 50 | 51 | os_ver=$(detect_os "$TARGET") 52 | # shellcheck disable=SC2086 53 | set -- $os_ver 54 | DOS=$1 55 | DVER=$2 56 | echo "#### Detected OS on mounted target $TARGET" 57 | echo "OS: $DOS ARCH: $arch VER: $DVER" 58 | 59 | # Match detected OS to known OS config 60 | do_centos() { 61 | echo "Configuring repos for CentOS" 62 | 63 | cat <<-EOF_yum_repo >"$TARGET/etc/yum.repos.d/packet.repo" 64 | [packet-base] 65 | name=CentOS-\$releasever - Base 66 | baseurl=${mirror}/centos/\$releasever/os/\$basearch/ 67 | gpgcheck=1 68 | enabled=1 69 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 70 | priority=10 71 | 72 | #released updates 73 | [packet-updates] 74 | name=CentOS-\$releasever - Updates 75 | baseurl=${mirror}/centos/\$releasever/updates/\$basearch/ 76 | gpgcheck=1 77 | enabled=1 78 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 79 | priority=10 80 | 81 | [packet-extras] 82 | name=CentOS-\$releasever - Extras 83 | baseurl=${mirror}/centos/\$releasever/extras/\$basearch/ 84 | gpgcheck=0 85 | enabled=1 86 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 87 | priority=10 88 | EOF_yum_repo 89 | 90 | sed -i '/distroverpkg=centos-release/a exclude=microcode_ctl' "$TARGET/etc/yum.conf" 91 | } 92 | 93 | do_ubuntu() { 94 | case "$DVER" in 95 | '14.04') do_ubuntu_14_04 ;; 96 | '16.04') "do_ubuntu_16_04_$arch" ;; 97 | '17.10') "do_ubuntu_17_10_$arch" ;; 98 | '18.04') "do_ubuntu_18_04_$arch" ;; 99 | '19.04') "do_ubuntu_19_04_$arch" ;; 100 | *) do_unknown ;; 101 | esac 102 | } 103 | 104 | do_ubuntu_14_04() { 105 | echo "Configuring repos for Ubuntu $DVER" 106 | 107 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 108 | deb [arch=amd64] ${mirror}/ubuntu trusty main universe 109 | deb [arch=amd64] ${mirror}/ubuntu trusty-updates main universe 110 | deb [arch=amd64] ${mirror}/ubuntu trusty-security main universe 111 | EOF_ub_repo 112 | } 113 | 114 | do_ubuntu_16_04_x86_64() { 115 | echo "Configuring repos for Ubuntu $DVER" 116 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 117 | deb http://archive.ubuntu.com/ubuntu/ xenial main universe 118 | deb http://archive.ubuntu.com/ubuntu/ xenial-updates main universe 119 | deb http://archive.ubuntu.com/ubuntu/ xenial-security main universe 120 | EOF_ub_repo 121 | } 122 | 123 | do_ubuntu_16_04_aarch64() { 124 | echo "Configuring repos for Ubuntu $DVER for $arch" 125 | echo 'Acquire::ForceIPv4 "true";' >"$TARGET/etc/apt/apt.conf.d/99force-ipv4" 126 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 127 | deb http://ports.ubuntu.com/ubuntu-ports xenial main multiverse universe 128 | deb http://ports.ubuntu.com/ubuntu-ports xenial-backports main multiverse universe 129 | deb http://ports.ubuntu.com/ubuntu-ports xenial-security main multiverse universe 130 | deb http://ports.ubuntu.com/ubuntu-ports xenial-updates main multiverse universe 131 | EOF_ub_repo 132 | } 133 | 134 | do_ubuntu_17_10_x86_64() { 135 | echo "Configuring repos for Ubuntu $DVER" 136 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 137 | deb http://archive.ubuntu.com/ubuntu/ artful main universe 138 | deb http://archive.ubuntu.com/ubuntu/ artful-updates main universe 139 | deb http://archive.ubuntu.com/ubuntu/ artful-security main universe 140 | EOF_ub_repo 141 | 142 | cat <<-EOF_ub_motd >"$TARGET/etc/motd" 143 | ===================================================================== 144 | - NOTICE - 145 | - Ubuntu 17.10 is/will be EOL and Packet.net will discontinue support - 146 | - for it soon. Please consider upgrading or moving to LTS. For - 147 | - more information please see https://bit.ly/2glHLU8 - 148 | ===================================================================== 149 | EOF_ub_motd 150 | } 151 | 152 | do_ubuntu_17_10_aarch64() { 153 | echo "Configuring repos for Ubuntu $DVER for $arch" 154 | echo 'Acquire::ForceIPv4 "true";' >"$TARGET/etc/apt/apt.conf.d/99force-ipv4" 155 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 156 | deb http://ports.ubuntu.com/ubuntu-ports artful main multiverse universe 157 | deb http://ports.ubuntu.com/ubuntu-ports artful-backports main multiverse universe 158 | deb http://ports.ubuntu.com/ubuntu-ports artful-security main multiverse universe 159 | deb http://ports.ubuntu.com/ubuntu-ports artful-updates main multiverse universe 160 | EOF_ub_repo 161 | 162 | cat <<-EOF_ub_motd >"$TARGET/etc/motd" 163 | ===================================================================== 164 | - NOTICE - 165 | - Ubuntu 17.10 is/will be EOL and Packet.net will discontinue support - 166 | - for it soon. Please consider upgrading or moving to LTS. For - 167 | - more information please see https://bit.ly/2glHLU8 - 168 | ===================================================================== 169 | EOF_ub_motd 170 | } 171 | 172 | do_ubuntu_18_04_x86_64() { 173 | echo "Configuring repos for Ubuntu $DVER" 174 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 175 | deb http://archive.ubuntu.com/ubuntu bionic main restricted 176 | deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted 177 | deb http://archive.ubuntu.com/ubuntu bionic universe 178 | deb http://archive.ubuntu.com/ubuntu bionic-updates universe 179 | deb http://archive.ubuntu.com/ubuntu bionic multiverse 180 | deb http://archive.ubuntu.com/ubuntu bionic-updates multiverse 181 | deb http://archive.ubuntu.com/ubuntu bionic-backports main restricted universe multiverse 182 | deb http://security.ubuntu.com/ubuntu bionic-security main restricted 183 | deb http://security.ubuntu.com/ubuntu bionic-security universe 184 | deb http://security.ubuntu.com/ubuntu bionic-security multiverse 185 | EOF_ub_repo 186 | } 187 | 188 | do_ubuntu_18_04_aarch64() { 189 | echo "Configuring repos for Ubuntu $DVER for $arch" 190 | echo 'Acquire::ForceIPv4 "true";' >"$TARGET/etc/apt/apt.conf.d/99force-ipv4" 191 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 192 | deb http://ports.ubuntu.com/ubuntu-ports bionic main multiverse universe 193 | deb http://ports.ubuntu.com/ubuntu-ports bionic-backports main multiverse universe 194 | deb http://ports.ubuntu.com/ubuntu-ports bionic-security main multiverse universe 195 | deb http://ports.ubuntu.com/ubuntu-ports bionic-updates main multiverse universe 196 | EOF_ub_repo 197 | } 198 | 199 | do_ubuntu_19_04_x86_64() { 200 | echo "Configuring repos for Ubuntu $DVER" 201 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 202 | deb http://archive.ubuntu.com/ubuntu disco main restricted 203 | deb http://archive.ubuntu.com/ubuntu disco-updates main restricted 204 | deb http://archive.ubuntu.com/ubuntu disco universe 205 | deb http://archive.ubuntu.com/ubuntu disco-updates universe 206 | deb http://archive.ubuntu.com/ubuntu disco multiverse 207 | deb http://archive.ubuntu.com/ubuntu disco-updates multiverse 208 | deb http://archive.ubuntu.com/ubuntu disco-backports main restricted universe multiverse 209 | deb http://security.ubuntu.com/ubuntu disco-security main restricted 210 | deb http://security.ubuntu.com/ubuntu disco-security universe 211 | deb http://security.ubuntu.com/ubuntu disco-security multiverse 212 | EOF_ub_repo 213 | } 214 | 215 | do_ubuntu_19_04_aarch64() { 216 | echo "Configuring repos for Ubuntu $DVER for $arch" 217 | echo 'Acquire::ForceIPv4 "true";' >"$TARGET/etc/apt/apt.conf.d/99force-ipv4" 218 | cat <<-EOF_ub_repo >"$TARGET/etc/apt/sources.list" 219 | deb http://ports.ubuntu.com/ubuntu-ports disco main multiverse universe 220 | deb http://ports.ubuntu.com/ubuntu-ports disco-backports main multiverse universe 221 | deb http://ports.ubuntu.com/ubuntu-ports disco-security main multiverse universe 222 | deb http://ports.ubuntu.com/ubuntu-ports disco-updates main multiverse universe 223 | EOF_ub_repo 224 | } 225 | 226 | do_debian() { 227 | case "$DVER" in 228 | '8.8') do_debian_8 ;; 229 | 9.*) do_debian_9 ;; 230 | 10.*) do_debian_10 ;; 231 | *) do_unknown ;; 232 | esac 233 | } 234 | 235 | do_debian_8() { 236 | if [ "$arch" = "aarch64" ]; then 237 | echo "Nothing to do for Debian arm64" 238 | exit 0 239 | fi 240 | 241 | echo "Configuring repos for Debian $DVER" 242 | 243 | cat <<-EOF_deb_repo >"$TARGET/etc/apt/sources.list" 244 | deb [arch=amd64] http://security.debian.org jessie/updates main non-free contrib 245 | deb [arch=amd64] ${mirror}/debian jessie-backports main 246 | deb [arch=amd64] ${mirror}/debian jessie main non-free contrib 247 | deb [arch=amd64] ${mirror}/debian jessie-updates main non-free contrib 248 | EOF_deb_repo 249 | } 250 | 251 | do_debian_9() { 252 | if [ "$arch" = "aarch64" ]; then 253 | echo "Nothing to do for Debian arm64" 254 | exit 0 255 | fi 256 | 257 | echo "Configuring repos for Debian $DVER" 258 | 259 | cat <<-EOF_deb_repo >"$TARGET/etc/apt/sources.list" 260 | deb [arch=amd64] http://security.debian.org stretch/updates main non-free contrib 261 | deb [arch=amd64] ${mirror}/debian stretch-backports main 262 | deb [arch=amd64] ${mirror}/debian stretch main non-free contrib 263 | deb [arch=amd64] ${mirror}/debian stretch-updates main non-free contrib 264 | EOF_deb_repo 265 | } 266 | 267 | do_debian_10() { 268 | if [ "$arch" = "aarch64" ]; then 269 | echo "Nothing to do for Debian arm64" 270 | exit 0 271 | fi 272 | 273 | echo "Configuring repos for Debian $DVER" 274 | 275 | cat <<-EOF_deb_repo >"$TARGET/etc/apt/sources.list" 276 | deb [arch=amd64] http://security.debian.org buster/updates main non-free contrib 277 | deb [arch=amd64] ${mirror}/debian buster-backports main 278 | deb [arch=amd64] ${mirror}/debian buster main non-free contrib 279 | deb [arch=amd64] ${mirror}/debian buster-updates main non-free contrib 280 | EOF_deb_repo 281 | } 282 | 283 | do_scientificcernslc() { 284 | echo "Nothing to do for SciLinux" 285 | } 286 | 287 | do_redhatenterpriseserver() { 288 | echo "Nothing to do for RedHatEnterpriseServer" 289 | } 290 | 291 | do_unknown() { 292 | echo "Warning: Detected OS $DOS not matched! It's up to the image repo now." 293 | } 294 | 295 | # TODO: maybe break off in into repo-$DOS-$arch.sh, error if ! test -x? 296 | case "$DOS" in 297 | 'CentOS' | 'RedHatEnterpriseServer' | 'Debian' | 'ScientificCERNSLC' | 'Ubuntu') "do_${DOS,,}" ;; 298 | *) do_unknown ;; 299 | esac 300 | exit 0 301 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tink-workflow 2 | 3 | With this step-by-step guide, you have everything you need to provision a bare-metal server using the [Tinkerbell project](https://tinkerbell.org). 4 | 5 | ## Getting Started 6 | 7 | ### Prerequisites 8 | 9 | 1. Get two machine, one is provisioner which it could be VM, the other one if the bare metal server you'd like to be 10 | provisioned by tinkerbell, here we call it worker node. 11 | 12 | [Use the Tinkerbell Terraform module to setup a single provisioner and worker machine](https://tinkerbell.org/setup/packet-with-terraform/) 13 | 14 | You will need a Packet account and a personal user access token, not a project-level token. 15 | 16 | 2. You need setup the tinkerbell provision engine before working on the workflow. 17 | 18 | ```bash 19 | curl -sLS https://raw.githubusercontent.com/tinkerbell/tink/master/setup.sh | sh 20 | ``` 21 | 22 | ### Fix NAT 23 | 24 | ```bash 25 | 26 | # Fix Docker from interfering with NAT 27 | # https://docs.docker.com/network/iptables/ 28 | iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT 29 | 30 | # Now setup NAT from the internal network to the public network 31 | # https://www.revsys.com/writings/quicktips/nat.html 32 | iptables -t nat -A POSTROUTING -o bond0 -j MASQUERADE 33 | iptables -A FORWARD -i bond0 -o enp1s0f1 -m state --state RELATED,ESTABLISHED -j ACCEPT 34 | iptables -A FORWARD -i enp1s0f1 -o bond0 -j ACCEPT 35 | ``` 36 | 37 | ### Build workflow action docker images 38 | 39 | Customise the cloud-init stage with an SSH key from the provisioner. 40 | 41 | Run `ssh-keygen` on the provisioner, then hit enter to each prompt. 42 | 43 | Now run `cat ~/.ssh/id_rsa.pub` and paste the value into the `ssh_authorized_keys` section of `./05-cloud-init/cloud-init.sh`: 44 | 45 | ```yaml 46 | ssh_authorized_keys: 47 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8TlZp6SMhZ3OCKxWbRAwOsuk8alXapXb7GQV4DPwZ+ug1AtkDCSSzPGZI6PP3rFILfobQdw6/t/GT3TKwQ1HY2vYqikWXG7YjT6r5IlsaaZ6y3KAuestYx2lG8I+MCbLmvcjo4k2qeJuf2yj331izRkeNRlRx/VWFUAtoCw2Kr2oZK+LbV8Ewv+x6jMVn9+NgxmMj+fHj9ajVtDacVvyJ8cStmRmOyIGd+rPKDb8txJT4FYXIsy5URhioni7QQuJcXN/qqy4TSY+EaYkGUo2j91MuDJZbdQYniOV4ODS8At/a/Ua51x+ia6Y51pCHMvPsm7DFhK13EQUXhIGdPVY3 root@tf-provisioner 48 | ``` 49 | 50 | Each image from 00-07 will be created as a Docker image and then pushed to the registry. 51 | 52 | ```bash 53 | ./create_images.sh 54 | ``` 55 | 56 | ### Build provision images for worker 57 | 58 | Archives will be required for: 59 | * rootfs 60 | * kernel 61 | * modules 62 | * initrd 63 | 64 | Since we are using Packet's infrastructure, we can also use their image builder and custom repository. 65 | 66 | A Docker build will be run to reproduce for tar.gz files which need to be copied into Nginx's root, where OSIE will serve them to the worker. 67 | 68 | The initial Terraform uses the c3.small.x86 worker type, so use the following parameters to configure Ubuntu 18.04. 69 | 70 | ```bash 71 | # Elevate to root privileges 72 | 73 | sudo -i 74 | 75 | # 76 | 77 | apt update && apt install -qy git git-lfs fakeroot jq 78 | git clone https://github.com/packethost/packet-images && \ 79 | cd packet-images && \ 80 | git-lfs install 81 | 82 | ``` 83 | 84 | Optional step if using Vagrant instead of Packet's infrastructure: 85 | 86 | Edit the `./tools/get-ubuntu-image` file with nano or vim and add `:80` to the `+gpg --keyserver` line. 87 | 88 | ``` 89 | diff --git a/tools/get-ubuntu-image b/tools/get-ubuntu-image 90 | index cd983e031..40d4a6757 100755 91 | --- a/tools/get-ubuntu-image 92 | +++ b/tools/get-ubuntu-image 93 | @@ -25,7 +25,7 @@ path="http://cdimage.ubuntu.com/ubuntu-base/releases/$version/release" 94 | file=ubuntu-base-$fullversion-base-$arch.tar.gz 95 | 96 | echo "Fetching keys" 97 | -gpg --keyserver hkp://ha.pool.sks-keyservers.net --recv-keys \ 98 | +gpg --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-keys \ 99 | 843938DF228D22F7B3742BC0D94AA3F0EFE21092 \ 100 | C5986B4F1257FFA86632CBA746181433FBB75451 101 | ``` 102 | 103 | Now run this to build the image: 104 | 105 | ``` 106 | # This will take a few minutes 107 | ./tools/build.sh -d ubuntu_18_04 -p c3.small.x86 -a x86_64 -b ubuntu_18_04-c3.small.x86 108 | ``` 109 | 110 | ### Deploy images 111 | 112 | #### Packet users 113 | 114 | ```bash 115 | # ls -l /var/tinkerbell/nginx/misc/osie/current/ubuntu_18_04/ 116 | total 397756 117 | -rw-r--r-- 1 root root 278481368 May 19 08:54 image.tar.gz 118 | -rw-r--r-- 1 root root 25380938 May 19 08:54 initrd.tar.gz 119 | -rw-r--r-- 1 root root 7896480 May 19 08:54 kernel.tar.gz 120 | -rw-r--r-- 1 root root 65386698 May 19 08:54 modules.tar.gz 121 | 122 | # Now copy the output so that it's available to be served over HTTP 123 | mkdir -p /var/tinkerbell/nginx/misc/osie/current/ubuntu_18_04 124 | cp *.tar.gz /var/tinkerbell/nginx/misc/osie/current/ubuntu_18_04/ 125 | ``` 126 | 127 | #### Vagrant users 128 | 129 | Take note that users of Vagrant will find the Nginx root directory available at `/usr/share/nginx` instead of `/var/tinkerbell/nginx`. 130 | 131 | ```bash 132 | # ls -l ./work/ubuntu_18_04-c3.small.x86/ 133 | total 397756 134 | -rw-r--r-- 1 root root 278481368 May 19 08:54 image.tar.gz 135 | -rw-r--r-- 1 root root 25380938 May 19 08:54 initrd.tar.gz 136 | -rw-r--r-- 1 root root 7896480 May 19 08:54 kernel.tar.gz 137 | -rw-r--r-- 1 root root 65386698 May 19 08:54 modules.tar.gz 138 | 139 | # Now copy the output so that it's available to be served over HTTP 140 | mkdir -p /vagrant/deploy/state/webroot/misc/osie/current/ubuntu_18_04 141 | 142 | # Make the individual archives: 143 | ./tools/archive-ubuntu ./ubuntu_18_04-c3.small.x86-image.tar.gz ./ 144 | 145 | mv ubuntu_18_04-c3.small.x86-image.tar.gz images.tar.gz 146 | cp ./*.tar.gz /vagrant/deploy/state/webroot/misc/osie/current/ubuntu_18_04/ 147 | ``` 148 | 149 | #### Internal Equinix use only 150 | 151 | Internal Equinix users can run, however this is not recommended for general use. 152 | 153 | ```bash 154 | # 1. git-lfs 155 | apt-get install git-lfs 156 | #2. get-ubuntu-image 157 | wget https://raw.githubusercontent.com/packethost/packet-images/master/tools/get-ubuntu-image 158 | #3. make get-ubuntu-image executable 159 | chmod +x get-ubuntu-image 160 | #4. packet-save2image 161 | wget https://raw.githubusercontent.com/packethost/packet-images/master/tools/packet-save2image 162 | #5. set packet-save2image to executable 163 | chmod +x packet-save2image 164 | #6. Download Dockerfile 165 | wget https://raw.githubusercontent.com/packethost/packet-images/ubuntu_18_04-base/x86_64/Dockerfile 166 | #7. Download Image: 167 | ./get-ubuntu-image 16.04 x86_64 . 168 | #8. Build: 169 | docker build -t custom-ubuntu-16 . 170 | #9. Save 171 | docker save custom-ubuntu-16 > custom-ubuntu-16.tar 172 | #10. Package: 173 | ./packet-save2image < custom-ubuntu-16.tar > image.tar.gz 174 | ``` 175 | 176 | ### Register the hardware 177 | 178 | 1. Download this repo to your provisioner 179 | 180 | 2. Use `vim` to modify the `generate.sh` file to create the `hw1.json` file for your baremetal server 181 | 182 | For Vagrant users the worker machine's MAC address is `08:00:27:00:00:01` 183 | 184 | ```bash 185 | #!/bin/bash 186 | 187 | export UUID=$(uuidgen|tr "[:upper:]" "[:lower:]") #UUID will be generated by uuidgen 188 | export MAC=b8:59:9f:e0:f6:8c # Change this MAC address to your worker node PXE port mac address, it has to match. 189 | ``` 190 | 191 | Now run `./generate.sh` to create the `hardware.json` file which contains the MAC address and a unique UUID. 192 | 193 | 3. Login into tink-cli client and push hw1.json and ubuntu.tmpl into tink 194 | You need copy both hw1.json and ubuntu.tmpl file to tink cli before you can push them into tink 195 | 196 | 3.1 Create hardware 197 | 198 | ```bash 199 | # Run the CLI from within Docker 200 | docker exec -it deploy_tink-cli_1 sh 201 | 202 | # push the hardware information to tink database 203 | /tmp # tink hardware push --file /tmp/hw1.json 204 | ``` 205 | 206 | ### Create the template and workflow 207 | 208 | 1. Create workflow template 209 | 210 | ```bash 211 | # Save ubuntu.tmpl to a file /tmp/ubuntu.tmpl 212 | 213 | # Create a template based upon the output 214 | tink template create -n 'ubuntu' -p /tmp/ubuntu.tmpl 215 | ``` 216 | 217 | 1.1 Create workflow 218 | 219 | ```bash 220 | # See the output from Terraform 221 | export MAC="" 222 | 223 | # See tink template list 224 | export TEMPLATE_ID="" 225 | tink workflow create -t "$TEMPLATE_ID" -r '{"device_1": "'$MAC'"}' 226 | ``` 227 | 228 | ### Reboot worker 229 | 230 | For Packet and on-premises reboot the worker node. This will trigger workflow and you can monitor it using the `tink workflow events` command. 231 | 232 | For Vagrant users, run `vagrant up worker` instead. 233 | 234 | ``` 235 | /tmp # tink workflow events f588090f-e64b-47e9-b8d0-a3eed1dc5439 236 | +--------------------------------------+-----------------+-----------------+----------------+---------------------------------+--------------------+ 237 | | WORKER ID | TASK NAME | ACTION NAME | EXECUTION TIME | MESSAGE | ACTION STATUS | 238 | +--------------------------------------+-----------------+-----------------+----------------+---------------------------------+--------------------+ 239 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | disk-wipe | 0 | Started execution | ACTION_IN_PROGRESS | 240 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | disk-wipe | 7 | Finished Execution Successfully | ACTION_SUCCESS | 241 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | disk-partition | 0 | Started execution | ACTION_IN_PROGRESS | 242 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | disk-partition | 12 | Finished Execution Successfully | ACTION_SUCCESS | 243 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | install-root-fs | 0 | Started execution | ACTION_IN_PROGRESS | 244 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | install-root-fs | 8 | Finished Execution Successfully | ACTION_SUCCESS | 245 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | install-grub | 0 | Started execution | ACTION_IN_PROGRESS | 246 | | 90e16ddd-a4ce-4591-bb91-3ec1eddd0e2b | os-installation | install-grub | 5 | Finished Execution Successfully | ACTION_SUCCESS | 247 | +--------------------------------------+-----------------+-----------------+----------------+---------------------------------+--------------------+ 248 | ``` 249 | 250 | Important note: if you need to re-run the provisioning workflow, you need to run `tink workflow create` again. 251 | 252 | Vagrant users will want to open the VirtualBox app and increase the Display scaling size to 300% if they are using a 4k or Retina display. 253 | 254 | ### Did something go wrong? 255 | 256 | If anything appears to go wrong, you can log into the OSIE environment which was netbooted. 257 | 258 | * Login with `root` (no password) 259 | * Run `docker ps -a` to find the exited Docker container for the Tinkerbell worker 260 | * Run `docker logs CONTAINER_ID` where CONTAINER_ID is the whole name, or a few characters from the container ID 261 | 262 | Or in a single command: `docker logs $(docker ps -qa|head -n1)` 263 | 264 | Once you've determined the error, create a new workflow again with `tink workflow create` and reboot the worker. 265 | 266 | If you're using Vagrant, for the time being you will need to run: `vagrant destroy worker --force && vagrant up worker`. 267 | 268 | ### Change the boot order 269 | 270 | You now need to stop the machine from netbooting. 271 | 272 | #### Packet 273 | 274 | Go to the Packet dashboard and click "Server Actions" -> "Disable Always PXE boot". This setting can be toggled as required, or if you need to reprovision a machine. 275 | 276 | Now reboot the worker machine, and it should show GRUB before booting Ubuntu. 277 | 278 | #### On-premises / at-home or with Vagrant 279 | 280 | Now reboot the worker machine, and it should show GRUB before booting Ubuntu. Hit `e` for edit, and remove the text `console=ttyS0, 115200` and hit the key to continue booting (F10) 281 | 282 | If you do not do this, then you will not be able to see the computer boot up and you won't be able to log in to `tty0` through VirtualBox. 283 | 284 | ### Login in for the first time 285 | 286 | The username and password are both `ubuntu` and this must be changed on first logon. To change the initial password or to remove the default password, you can edit `./05-cloud-init/cloud-init.sh` and run `./create-images.sh` again, then re-provision the host. 287 | 288 | You can connect with the packet SOS ssh console or over SSH from the worker, the IP should be 192.168.1.5. 289 | 290 | ```bash 291 | ssh ubuntu@192.168.1.5 292 | ``` 293 | 294 | ## Questions and comments 295 | 296 | Please direct queries to #tinkerbell on [Packet's Slack channel](https://slack.packet.com/) 297 | 298 | ## Authors 299 | 300 | This work is derived [from a sample by Packet and Infracloud](https://github.com/tinkerbell/tink/tree/first-good-workflow/workflow-samples/ubuntu) 301 | 302 | * **Alex Ellis** - Fixed networking and other bugs, user experience & README. Steps for Vagrant. 303 | * **Xin Wang** - *Initial set of fixes and adding cloud-init* - [tink-workflow](https://github.com/wangxin311/tink-workflow) 304 | 305 | License: Apache 2.0 306 | 307 | Copyright: [tink-workflow authors](https://github.com/wangxin311/tink-workflow/graphs/contributors) 308 | -------------------------------------------------------------------------------- /00-base/functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function init() { 4 | # Color codes 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | YELLOW='\033[0;33m' 8 | BLUE='\033[0;34m' 9 | MAGENTA='\033[0;35m' 10 | CYAN='\033[0;36m' 11 | WHITE='\033[0;37m' 12 | BYELLOW='\033[0;33;5;7m' 13 | NC='\033[0m' # No Color 14 | 15 | set -o errexit -o pipefail -o xtrace 16 | } 17 | 18 | function rainbow() { 19 | echo -e "$RED:RED" 20 | echo -e "$GREEN:GREEN" 21 | echo -e "$YELLOW:YELLOW" 22 | echo -e "$BLUE:BLUE" 23 | echo -e "$MAGENTA:MAGENTA" 24 | echo -e "$CYAN:CYAN" 25 | echo -e "$WHITE:WHITE" 26 | echo -e "$BYELLOW:BYELLOW" 27 | echo -e "$NC:NC" 28 | } 29 | 30 | # syntax: phone_home 1.2.3.4 '{"this": "data"}' 31 | function phone_home() { 32 | local tink_host=$1 33 | shift 34 | 35 | puttink "${tink_host}" phone-home "$@" 36 | } 37 | 38 | # syntax: problem 1.2.3.4 '{"problem":"something is wrong"}' 39 | function problem() { 40 | local tink_host=$1 41 | shift 42 | 43 | puttink "${tink_host}" problem "$@" 44 | } 45 | 46 | # syntax: fail 1.2.3.4 "reason" 47 | function fail() { 48 | local tink_host=$1 49 | shift 50 | 51 | puttink "${tink_host}" phone-home '{"type":"failure", "reason":"'"$1"'"}' 52 | } 53 | 54 | # syntax: tink POST 1.2.3.4 phone-home '{"this": "data"}' 55 | function tink() { 56 | local method=$1 tink_host=$2 endpoint=$3 post_data=$4 57 | 58 | curl \ 59 | -f \ 60 | -vvvvv \ 61 | -X "${method}" \ 62 | -H "Content-Type: application/json" \ 63 | -d "${post_data}" \ 64 | "${tink_host}/${endpoint}" 65 | } 66 | 67 | # syntax: puttink 1.2.3.4 phone-home '{"this": "data"}' 68 | function puttink() { 69 | local tink_host=$1 endpoint=$2 post_data=$3 70 | 71 | tink "PUT" "${tink_host}" "${endpoint}" "${post_data}" 72 | } 73 | 74 | # syntax: posttink 1.2.3.4 phone-home '{"this": "data"}' 75 | function posttink() { 76 | local tink_host=$1 endpoint=$2 post_data=$3 77 | 78 | tink "POST" "${tink_host}" "${endpoint}" "${post_data}" 79 | } 80 | 81 | function dns_resolvers() { 82 | declare -ga resolvers 83 | 84 | # shellcheck disable=SC2207 85 | resolvers=($(awk '/^nameserver/ {print $2}' /etc/resolv.conf)) 86 | if [ ${#resolvers[@]} -eq 0 ]; then 87 | resolvers=("147.75.207.207" "147.75.207.208") 88 | fi 89 | } 90 | 91 | function dns_redhat() { 92 | local filename=$1 93 | shift 94 | 95 | for ((i = 0; i <= $((${#resolvers[*]} - 1)); i++)); do 96 | echo "DNS$((i + 1))=${resolvers[i]}" >>"${filename}" 97 | done 98 | } 99 | 100 | function dns_resolvconf() { 101 | local filename=$1 102 | shift 103 | 104 | printf 'nameserver %s\n' "${resolvers[@]}" >"${filename}" 105 | } 106 | 107 | function filter_bad_devs() { 108 | # 7 = loopback devices (can go away) 109 | # 251, 253, 259 = virtio disk (for qemu tests) 110 | # others = SCSI block disks 111 | grep -vE '^(7|8|6[5-9]|7[01]|12[89]|13[0-5]|25[139]):' 112 | } 113 | 114 | # args: device,... 115 | # exits if args are not block or loop devices 116 | function assert_block_or_loop_devs() { 117 | local baddevs 118 | if baddevs=$(lsblk -dnro 'MAJ:MIN' "$@" | filter_bad_devs) && [[ -n $baddevs ]]; then 119 | echo "$0: All devices may only be block or loop devices" >&2 120 | echo "$baddevs" >&2 121 | exit 1 122 | fi 123 | } 124 | 125 | # args: device,... 126 | # exits if args are not of same device type 127 | function assert_same_type_devs() { 128 | # shellcheck disable=SC2207 129 | local majors=($(lsblk -dnro 'MAJ:MIN' "$@" | awk -F: '{print $1}' | sort -u)) 130 | if [[ ${majors[*]} =~ 7 ]] && ((${#majors[*]} > 1)); then 131 | echo "$0: loop back devices can't be mixed with physical devices" 132 | exit 1 133 | fi 134 | } 135 | 136 | # syntax: is_/loop_dev device,... 137 | # returns 0 if true, 1 if false 138 | function is_loop_dev() { 139 | loopdev=1 140 | if [[ $(lsblk -dnro 'MAJ:MIN' "$@") == 7:* ]]; then 141 | loopdev=0 142 | fi 143 | return $loopdev 144 | } 145 | 146 | # syntax: is_uefi,... 147 | # returns 0 if true, 1 if false 148 | function is_uefi() { 149 | [[ -d /sys/firmware/efi ]] 150 | } 151 | 152 | efi_device() { 153 | local efi_path="$1" 154 | [ -n "$efi_path" ] && shift || efi_path="/boot/efi" 155 | findmnt -n --target "$efi_path" "$@" 156 | } 157 | 158 | find_uuid_boot_id() { 159 | efibootmgr -v | grep "$1" | sed 's/^Boot\([0-9a-f]\{4\}\).*/\1/gI;t;d' 160 | } 161 | 162 | # syntax: name key 163 | # expects metadata in stdin 164 | # safely sets $name=$metadata[$key] 165 | # accepts a default value as third param 166 | function set_from_metadata() { 167 | local var=$1 key=$2 168 | local val 169 | val=$(jq -r "select(.$key != null) | .$key") 170 | if [[ -z $val ]]; then 171 | echo "$key is missing, empty or null" >&2 172 | if [[ -z $3 ]]; then 173 | return 1 174 | else 175 | echo "using default value $val for $key" >&2 176 | val=$3 177 | fi 178 | fi 179 | 180 | declare -g "$var=$val" 181 | } 182 | 183 | # syntax: argvalue name switch 184 | # returns 0 if argvalue is not empty, 1 otherwise after printing to stderr 185 | # the message printed to stderr will be "$0: No $name was provided, $switch is required" 186 | function check_required_arg() { 187 | arg=$1 188 | name=$2 189 | switch=$3 190 | if [[ -n $arg ]]; then 191 | return 0 192 | fi 193 | echo "$0: No $name was provided, $switch is required." >&2 194 | return 1 195 | } 196 | 197 | # usage: assert_all_args_consumed OPTIND $@ 198 | # asserts that the caller did not pass in any extra arguments that are not 199 | # handled by getopts 200 | function assert_all_args_consumed() { 201 | local index=$1 202 | shift 203 | if ((index != $# + 1)); then 204 | echo "unexpected positional argument: OPTIND:$index args:$*" >&2 205 | exit 1 206 | fi 207 | } 208 | 209 | # usage: assert_num_disks hwtype num_disks 210 | function assert_num_disks() { 211 | local hwtype=$1 ndisks=$2 212 | local -A type2disks=( 213 | [baremetal_0]=1 214 | [baremetal_1]=2 215 | [baremetal_1e]=1 216 | [baremetal_2]=6 217 | [baremetal_2a]=1 218 | [baremetal_2a2]=1 219 | [baremetal_2a4]=1 220 | [baremetal_2a5]=1 221 | [baremetal_2a6]=1 222 | [baremetal_3]=3 223 | [baremetal_hua]=1 224 | [baremetal_s]=14 225 | ) 226 | 227 | ((ndisks >= type2disks[hwtype])) 228 | } 229 | 230 | # usage: assert_storage_size hwtype blockdev... 231 | function assert_storage_size() { 232 | # TODO: remove when https://github.com/shellcheck/issues/1213 is closed 233 | # shellcheck disable=SC2034 234 | local hwtype=$1 235 | shift 236 | local gig=$((1024 * 1024 * 1024)) 237 | local -A type2storage=( 238 | [baremetal_0]=$((80 * gig)) 239 | [baremetal_1]=$((2 * 120 * gig)) 240 | [baremetal_1e]=$((240 * gig)) 241 | [baremetal_2]=$((6 * 480 * gig)) 242 | [baremetal_2a]=$((340 * gig)) 243 | [baremetal_2a2]=1 244 | [baremetal_2a4]=1 245 | [baremetal_2a5]=1 246 | [baremetal_2a6]=1 247 | [baremetal_3]=$(((2 * 120 + 1600) * gig)) 248 | [baremetal_hua]=1 249 | [baremetal_s]=$(((12 * 2048 + 2 * 480) * gig)) 250 | ) 251 | 252 | local got=0 sz=0 253 | for disk; do 254 | sz=$(blockdev --getsize64 "$disk") 255 | got=$((got + sz)) 256 | done 257 | ((got >= type2storage[hwtype])) 258 | } 259 | 260 | # usage: should_stream $image_url 261 | # returns 0 if image size is unknown or larger than available space in destination 262 | # returns 1 otherwise 263 | function should_stream() { 264 | local image=$1 265 | local dest=$2 266 | 267 | available=$(BLOCKSIZE=1 df --output=avail "$dest" | grep -v Avail) 268 | img_size=$(curl -s -I "$image" | tr -d '\r' | awk 'tolower($0) ~ /content-length/ { print $2 }') 269 | max_size=$((available - (1024 * 1024 * 1024))) # be safe and allow 1G of leeway 270 | 271 | # img_size == 0 is if server can't stat the file, for example some 272 | # backend is dynamically generating the file for whatever reason 273 | if ((img_size == 0)) || ((img_size >= max_size)); then 274 | return 0 275 | else 276 | return 1 277 | fi 278 | } 279 | 280 | # rand31s returns a stream of non-negative random 31-bit integers as uint32s 281 | rand31s() { 282 | od -An -td4 -w4 &2 431 | } 432 | 433 | # marvell_reset uses mvcli to reset the raid card to JBODs 434 | # usage: megaraid_reset disk... 435 | function marvell_reset() { 436 | # dmidecode prints error messages on stdout!!!! 437 | systemmfg=$(dmidecode -s system-manufacturer | head -1) 438 | echo "Marvell hardware raid device is present on system mfg: $systemmfg" 439 | 440 | echo "Marvell-MVCLI - Deleting all VDs" 441 | vds=$(mvcli info -o vd | awk '/id:/ {print $2}') 442 | for vd in $vds; do 443 | echo "Marvell-MVCLI - Deleting VD id:$vd" 444 | echo y | mvcli delete -f -o vd -i "$vd" 445 | done 446 | } 447 | 448 | # perc_reset uses perccli to reset the raid card to JBODs 449 | # usage: perc_reset disk... 450 | function perc_reset() { 451 | # dmidecode prints error messages on stdout!!!! 452 | systemmfg=$(dmidecode -s system-manufacturer | head -1) 453 | percmodel=$(perccli64 show all | grep PERC | awk '{print $2}') 454 | echo "Dell PERC hardware raid device is present on system mfg: $systemmfg" 455 | 456 | #Query controller for drive state smart alert info 457 | #NOTE: disks in JBOD do not appear as Online or GOOD. Show all slot info for err state 458 | if perccli64 /call/eall/sall show all | grep "S.M.A.R.T alert flagged by drive" | grep No >/dev/null; then 459 | echo "PERCCLI - Controller drive state - OK" 460 | else 461 | echo "PERCCLI - Controller drive state has problem with SMART data alert! FAIL" 462 | exit 1 463 | fi 464 | 465 | #Check/set personality 466 | if perccli64 /c0 show personality | grep "Current Personality" | grep "HBA-Mode" >/dev/null; then 467 | echo "PERCCLI - Controller in HBA-Mode - OK" 468 | elif [[ $percmodel == 'PERCH710PMini' || $percmodel == 'PERCH740PMini' ]]; then 469 | echo "PERCCLI - Skipping set HBA-Mode. This $percmodel does not support HBA mode" 470 | else 471 | echo "PERCCLI - Setting personality to HBA-Mode" 472 | perccli64 /c0 set personality=HBA 473 | fi 474 | 475 | #Check/delete all VDs! 476 | if perccli64 /c0 /vall show | grep "No VDs" >/dev/null; then 477 | echo "PERCCLI - No VDs configured - OK" 478 | else 479 | echo "PERCCLI - Deleting all VDs" 480 | #This also resets all other configs as well per Dell 481 | perccli64 /c0 /vall delete force 482 | fi 483 | 484 | #Check for jbod and enable if needed 485 | if perccli64 /c0 show jbod | grep "JBOD ON" >/dev/null; then 486 | echo "PERCCLI - JBOD is on - OK" 487 | elif [[ $percmodel == 'PERCH710PMini' || $percmodel == 'PERCH740PMini' ]]; then 488 | echo "PERCCLI - Skipping set JBOD since $percmodel does not support it" 489 | else 490 | echo "PERCCLI - Enable JBOD" 491 | perccli64 /c0 set jbod=on force 492 | fi 493 | 494 | if [[ $percmodel == 'PERCH710PMini' || $percmodel == 'PERCH740PMini' ]]; then 495 | percdisk=$(perccli64 /call/eall/sall show all | grep "[0-9]:[0-9]" | awk '{print $1}' | head -1) 496 | echo "PERCCLI - Creating RAID0 HW RAID on $percmodel" 497 | perccli64 /c0 add vd r0 name=RAID0 drives="$percdisk" 498 | sleep 5 499 | fi 500 | } 501 | 502 | # megaraid_reset uses MegaCli64 to reset the raid card to JBODs 503 | # usage: megaraid_reset disk... 504 | function megaraid_reset() { 505 | # dmidecode prints error messages on stdout!!!! 506 | systemmfg=$(dmidecode -s system-manufacturer | head -1) 507 | echo "LSI hardware raid device is present on system mfg: $systemmfg" 508 | 509 | enc=$(MegaCli64 -EncInfo -a0 | awk '/Device ID/ {print $4}') 510 | slots=$(MegaCli64 -PDList -a0 | awk '/^Slot Number/ {print $3}') 511 | 512 | echo "LSI-MegaCLI - Disabling battery warning at boot" 513 | MegaCli64 -AdpSetProp BatWarnDsbl 1 -a0 514 | 515 | echo "LSI-MegaCLI - Marking physical devices on adapter 0 as 'Good'" 516 | for slot in $slots; do 517 | info=$(MegaCli64 -PDInfo -PhysDrv "[$enc:$slot]" -a0 | sed -n '/^Firmware state: / s|Firmware state: ||p') 518 | ! [[ $info =~ bad ]] && continue 519 | MegaCli64 -PDMakeGood -PhysDrv "[$enc:$slot]" -Force -a0 520 | done 521 | 522 | echo "LSI-MegaCLI - Clearing controller of any foreign configs" 523 | MegaCli64 -CfgForeign -Clear -a0 524 | 525 | echo "LSI-MegaCLI - Clearing controller config to defaults" 526 | MegaCli64 -CfgClr -a0 527 | 528 | echo "LSI-MegaCLI - Deleting all LDs" 529 | MegaCli64 -CfgLdDel -LALL -a0 530 | 531 | echo "LSI-MegaCLI - Configuring controller as JBOD" 532 | MegaCli64 -AdpSetProp -EnableJBOD -0 -a0 533 | MegaCli64 -AdpSetProp -EnableJBOD -1 -a0 534 | for slot in $slots; do 535 | info=$(MegaCli64 -PDInfo -PhysDrv "[$enc:$slot]" -a0 | sed -n '/^Firmware state: / s|Firmware state: ||p') 536 | [[ $info =~ JBOD ]] && continue 537 | MegaCli64 -PDMakeJBOD -PhysDrv "[$enc:$slot]" -a0 538 | done 539 | 540 | if ! [[ $systemmfg =~ Dell ]]; then 541 | MegaCli64 -AdpSetProp -EnableJBOD -0 -a0 542 | echo "Creating pseudo JBOD config on the controller" 543 | echo "LSI-MegaCLI - Creating JBOD with single disk raid0 arrays" 544 | MegaCli64 -CfgEachDskRaid0 WT RA Direct NoCachedBadBBU -a0 545 | fi 546 | 547 | sleep 5 548 | udevadm settle 549 | } 550 | 551 | # detect_os detects the target os by first calling `lsb_release` in the rootdir 552 | # via `chroot`, falling back to using the patched lsb_release bash script 553 | # embedded in osie. 554 | # usage: detect_os $rootdir 555 | # returns 2 strings: os version 556 | function detect_os() { 557 | local rootdir os version 558 | rootdir=$1 559 | 560 | os=$(chroot "$rootdir" lsb_release -si | sed 's/ //g' || :) 561 | version=$(chroot "$rootdir" lsb_release -sr || :) 562 | [[ -n $os ]] && [[ -n $version ]] && echo "$os $version" && return 563 | 564 | os=$(ROOTDIR=$rootdir ./packet_lsb_release -si) 565 | version=$(ROOTDIR=$rootdir ./packet_lsb_release -sr) 566 | echo "$os $version" 567 | } 568 | 569 | # use xmlstarlet to find the value of a specific matched element (or attribute) 570 | function xml_ev() { 571 | local _xml="${1}" _match="${2}" _value="${3}" 572 | 573 | (echo "${_xml}" | xmlstarlet sel -t -m "${_match}" -v "${_value}") || echo "" 574 | } 575 | 576 | # use xmlstarlet to select a specific XML element and all elements contained within 577 | function xml_elem() { 578 | local _xml="${1}" _match="${2}" 579 | 580 | echo "${_xml}" | xmlstarlet sel -t -c "${_match}" 581 | } 582 | 583 | # convert a bash associative array to json 584 | function bash_aa_to_json() { 585 | local _json="" 586 | eval "local -A _f_array=""${1#*=}" 587 | 588 | # shellcheck disable=SC2154 589 | for k in "${!_f_array[@]}"; do 590 | if [ "${_json}" = "" ]; then 591 | _json="\"${k}\": \"${_f_array[k]}\"" 592 | else 593 | _json="${_json}, \"${k}\": \"${_f_array[k]}\"" 594 | fi 595 | done 596 | 597 | _json="{ ${_json} }" 598 | echo -n "${_json}" 599 | } 600 | 601 | function set_root_pw() { 602 | # TODO 603 | # FIXME: make sure we don't log pwhash whenever osie logging to kibana happens 604 | # TODO 605 | echo -e "${GREEN}#### Setting rootpw${NC}" 606 | sed -i "s|^root:[^:]*|root:$1|" "$2" 607 | grep '^root' "$2" 608 | } 609 | 610 | function vmlinuz_version() { 611 | local kernel=$1 612 | 613 | set +o pipefail 614 | type=$(file -b "$kernel") 615 | case "$type" in 616 | *MS-DOS*) 617 | echo 'kernel is type MS-DOS' >&2 # huawei devs mostly 618 | strings <"$kernel" | sed -n 's|^Linux version \(\S\+\).*|\1|p' 619 | ;; 620 | *gzip*) 621 | echo 'kernel is type gzip' >&2 # 2a 622 | gunzip <"$kernel" | strings | sed -n 's|^Linux version \(\S\+\).*|\1|p' 623 | ;; 624 | *bzImage*) 625 | echo 'kernel is type bzImage' >&2 # x86_64 626 | # shellcheck disable=SC2001 627 | echo "$type" | sed 's|.*, version \(\S\+\) .*|\1|' 628 | ;; 629 | esac 630 | set -o pipefail 631 | } 632 | 633 | function gethost() { 634 | python3 -c "import urllib3;host=urllib3.util.parse_url('$1').host;assert host;print(host)" || : 635 | } 636 | 637 | function is_reachable() { 638 | local host 639 | host=$(gethost "$1" | sed 's|^\[\(.*\)]$||') 640 | 641 | if [[ $host =~ ^[.*]$ ]]; then 642 | echo "host is an ipv6 address, thats not supported" >&2 && exit 1 643 | fi 644 | 645 | ping -c1 -W1 "$host" &>/dev/null 646 | } 647 | 648 | function reacquire_dhcp() { 649 | dhclient -1 "$1" 650 | } 651 | 652 | function ensure_reachable() { 653 | local url=$1 654 | 655 | echo -e "${YELLOW}###### Checking connectivity to \"$url\"...${NC}" 656 | if ! is_reachable "$url"; then 657 | echo -e "${YELLOW}###### Failed${NC}" 658 | echo -e "${YELLOW}###### Reacquiring dhcp for publicly routable ip...${NC}" 659 | reacquire_dhcp "$(ip_choose_if)" 660 | echo -e "${YELLOW}###### OK${NC}" 661 | fi 662 | echo -e "${YELLOW}###### Verifying connectivity to custom url host...${NC}" 663 | is_reachable "$url" 664 | echo -e "${YELLOW}###### OK${NC}" 665 | } 666 | 667 | # determine the default interface to use if ip=dhcp is set 668 | # uses "PACKET_BOOTDEV_MAC" kopt value if it exists 669 | # if none, will use the first "eth" interface that has a carrier link 670 | # falls back to the first "eth" interface alphabetically 671 | # keep sync'ed with installer/alpine/init-*64 672 | # shellcheck disable=SC2019 673 | # shellcheck disable=SC2018 674 | ip_choose_if() { 675 | local mac 676 | mac=$(echo "${PACKET_BOOTDEV_MAC:-}" | tr 'A-Z' 'a-z') 677 | if [ -n "$mac" ]; then 678 | for x in /sys/class/net/eth*; do 679 | [ -e "$x" ] && grep -q "$mac" "$x/address" && echo "${x##*/}" && return 680 | done 681 | fi 682 | 683 | for x in /sys/class/net/eth*; do 684 | [ -e "$x" ] && ip link set "${x##*/}" up 685 | done 686 | 687 | sleep 1 688 | 689 | for x in /sys/class/net/eth*; do 690 | [ -e "$x" ] && grep -q 1 "$x" && echo "${x##*/}" && return 691 | done 692 | 693 | for x in /sys/class/net/eth*; do 694 | [ -e "$x" ] && echo "${x##*/}" && return 695 | done 696 | } 697 | 698 | add_post_install_service() { 699 | local target="$1" 700 | install -Dm700 target-files/bin/packet-post-install.sh "$target/bin/packet-post-install.sh" 701 | 702 | cp -v target-files/services/packet-post-install.service "$target/etc/systemd/system/packet-post-install.service" 703 | ln -s /etc/systemd/system/packet-post-install.service "$target/etc/systemd/system/multi-user.target.wants/packet-post-install.service" 704 | } 705 | --------------------------------------------------------------------------------