summaryrefslogtreecommitdiff
path: root/roles/cloud/install
diff options
context:
space:
mode:
Diffstat (limited to 'roles/cloud/install')
-rw-r--r--roles/cloud/install/filter_plugins/hroot.py33
-rw-r--r--roles/cloud/install/tasks/hcloud.yml68
-rw-r--r--roles/cloud/install/tasks/hetzner_installimage.yml38
-rw-r--r--roles/cloud/install/tasks/hroot.yml109
-rw-r--r--roles/cloud/install/tasks/main.yml2
-rw-r--r--roles/cloud/install/templates/hetzner_installimage.conf.j226
-rw-r--r--roles/cloud/install/templates/hetzner_postinst.sh.j246
7 files changed, 322 insertions, 0 deletions
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 <<EOK > /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"