From d4dd2f186ea9c5977ea66d3fcc13da8f1553b9d3 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 8 Sep 2019 00:20:46 +0200 Subject: major refactoring for cloud install --- chaos-at-home/cloud-install.yml | 10 +- common/cloud-install.yml | 40 +++++++ dan/cloud-install.yml | 10 +- dan/host_vars/sk-2019vm.yml | 10 ++ dan/host_vars/sk-cloudia.yml | 18 +-- dan/sk-2019vm.yml | 7 ++ inventory/group_vars/hcloud/main.yml | 2 - inventory/group_vars/hroot/main.yml | 2 - inventory/host_vars/sk-2019vm.yml | 8 ++ inventory/host_vars/sk-cloudia.yml | 8 +- inventory/hosts.ini | 2 + roles/cloud-install/defaults/main.yml | 3 - roles/cloud-install/filter_plugins/hroot.py | 33 ------ roles/cloud-install/tasks/hetzner_installimage.yml | 38 ------- roles/cloud-install/tasks/install_hcloud.yml | 83 -------------- roles/cloud-install/tasks/install_hroot.yml | 124 --------------------- roles/cloud-install/tasks/main.yml | 8 -- roles/cloud-install/tasks/post_hcloud.yml | 6 - .../templates/hetzner_installimage.conf.j2 | 24 ---- .../cloud-install/templates/hetzner_postinst.sh.j2 | 46 -------- roles/cloud/install/filter_plugins/hroot.py | 33 ++++++ roles/cloud/install/tasks/hcloud.yml | 68 +++++++++++ roles/cloud/install/tasks/hetzner_installimage.yml | 38 +++++++ roles/cloud/install/tasks/hroot.yml | 109 ++++++++++++++++++ roles/cloud/install/tasks/main.yml | 2 + .../install/templates/hetzner_installimage.conf.j2 | 26 +++++ .../cloud/install/templates/hetzner_postinst.sh.j2 | 46 ++++++++ roles/cloud/post-install/tasks/hcloud.yml | 6 + roles/cloud/post-install/tasks/hroot.yml | 2 + roles/cloud/post-install/tasks/main.yml | 2 + spreadspace/cloud-install.yml | 10 ++ 31 files changed, 439 insertions(+), 385 deletions(-) create mode 100644 common/cloud-install.yml create mode 100644 dan/host_vars/sk-2019vm.yml create mode 100644 dan/sk-2019vm.yml create mode 100644 inventory/host_vars/sk-2019vm.yml delete mode 100644 roles/cloud-install/defaults/main.yml delete mode 100644 roles/cloud-install/filter_plugins/hroot.py delete mode 100644 roles/cloud-install/tasks/hetzner_installimage.yml delete mode 100644 roles/cloud-install/tasks/install_hcloud.yml delete mode 100644 roles/cloud-install/tasks/install_hroot.yml delete mode 100644 roles/cloud-install/tasks/main.yml delete mode 100644 roles/cloud-install/tasks/post_hcloud.yml delete mode 100644 roles/cloud-install/templates/hetzner_installimage.conf.j2 delete mode 100644 roles/cloud-install/templates/hetzner_postinst.sh.j2 create mode 100644 roles/cloud/install/filter_plugins/hroot.py create mode 100644 roles/cloud/install/tasks/hcloud.yml create mode 100644 roles/cloud/install/tasks/hetzner_installimage.yml create mode 100644 roles/cloud/install/tasks/hroot.yml create mode 100644 roles/cloud/install/tasks/main.yml create mode 100644 roles/cloud/install/templates/hetzner_installimage.conf.j2 create mode 100644 roles/cloud/install/templates/hetzner_postinst.sh.j2 create mode 100644 roles/cloud/post-install/tasks/hcloud.yml create mode 100644 roles/cloud/post-install/tasks/hroot.yml create mode 100644 roles/cloud/post-install/tasks/main.yml create mode 100644 spreadspace/cloud-install.yml diff --git a/chaos-at-home/cloud-install.yml b/chaos-at-home/cloud-install.yml index 24d8d9c2..9b5bbee9 100644 --- a/chaos-at-home/cloud-install.yml +++ b/chaos-at-home/cloud-install.yml @@ -1,6 +1,10 @@ --- -- name: basic installation +- name: setup variables hosts: "{{ hostname }}" gather_facts: no - roles: - - role: cloud-install + tasks: + - set_fact: + install_cooked: "{{ install }}" + network_cooked: "{{ network }}" + +- import_playbook: ../common/cloud-install.yml diff --git a/common/cloud-install.yml b/common/cloud-install.yml new file mode 100644 index 00000000..79e22378 --- /dev/null +++ b/common/cloud-install.yml @@ -0,0 +1,40 @@ +--- +- name: basic installation + hosts: "{{ hostname }}" + gather_facts: no + roles: + - role: cloud/install + +- name: wait for new vm to start up + hosts: "{{ hostname }}" + gather_facts: no + tasks: + ## TODO: find a better way to fetch host key of new VMs + - name: disable ssh StrictHostKeyChecking for the next step + set_fact: + ansible_ssh_extra_args: -o StrictHostKeyChecking=no + - name: wait for vm to start up + wait_for_connection: + delay: 5 + timeout: 120 + - name: reenable StrictHostKeyChecking + set_fact: + ansible_ssh_extra_args: "" + +- name: Apply VM configuration roles + hosts: "{{ hostname }}" + pre_tasks: + - name: make sure to update cached facts + setup: + roles: + - role: cloud/post-install + +- import_playbook: "../{{ hostenv }}/{{ hostname }}.yml" + +- name: reboot and wait for VM come back + hosts: "{{ hostname }}" + gather_facts: no + roles: + - role: reboot-and-wait + reboot_delay: 10 + reboot_timeout: 120 diff --git a/dan/cloud-install.yml b/dan/cloud-install.yml index 24d8d9c2..9b5bbee9 100644 --- a/dan/cloud-install.yml +++ b/dan/cloud-install.yml @@ -1,6 +1,10 @@ --- -- name: basic installation +- name: setup variables hosts: "{{ hostname }}" gather_facts: no - roles: - - role: cloud-install + tasks: + - set_fact: + install_cooked: "{{ install }}" + network_cooked: "{{ network }}" + +- import_playbook: ../common/cloud-install.yml diff --git a/dan/host_vars/sk-2019vm.yml b/dan/host_vars/sk-2019vm.yml new file mode 100644 index 00000000..53629208 --- /dev/null +++ b/dan/host_vars/sk-2019vm.yml @@ -0,0 +1,10 @@ +$ANSIBLE_VAULT;1.2;AES256;dan +64313638393461613535643731303830343539313333643462633232303936346665636536313630 +6261376532663565343434376633613930613331626530380a633235326261306166356166636363 +32636530656665303633373331353565626534646466666336636561376638323834646262633636 +3633656465366263640a653837613439363438653366643763323933366361323938326439373138 +36323638633530323630323133386332303965353866353831383961333363613933373132353663 +35393938326630356261336136633763316436366435313965306166656138393032306434363861 +62383632636239653233626535316361376637646564333861323936343833383030303139346135 +39303735623038633661626238616638373061643762336339366434303162633731646432626364 +3432 diff --git a/dan/host_vars/sk-cloudia.yml b/dan/host_vars/sk-cloudia.yml index 53629208..7bb3860d 100644 --- a/dan/host_vars/sk-cloudia.yml +++ b/dan/host_vars/sk-cloudia.yml @@ -1,10 +1,10 @@ $ANSIBLE_VAULT;1.2;AES256;dan -64313638393461613535643731303830343539313333643462633232303936346665636536313630 -6261376532663565343434376633613930613331626530380a633235326261306166356166636363 -32636530656665303633373331353565626534646466666336636561376638323834646262633636 -3633656465366263640a653837613439363438653366643763323933366361323938326439373138 -36323638633530323630323133386332303965353866353831383961333363613933373132353663 -35393938326630356261336136633763316436366435313965306166656138393032306434363861 -62383632636239653233626535316361376637646564333861323936343833383030303139346135 -39303735623038633661626238616638373061643762336339366434303162633731646432626364 -3432 +66326237393963643938383265396133366166376563616436303365353661366232616539373163 +6139613939613033626635616564613463663333376130350a386262326435303164346234666536 +31373463396433366661356461663439303437393738336331346465333530373332623530656561 +6464656633633865620a323839386230626532303639356164613264333565633863306438363866 +37393234656630333034346233653730333837323833313838653536343161623963643162633936 +31636264356664613236313439616630366133653038396165613561613237656534353839666135 +36396133306565343861323237316237383330383734666164343766306130373233373930653738 +63393363653863613137613637313030353765643966343132363230636536303163663262326234 +3237 diff --git a/dan/sk-2019vm.yml b/dan/sk-2019vm.yml new file mode 100644 index 00000000..10a633b0 --- /dev/null +++ b/dan/sk-2019vm.yml @@ -0,0 +1,7 @@ +--- +- name: Basic Setup + hosts: sk-2019vm + roles: + - role: base + - role: sshd + - role: zsh diff --git a/inventory/group_vars/hcloud/main.yml b/inventory/group_vars/hcloud/main.yml index 83219510..564ce6e1 100644 --- a/inventory/group_vars/hcloud/main.yml +++ b/inventory/group_vars/hcloud/main.yml @@ -1,4 +1,2 @@ --- cloud_provider: hcloud - -hcloud_api_token: "{{ vault_hcloud_api_token }}" diff --git a/inventory/group_vars/hroot/main.yml b/inventory/group_vars/hroot/main.yml index 828a3720..7802f3ad 100644 --- a/inventory/group_vars/hroot/main.yml +++ b/inventory/group_vars/hroot/main.yml @@ -1,4 +1,2 @@ --- cloud_provider: hroot - -hroot_robot_account: "{{ vault_hroot_robot_account }}" diff --git a/inventory/host_vars/sk-2019vm.yml b/inventory/host_vars/sk-2019vm.yml new file mode 100644 index 00000000..34a43bb6 --- /dev/null +++ b/inventory/host_vars/sk-2019vm.yml @@ -0,0 +1,8 @@ +--- +install: + cloud_credentials: "{{ vault_hroot_robot_account }}" + disks: + layout: nvme_raid + root_lvm_size: 10G + +network: {} diff --git a/inventory/host_vars/sk-cloudia.yml b/inventory/host_vars/sk-cloudia.yml index d6664d4d..b169d5b5 100644 --- a/inventory/host_vars/sk-cloudia.yml +++ b/inventory/host_vars/sk-cloudia.yml @@ -1,2 +1,8 @@ --- -hetzner_root_lvm_size: 15G +install: + cloud_credentials: "{{ vault_hroot_robot_account }}" + disks: + layout: nvme_raid + root_lvm_size: 15G + +network: {} diff --git a/inventory/hosts.ini b/inventory/hosts.ini index 8681ba99..6b26014a 100644 --- a/inventory/hosts.ini +++ b/inventory/hosts.ini @@ -119,6 +119,7 @@ ansible_port=222 sk2013 host_name=2013 ansible_port=22000 sk2016 host_name=2016 ansible_port=22000 sk-cloudia host_name=cloudia +sk-2019vm host_name=2019vm [ele-ap] @@ -195,6 +196,7 @@ r3-cccamp19-av sk2013 sk2016 sk-cloudia +sk-2019vm [hcloud] emc-00 diff --git a/roles/cloud-install/defaults/main.yml b/roles/cloud-install/defaults/main.yml deleted file mode 100644 index 75e47612..00000000 --- a/roles/cloud-install/defaults/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -hetzner_disk_setup: "{% if cloud_provider == 'hroot' %}nvme_raid{% elif cloud_provider == 'hcloud' %}hcloud{% endif %}" -hetzner_root_lvm_size: all diff --git a/roles/cloud-install/filter_plugins/hroot.py b/roles/cloud-install/filter_plugins/hroot.py deleted file mode 100644 index d2abff1b..00000000 --- a/roles/cloud-install/filter_plugins/hroot.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -from ansible import errors - - -def extract_ssh_key_fingerprints(data): - try: - return [k['key']['fingerprint'] for k in data] - except Exception as e: - raise errors.AnsibleFilterError("hroot_ssh_key_fingerprints(): %s" % str(e)) - - -def extract_serverip(data, server_name): - try: - ips = [s['server']['server_ip'] for s in data if s['server']['server_name'] == server_name] - if len(ips): - return ips[0] - raise errors.AnsibleFilterError("hroot_extract_serverip(): server %s not found" % server_name) - except Exception as e: - raise errors.AnsibleFilterError("hroot_extract_serverip(): %s" % str(e)) - - -class FilterModule(object): - - ''' Ansible math jinja2 filters ''' - - def filters(self): - return { - 'hroot_extract_ssh_key_fingerprints': extract_ssh_key_fingerprints, - 'hroot_extract_serverip': extract_serverip, - } diff --git a/roles/cloud-install/tasks/hetzner_installimage.yml b/roles/cloud-install/tasks/hetzner_installimage.yml deleted file mode 100644 index f54a785b..00000000 --- a/roles/cloud-install/tasks/hetzner_installimage.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -- name: determine latest image name - shell: | - set -o pipefail - shopt -s nocaseglob - ls /root/.oldroot/nfs/images/{{ install_distro }}-*-{{ install_codename }}-64-minimal.tar.gz | sort -r | head -n 1 - args: - executable: /bin/bash - check_mode: no - changed_when: false - register: latest_image - -- name: generate installimage config - template: - src: hetzner_installimage.conf.j2 - dest: /root/installimage.conf - -- name: generate postinst script - template: - src: hetzner_postinst.sh.j2 - dest: /root/postinst.sh - mode: 0755 - -- name: run installimage - command: /root/.oldroot/nfs/install/installimage -a -c installimage.conf -x postinst.sh - register: hetzner_installimage_cmd - changed_when: true - args: - chdir: /root - -- name: "print installimage output" - debug: - msg: "{{ hetzner_installimage_cmd.stdout_lines + hetzner_installimage_cmd.stderr_lines }}" - -- name: "check if installimage succeeded" - fail: - msg: "failed to run installimage" - when: "hetzner_installimage_cmd.rc != 0 or 'postinst.sh finished successfully' not in hetzner_installimage_cmd.stdout_lines" diff --git a/roles/cloud-install/tasks/install_hcloud.yml b/roles/cloud-install/tasks/install_hcloud.yml deleted file mode 100644 index 68e8db60..00000000 --- a/roles/cloud-install/tasks/install_hcloud.yml +++ /dev/null @@ -1,83 +0,0 @@ ---- -- name: retrieve ssh key ids - uri: - url: "https://api.hetzner.cloud/v1/ssh_keys" - method: GET - headers: - Authorization: "Bearer {{ hcloud_api_token }}" - status_code: 200 - register: sshkeys - delegate_to: localhost - -- name: retrieve server id and check if rescue mode is already active - uri: - url: "https://api.hetzner.cloud/v1/servers?name={{ inventory_hostname }}" - method: GET - headers: - Authorization: "Bearer {{ hcloud_api_token }}" - status_code: 200 - register: serverstatus - delegate_to: localhost - -- name: do not continue in check mode - fail: - msg: "can not bootstrap new servers in check mode" - when: ansible_check_mode | bool - check_mode: no - -### TODO: for now we add all ssh keys that are installed for this project - this might not be a good idea! -- name: activate rescue mode - when: not serverstatus.json.servers[0].rescue_enabled - uri: - url: "https://api.hetzner.cloud/v1/servers/{{ serverstatus.json.servers[0].id }}/actions/enable_rescue" - method: POST - body: "{{ {'type': 'linux64', 'ssh_keys': (sshkeys.json.ssh_keys | map(attribute='id') | list) } | to_nice_json }}" - headers: - Authorization: "Bearer {{ hcloud_api_token }}" - Content-Type: "application/json" - status_code: 201 - delegate_to: localhost - -- name: do a hardware reset - uri: - url: "https://api.hetzner.cloud/v1/servers/{{ serverstatus.json.servers[0].id }}/actions/reset" - method: POST - headers: - Authorization: "Bearer {{ hcloud_api_token }}" - status_code: 201 - delegate_to: localhost - -### TODO: would be nice to get the SSH host key from robot -- name: completely ignore ssh host keys for now - set_fact: - old_ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }}" - ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" - -- name: wait for rescue system to start up - wait_for_connection: - delay: 30 - timeout: 120 - -- include_tasks: hetzner_installimage.yml - -- name: reboot - shell: sleep 2 && shutdown -r now "triggered by ansible after running installimage" - async: 1 - poll: 0 - ignore_errors: True - changed_when: True - -### TODO: SSH host key handling needs to be improved -- name: automatically accept new ssh host key - set_fact: - ansible_ssh_extra_args: "{{ old_ansible_ssh_extra_args }} -o StrictHostKeyChecking=no" - -- name: wait for host to start up - wait_for_connection: - delay: 15 - timeout: 120 - -### TODO: SSH host key handling needs to be improved -- name: re-enable ssh host key checking - set_fact: - ansible_ssh_extra_args: "{{ old_ansible_ssh_extra_args }}" diff --git a/roles/cloud-install/tasks/install_hroot.yml b/roles/cloud-install/tasks/install_hroot.yml deleted file mode 100644 index 6c57d874..00000000 --- a/roles/cloud-install/tasks/install_hroot.yml +++ /dev/null @@ -1,124 +0,0 @@ ---- -- name: retrieve ssh key fingerprints - uri: - url: "https://robot-ws.your-server.de/key" - method: GET - user: "{{ hroot_robot_account.username }}" - password: "{{ hroot_robot_account.password }}" - force_basic_auth: yes - status_code: 200 - register: sshkeys - delegate_to: localhost - check_mode: no - -- name: do not continue in check mode - fail: - msg: "can not bootstrap new servers in check mode" - when: ansible_check_mode | bool - check_mode: no - -- block: - - name: retrieve server list from robot - uri: - url: "https://robot-ws.your-server.de/server" - method: GET - user: "{{ hroot_robot_account.username }}" - password: "{{ hroot_robot_account.password }}" - force_basic_auth: yes - status_code: 200 - register: servers - delegate_to: localhost - check_mode: no - - - name: extract server IP address from robot result - set_fact: - hetzner_main_ip: "{{ servers.json | hroot_extract_serverip(host_name) }}" - - when: hetzner_main_ip is not defined - -- name: display warning message - pause: - prompt: | - *** Danger **** - will be bootstraping host {{ inventory_hostname }} with main IP {{ hetzner_main_ip }} ... - ALL DATA WILL BE LOST!!! press CTRL-C then A to abort. - seconds: 15 - -- name: check if rescue mode is already active - uri: - url: "https://robot-ws.your-server.de/boot/{{ hetzner_main_ip }}/rescue" - method: GET - user: "{{ hroot_robot_account.username }}" - password: "{{ hroot_robot_account.password }}" - force_basic_auth: yes - status_code: 200 - register: rescuestatus - delegate_to: localhost - check_mode: no - -### TODO: for now we add all ssh keys that are installed in the robot - this might not be a good idea! -- name: activate rescue mode - when: not rescuestatus.json.rescue.active - uri: - url: "https://robot-ws.your-server.de/boot/{{ hetzner_main_ip }}/rescue" - method: POST - user: "{{ hroot_robot_account.username }}" - password: "{{ hroot_robot_account.password }}" - force_basic_auth: yes - body: "os=linux&arch=64&authorized_key[]={{ sshkeys.json | hroot_extract_ssh_key_fingerprints | join('&authorized_key[]=') }}" - status_code: 200 - headers: - Content-Type: "application/x-www-form-urlencoded" - delegate_to: localhost - -- name: wait for the rescue mode to become active - pause: - seconds: 5 - -- name: do a hardware reset - uri: - url: "https://robot-ws.your-server.de/reset/{{ hetzner_main_ip }}" - method: POST - user: "{{ hroot_robot_account.username }}" - password: "{{ hroot_robot_account.password }}" - force_basic_auth: yes - body: "type=hw" - status_code: 200 - headers: - Content-Type: "application/x-www-form-urlencoded" - delegate_to: localhost - -### TODO: would be nice to get the SSH host key from robot -- name: completely ignore ssh host keys for now - set_fact: - old_ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }}" - ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" - -- name: wait for rescue system to start up - wait_for_connection: - delay: 30 - timeout: 120 - -- include_tasks: hetzner_installimage.yml - -- name: reboot - shell: sleep 2 && shutdown -r now "triggered by ansible after running installimage" - async: 1 - poll: 0 - ignore_errors: True - changed_when: True - -### TODO: SSH host key handling needs to be improved -- name: automatically accept new ssh host key - set_fact: - ansible_ssh_extra_args: "{{ old_ansible_ssh_extra_args }} -o StrictHostKeyChecking=no" - -- name: wait for host to start up - wait_for_connection: - delay: 15 - timeout: 120 - -### TODO: SSH host key handling needs to be improved -- name: re-enable ssh host key checking - set_fact: - ansible_ssh_extra_args: "{{ old_ansible_ssh_extra_args }}" diff --git a/roles/cloud-install/tasks/main.yml b/roles/cloud-install/tasks/main.yml deleted file mode 100644 index 6d9eb96e..00000000 --- a/roles/cloud-install/tasks/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- include_tasks: "install_{{ cloud_provider }}.yml" - -- name: force facts cache to get updated - setup: - -- when: lookup('first_found', ['post_' + cloud_provider + '.yml'], errors='ignore') - include_tasks: "post_{{ cloud_provider }}.yml" diff --git a/roles/cloud-install/tasks/post_hcloud.yml b/roles/cloud-install/tasks/post_hcloud.yml deleted file mode 100644 index 96108c58..00000000 --- a/roles/cloud-install/tasks/post_hcloud.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: install additional packages - apt: - name: - - qemu-guest-agent - state: present diff --git a/roles/cloud-install/templates/hetzner_installimage.conf.j2 b/roles/cloud-install/templates/hetzner_installimage.conf.j2 deleted file mode 100644 index 801d448f..00000000 --- a/roles/cloud-install/templates/hetzner_installimage.conf.j2 +++ /dev/null @@ -1,24 +0,0 @@ -HOSTNAME {{ host_name }} -{% if hetzner_disk_setup == "nvme_raid" %} -DRIVE1 /dev/nvme0n1 -DRIVE2 /dev/nvme1n1 -SWRAID 1 -SWRAIDLEVEL 1 -{% elif hetzner_disk_setup == "sata_raid" %} -DRIVE1 /dev/sda -DRIVE2 /dev/sdb -SWRAID 1 -SWRAIDLEVEL 1 -{% elif hetzner_disk_setup == "hcloud" %} -DRIVE1 /dev/sda -{% endif %} -BOOTLOADER grub -PART /boot ext4 512M -PART lvm {{ host_name }} {{ hetzner_root_lvm_size }} -{% if hetzner_root_lvm_size != "all" %} -PART /dummy ext4 all -{% endif %} -LV {{ host_name }} root / ext4 2560M -LV {{ host_name }} var /var ext4 1280M -LV {{ host_name }} var+log /var/log ext4 768M -IMAGE {{ latest_image.stdout }} diff --git a/roles/cloud-install/templates/hetzner_postinst.sh.j2 b/roles/cloud-install/templates/hetzner_postinst.sh.j2 deleted file mode 100644 index 660c0ea5..00000000 --- a/roles/cloud-install/templates/hetzner_postinst.sh.j2 +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -set -euf -o pipefail - -export DEBIAN_FRONTEND=noninteractive -apt-get update -q -apt-get full-upgrade -y -q -apt-get install -y -q --no-install-recommends openssh-server python python-apt - -passwd -d root && passwd -l root -{% if install_distro == "debian" %} -sed -e 's/^allow-hotplug/auto/' -i /etc/network/interfaces -{% endif %} -sed -r 's#(\s+/var/log\s+ext4\s+)defaults#\1noatime,nodev,noexec#g' -i /etc/fstab - -mkdir -p -m 0700 /target/root/.ssh -cat < /root/.ssh/authorized_keys -{{ ssh_keys_root | join('\n') }} -EOK -{% if hostvars[hostname].ansible_port is defined %} -sed -e 's/^\(\s*#*\s*Port.*\)/Port {{ hostvars[hostname].ansible_port }}/' -i /etc/ssh/sshd_config -{% endif %} - -{# this is actually only needed on ubuntu bionic and beyond but should not hurt on other installations either #} -swapoff -a -sed -e '/^\/swapfile/d' -i /etc/fstab -rm -f /swapfile - -{% if hetzner_root_lvm_size != "all" %} -umount /dummy -sed -e '/\/dummy/d' -i /etc/fstab -rm -rf /dummy - -raid_devices=$(mdadm -Q -Y --detail /dev/md2 2> /dev/null | awk -F = '/MD_DEVICE_.*_DEV=/ { print($2) }') -if [ -n "$raid_devices" ]; then - mdadm --stop /dev/md2 2> /dev/null - for dev in $raid_devices; do - wipefs -a "$dev" - done - sed -e '/^ARRAY \/dev\/md\/2 /d' -i /etc/mdadm/mdadm.conf - update-initramfs -u -fi -{% endif %} - -update-grub - -echo "postinst.sh finished successfully" diff --git a/roles/cloud/install/filter_plugins/hroot.py b/roles/cloud/install/filter_plugins/hroot.py new file mode 100644 index 00000000..d2abff1b --- /dev/null +++ b/roles/cloud/install/filter_plugins/hroot.py @@ -0,0 +1,33 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible import errors + + +def extract_ssh_key_fingerprints(data): + try: + return [k['key']['fingerprint'] for k in data] + except Exception as e: + raise errors.AnsibleFilterError("hroot_ssh_key_fingerprints(): %s" % str(e)) + + +def extract_serverip(data, server_name): + try: + ips = [s['server']['server_ip'] for s in data if s['server']['server_name'] == server_name] + if len(ips): + return ips[0] + raise errors.AnsibleFilterError("hroot_extract_serverip(): server %s not found" % server_name) + except Exception as e: + raise errors.AnsibleFilterError("hroot_extract_serverip(): %s" % str(e)) + + +class FilterModule(object): + + ''' Ansible math jinja2 filters ''' + + def filters(self): + return { + 'hroot_extract_ssh_key_fingerprints': extract_ssh_key_fingerprints, + 'hroot_extract_serverip': extract_serverip, + } diff --git a/roles/cloud/install/tasks/hcloud.yml b/roles/cloud/install/tasks/hcloud.yml new file mode 100644 index 00000000..ca5435b8 --- /dev/null +++ b/roles/cloud/install/tasks/hcloud.yml @@ -0,0 +1,68 @@ +--- +- name: retrieve ssh key ids + uri: + url: "https://api.hetzner.cloud/v1/ssh_keys" + method: GET + headers: + Authorization: "Bearer {{ install_cooked.cloud_credentials.token }}" + status_code: 200 + register: sshkeys + delegate_to: localhost + +- name: retrieve server id and check if rescue mode is already active + uri: + url: "https://api.hetzner.cloud/v1/servers?name={{ inventory_hostname }}" + method: GET + headers: + Authorization: "Bearer {{ install_cooked.cloud_credentials.token }}" + status_code: 200 + register: serverstatus + delegate_to: localhost + +- name: do not continue in check mode + fail: + msg: "can not bootstrap new servers in check mode" + when: ansible_check_mode | bool + check_mode: no + +### TODO: for now we add all ssh keys that are installed for this project - this might not be a good idea! +- name: activate rescue mode + when: not serverstatus.json.servers[0].rescue_enabled + uri: + url: "https://api.hetzner.cloud/v1/servers/{{ serverstatus.json.servers[0].id }}/actions/enable_rescue" + method: POST + body: "{{ {'type': 'linux64', 'ssh_keys': (sshkeys.json.ssh_keys | map(attribute='id') | list) } | to_nice_json }}" + headers: + Authorization: "Bearer {{ install_cooked.cloud_credentials.token }}" + Content-Type: "application/json" + status_code: 201 + delegate_to: localhost + +- name: do a hardware reset + uri: + url: "https://api.hetzner.cloud/v1/servers/{{ serverstatus.json.servers[0].id }}/actions/reset" + method: POST + headers: + Authorization: "Bearer {{ install_cooked.cloud_credentials.token }}" + status_code: 201 + delegate_to: localhost + +### TODO: would be nice to get the SSH host key from robot +- name: completely ignore ssh host keys for now + set_fact: + old_ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }}" + ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" + +- name: wait for rescue system to start up + wait_for_connection: + delay: 30 + timeout: 120 + +- include_tasks: hetzner_installimage.yml + +- name: reboot + shell: sleep 2 && shutdown -r now "triggered by ansible after running installimage" + async: 1 + poll: 0 + ignore_errors: True + changed_when: True diff --git a/roles/cloud/install/tasks/hetzner_installimage.yml b/roles/cloud/install/tasks/hetzner_installimage.yml new file mode 100644 index 00000000..f54a785b --- /dev/null +++ b/roles/cloud/install/tasks/hetzner_installimage.yml @@ -0,0 +1,38 @@ +--- +- name: determine latest image name + shell: | + set -o pipefail + shopt -s nocaseglob + ls /root/.oldroot/nfs/images/{{ install_distro }}-*-{{ install_codename }}-64-minimal.tar.gz | sort -r | head -n 1 + args: + executable: /bin/bash + check_mode: no + changed_when: false + register: latest_image + +- name: generate installimage config + template: + src: hetzner_installimage.conf.j2 + dest: /root/installimage.conf + +- name: generate postinst script + template: + src: hetzner_postinst.sh.j2 + dest: /root/postinst.sh + mode: 0755 + +- name: run installimage + command: /root/.oldroot/nfs/install/installimage -a -c installimage.conf -x postinst.sh + register: hetzner_installimage_cmd + changed_when: true + args: + chdir: /root + +- name: "print installimage output" + debug: + msg: "{{ hetzner_installimage_cmd.stdout_lines + hetzner_installimage_cmd.stderr_lines }}" + +- name: "check if installimage succeeded" + fail: + msg: "failed to run installimage" + when: "hetzner_installimage_cmd.rc != 0 or 'postinst.sh finished successfully' not in hetzner_installimage_cmd.stdout_lines" diff --git a/roles/cloud/install/tasks/hroot.yml b/roles/cloud/install/tasks/hroot.yml new file mode 100644 index 00000000..606df5f6 --- /dev/null +++ b/roles/cloud/install/tasks/hroot.yml @@ -0,0 +1,109 @@ +--- +- name: retrieve ssh key fingerprints + uri: + url: "https://robot-ws.your-server.de/key" + method: GET + user: "{{ install_cooked.cloud_credentials.username }}" + password: "{{ install_cooked.cloud_credentials.password }}" + force_basic_auth: yes + status_code: 200 + register: sshkeys + delegate_to: localhost + check_mode: no + +- name: do not continue in check mode + fail: + msg: "can not bootstrap new servers in check mode" + when: ansible_check_mode | bool + check_mode: no + +- block: + - name: retrieve server list from robot + uri: + url: "https://robot-ws.your-server.de/server" + method: GET + user: "{{ install_cooked.cloud_credentials.username }}" + password: "{{ install_cooked.cloud_credentials.password }}" + force_basic_auth: yes + status_code: 200 + register: servers + delegate_to: localhost + check_mode: no + + - name: extract server IP address from robot result + set_fact: + hetzner_main_ip: "{{ servers.json | hroot_extract_serverip(host_name) }}" + + when: hetzner_main_ip is not defined + +- name: display warning message + pause: + prompt: | + *** Danger **** + will be bootstraping host {{ inventory_hostname }} with main IP {{ hetzner_main_ip }} ... + ALL DATA WILL BE LOST!!! press CTRL-C then A to abort. + seconds: 15 + +- name: check if rescue mode is already active + uri: + url: "https://robot-ws.your-server.de/boot/{{ hetzner_main_ip }}/rescue" + method: GET + user: "{{ install_cooked.cloud_credentials.username }}" + password: "{{ install_cooked.cloud_credentials.password }}" + force_basic_auth: yes + status_code: 200 + register: rescuestatus + delegate_to: localhost + check_mode: no + +### TODO: for now we add all ssh keys that are installed in the robot - this might not be a good idea! +- name: activate rescue mode + when: not rescuestatus.json.rescue.active + uri: + url: "https://robot-ws.your-server.de/boot/{{ hetzner_main_ip }}/rescue" + method: POST + user: "{{ install_cooked.cloud_credentials.username }}" + password: "{{ install_cooked.cloud_credentials.password }}" + force_basic_auth: yes + body: "os=linux&arch=64&authorized_key[]={{ sshkeys.json | hroot_extract_ssh_key_fingerprints | join('&authorized_key[]=') }}" + status_code: 200 + headers: + Content-Type: "application/x-www-form-urlencoded" + delegate_to: localhost + +- name: wait for the rescue mode to become active + pause: + seconds: 5 + +- name: do a hardware reset + uri: + url: "https://robot-ws.your-server.de/reset/{{ hetzner_main_ip }}" + method: POST + user: "{{ install_cooked.cloud_credentials.username }}" + password: "{{ install_cooked.cloud_credentials.password }}" + force_basic_auth: yes + body: "type=hw" + status_code: 200 + headers: + Content-Type: "application/x-www-form-urlencoded" + delegate_to: localhost + +### TODO: would be nice to get the SSH host key from robot +- name: completely ignore ssh host keys for now + set_fact: + old_ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }}" + ansible_ssh_extra_args: "{{ ansible_ssh_extra_args | default('') }} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" + +- name: wait for rescue system to start up + wait_for_connection: + delay: 30 + timeout: 120 + +- include_tasks: hetzner_installimage.yml + +- name: reboot + shell: sleep 2 && shutdown -r now "triggered by ansible after running installimage" + async: 1 + poll: 0 + ignore_errors: True + changed_when: True diff --git a/roles/cloud/install/tasks/main.yml b/roles/cloud/install/tasks/main.yml new file mode 100644 index 00000000..c5cc046f --- /dev/null +++ b/roles/cloud/install/tasks/main.yml @@ -0,0 +1,2 @@ +--- +- include_tasks: "{{ cloud_provider }}.yml" diff --git a/roles/cloud/install/templates/hetzner_installimage.conf.j2 b/roles/cloud/install/templates/hetzner_installimage.conf.j2 new file mode 100644 index 00000000..a30fb94a --- /dev/null +++ b/roles/cloud/install/templates/hetzner_installimage.conf.j2 @@ -0,0 +1,26 @@ +HOSTNAME {{ host_name }} +{% if cloud_provider == "hroot" %} +{% if install_cooked.disks.layout == "nvme_raid" %} +DRIVE1 /dev/nvme0n1 +DRIVE2 /dev/nvme1n1 +SWRAID 1 +SWRAIDLEVEL 1 +{% elif install_cooked.disks.layout == "sata_raid" %} +DRIVE1 /dev/sda +DRIVE2 /dev/sdb +SWRAID 1 +SWRAIDLEVEL 1 +{% endif %} +{% elif cloud_provider == "hcloud" %} +DRIVE1 /dev/sda +{% endif %} +BOOTLOADER grub +PART /boot ext4 512M +PART lvm {{ host_name }} {{ install_cooked.disks.root_lvm_size }} +{% if install_cooked.disks.root_lvm_size != "all" %} +PART /dummy ext4 all +{% endif %} +LV {{ host_name }} root / ext4 2560M +LV {{ host_name }} var /var ext4 1280M +LV {{ host_name }} var+log /var/log ext4 768M +IMAGE {{ latest_image.stdout }} diff --git a/roles/cloud/install/templates/hetzner_postinst.sh.j2 b/roles/cloud/install/templates/hetzner_postinst.sh.j2 new file mode 100644 index 00000000..271e51b7 --- /dev/null +++ b/roles/cloud/install/templates/hetzner_postinst.sh.j2 @@ -0,0 +1,46 @@ +#!/bin/bash +set -euf -o pipefail + +export DEBIAN_FRONTEND=noninteractive +apt-get update -q +apt-get full-upgrade -y -q +apt-get install -y -q --no-install-recommends openssh-server python python-apt + +passwd -d root && passwd -l root +{% if install_distro == "debian" %} +sed -e 's/^allow-hotplug/auto/' -i /etc/network/interfaces +{% endif %} +sed -r 's#(\s+/var/log\s+ext4\s+)defaults#\1noatime,nodev,noexec#g' -i /etc/fstab + +mkdir -p -m 0700 /target/root/.ssh +cat < /root/.ssh/authorized_keys +{{ ssh_keys_root | join('\n') }} +EOK +{% if hostvars[hostname].ansible_port is defined %} +sed -e 's/^\(\s*#*\s*Port.*\)/Port {{ hostvars[hostname].ansible_port }}/' -i /etc/ssh/sshd_config +{% endif %} + +{# this is actually only needed on ubuntu bionic and beyond but should not hurt on other installations either #} +swapoff -a +sed -e '/^\/swapfile/d' -i /etc/fstab +rm -f /swapfile + +{% if install_cooked.disks.root_lvm_size != "all" %} +umount /dummy +sed -e '/\/dummy/d' -i /etc/fstab +rm -rf /dummy + +raid_devices=$(mdadm -Q -Y --detail /dev/md2 2> /dev/null | awk -F = '/MD_DEVICE_.*_DEV=/ { print($2) }') +if [ -n "$raid_devices" ]; then + mdadm --stop /dev/md2 2> /dev/null + for dev in $raid_devices; do + wipefs -a "$dev" + done + sed -e '/^ARRAY \/dev\/md\/2 /d' -i /etc/mdadm/mdadm.conf + update-initramfs -u +fi +{% endif %} + +update-grub + +echo "postinst.sh finished successfully" diff --git a/roles/cloud/post-install/tasks/hcloud.yml b/roles/cloud/post-install/tasks/hcloud.yml new file mode 100644 index 00000000..96108c58 --- /dev/null +++ b/roles/cloud/post-install/tasks/hcloud.yml @@ -0,0 +1,6 @@ +--- +- name: install additional packages + apt: + name: + - qemu-guest-agent + state: present diff --git a/roles/cloud/post-install/tasks/hroot.yml b/roles/cloud/post-install/tasks/hroot.yml new file mode 100644 index 00000000..51315101 --- /dev/null +++ b/roles/cloud/post-install/tasks/hroot.yml @@ -0,0 +1,2 @@ +--- +# nothing to do here. diff --git a/roles/cloud/post-install/tasks/main.yml b/roles/cloud/post-install/tasks/main.yml new file mode 100644 index 00000000..c5cc046f --- /dev/null +++ b/roles/cloud/post-install/tasks/main.yml @@ -0,0 +1,2 @@ +--- +- include_tasks: "{{ cloud_provider }}.yml" diff --git a/spreadspace/cloud-install.yml b/spreadspace/cloud-install.yml new file mode 100644 index 00000000..9b5bbee9 --- /dev/null +++ b/spreadspace/cloud-install.yml @@ -0,0 +1,10 @@ +--- +- name: setup variables + hosts: "{{ hostname }}" + gather_facts: no + tasks: + - set_fact: + install_cooked: "{{ install }}" + network_cooked: "{{ network }}" + +- import_playbook: ../common/cloud-install.yml -- cgit v1.2.3