1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 # build-debian-nspawn-vm.yml - Ansible playbook to build a new Debian/Ubuntu
6 # systemd-nspawn VM from scratch
8 # Run this playbook on a host with systemd-nspawn installed, and it will create
9 # a minimal Debian/Ubuntu system with networking, SSH, and a user account with
10 # full sudo access. This is enough that you can start the VM and run more
11 # Ansible playbooks on it.
13 # The VM expects to work with a private network. It expects the host to provide
14 # a DHCP lease (e.g., the host is running systemd-networkd) and forward IP.
16 # You MUST set the following variables to run this playbook:
18 # * `image_name`: The name of the image to create. This must be a valid DNS
19 # component. Note a container by this name will be started while the playbook
22 # * `image_authorized_keys`: SSH public key string or URL.
24 # Other interesting variables you MAY set include:
26 # * `debootstrap_suite`: The codename of the Debian/Ubuntu release to install,
27 # like 'bookworm' or 'noble'. The default is Debian stable.
29 # * `debootstrap_mirror`: The URL of the Debian/Ubuntu mirror to install from.
30 # You MUST set this to an Ubuntu mirror if you want to install Ubuntu.
32 # * `image_username`, `image_passhash`, `image_gecos`, `image_shell`: These all
33 # define parameters for the user account created inside the VM. For details
34 # about how to generate `image_passhash`, see
35 # <https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module>
37 - name: Bootstrap image
40 image_path: "/var/lib/machines/{{ image_name }}"
41 debootstrap_suite: stable
42 debootstrap_mirror: "http://deb.debian.org/debian"
43 debootstrap_script: "{{ 'gutsy' if debootstrap_mirror is search('\\bubuntu\\b') else 'sid' }}"
47 ansible.builtin.command:
50 - --include=dbus,openssh-server,python3,sudo,systemd
51 - "{{ debootstrap_suite }}"
53 - "{{ debootstrap_mirror }}"
54 - "{{ debootstrap_script }}"
55 creates: "{{ (image_path, 'etc/os-release')|path_join }}"
56 - name: Set up authorized SSH keys for root
58 ansible.posix.authorized_key:
60 path: "{{ (image_path, 'root/.ssh/authorized_keys')|path_join }}"
61 key: "{{ image_authorized_keys }}"
63 - name: Start VM and add host
66 image_interface: host0
68 # We want to start the VM as early as possible because it's easier to
69 # manage the system state when we know it's running. We restart the VM
70 # to ensure it's running from the image we just built.
73 ansible.builtin.systemd_service:
74 name: "systemd-nspawn@{{ image_name }}.service"
77 - name: Enable networking and sshd
79 ansible.builtin.command:
82 - "--machine={{ image_name }}"
87 register: nspawn_enable
88 # Retry if we tried the command faster than the VM could start dbus.
89 until: "nspawn_enable.stderr is not search('^Failed to connect to bus:', multiline=true)"
92 - name: Wait for VM network
94 ansible.builtin.command:
97 - "--machine={{ image_name }}"
99 - /usr/lib/systemd/systemd-networkd-wait-online
100 - "--interface={{ image_interface }}"
102 - name: Get VM network address
104 ansible.builtin.command:
107 - "--machine={{ image_name }}"
112 - "{{ image_interface }}"
113 register: nspawn_netctl
114 - name: Add VM Ansible host
116 vm_addr: "{{ (nspawn_netctl.stdout|from_json).Addresses|selectattr('ScopeString', '==', 'global')|first }}"
117 ansible.builtin.add_host:
119 ansible_host: "{{ vm_addr.Address|join('.' if vm_addr.Family == 2 else ':') }}"
122 - name: Set up VM user with sudo
125 image_username: admin
128 image_shell: /usr/bin/bash
130 - name: Create user account
131 ansible.builtin.user:
132 name: "{{ image_username }}"
133 password: "{{ image_passhash }}"
134 comment: "{{ image_gecos }}"
135 shell: "{{ image_shell }}"
139 - name: Set up authorized SSH keys for user
140 ansible.posix.authorized_key:
141 user: "{{ image_username }}"
142 key: "{{ image_authorized_keys }}"
143 - name: Clean up authorized SSH keys for root
144 ansible.posix.authorized_key:
146 key: "{{ image_authorized_keys }}"
154 ansible.builtin.systemd_service:
155 name: "systemd-nspawn@{{ image_name }}.service"