- hosts: localhost become: true vars: # Debian packages that must be installed debian_packages: - acl - bat - bind9 - build-essential - console-common - console-setup - containerd.io - curl - dialog - docker-buildx-plugin - docker-ce - docker-ce-cli - docker-compose-plugin - firefox-esr - fzf - gawk - git - htop - ipcalc - iptables-persistent - keepassxc - ldap-utils - libldap2-dev - libsasl2-dev - libvirt-clients - libvirt-daemon - libvirt-daemon-config-network - libvirt-daemon-driver-qemu - libvirt-daemon-system - libvirt-daemon-system-systemd - libvirt-dbus - lua5.4 - net-tools - psmisc - python3-dev - python3-pip - qemu-guest-agent - qemu-kvm - remmina - spice-vdagent - sshpass - task-xfce-desktop - unzip - vim-gtk3 - virt-manager - zsh # Version of ASDF itself asdf_version: 0.15.0 # Tools that will be installed using asdf asdf_tools: - java - golang - nodejs - opentofu - packer - terragrunt # If some asdf-installed tools require specific versions, they must be # listed here. Tools that are not listed will default to the latest # version. asdf_tool_versions: java: openjdk-17 nodejs: 22.12.0 opentofu: 1.8.1 packer: 1.11.0 terragrunt: 0.66.9 # Python packages to install python_packages: - ansible>=9,<10 - dnspython - netaddr - python-ldap - xmltodict - pyvmomi - requests - git+https://github.com/vmware/vsphere-automation-sdk-python.git - pywinrm # Version of the git-delta utility git_delta_version: 0.18.2 # Version of the atuin utility atuin_version: 18.4.0 # Vim plugins that need to be installed vim_plugins: - airblade/vim-gitgutter - bling/vim-airline - cespare/vim-toml - ctrlpvim/ctrlp.vim - elzr/vim-json - Exafunction/codeium.vim - fatih/vim-go - Glench/Vim-Jinja2-Syntax - hashivim/vim-terraform - liuchengxu/vim-which-key - mattn/vim-lsp-settings - mbbill/undotree - octol/vim-cpp-enhanced-highlight - PProvost/vim-ps1 - prabirshrestha/asyncomplete-lsp.vim - prabirshrestha/asyncomplete.vim - prabirshrestha/async.vim - prabirshrestha/vim-lsp - rbong/vim-flog - rust-lang/rust.vim - scrooloose/nerdtree - Shougo/dein.vim - skywind3000/asyncrun.vim - tikhomirov/vim-glsl - tpope/vim-fugitive - vim-airline/vim-airline-themes - vim-perl/vim-perl - vim-test/vim-test - wsdjeg/dein-ui.vim - Xuyuanp/nerdtree-git-plugin # Various parameters need to be fetched from env variables domain_name: >- {{ lookup( "env", "VMNET_DOMAIN" ) }} update_key: >- {{ lookup( "env", "VMNET_BIND_KEY_ID" ) }} back_net: >- {{ lookup( "env" , "VMNET_BACK_ADDR" ) }} front_net: >- {{ lookup( "env" , "VMNET_FRONT_ADDR" ) }} back_arpa: >- {{ back_net.split( "." )[0:3] | reverse | join( "." ) }}.in-addr.arpa front_arpa: >- {{ front_net.split( "." )[0:3] | reverse | join( "." ) }}.in-addr.arpa locale: >- {{ lookup( "env", "VM_LOCALE" ) }} chezmoi_source: >- {{ lookup( "env", "CHEZMOI_SOURCE" ) }} tasks: # Configure grub - name: Reduce boot delay to 1s ansible.builtin.lineinfile: path: /etc/default/grub regexp: ^GRUB_TIMEOUT= line: GRUB_TIMEOUT=1 - name: Update Grub configuration ansible.builtin.command: cmd: update-grub2 # Ensure ext4 filesystems are mounted with discard enabled - name: Add discard option to mount points loop: [ "/" , "/boot" ] ansible.builtin.lineinfile: path: /etc/fstab backrefs: true regexp: '^(\S+\s+{{ item }}\s+\S+\s+)(?!(?:\S*,)?discard(?:,\S*)?\s+)(\S+)(\s+.+)$' line: '\1discard,\2\3' # Prepare for docker installation - name: Get APT key for the Docker repo ansible.builtin.shell: cmd: wget -O- https://download.docker.com/linux/debian/gpg > /etc/apt/keyrings/docker.asc - name: Add Docker APT repo ansible.builtin.shell: cmd: | echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] http://download.docker.com/linux/debian \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ > /etc/apt/sources.list.d/docker.list # Install various required packages - name: Remove unnecessary packages ansible.builtin.command: cmd: apt-get autoremove -y - name: Install packages ansible.builtin.apt: update_cache: true name: "{{ debian_packages }}" # Keyboard / locale configuration - name: Copy keyboard config ansible.builtin.copy: src: files/keyboard dest: /etc/default/keyboard force: true - name: Copy locale config ansible.builtin.template: src: files/locale.j2 dest: /etc/default/locale force: true - name: Change locale to {{ locale }} community.general.locale_gen: name: "{{ locale }}" state: present - name: Set timezone community.general.timezone: name: Europe/Paris - name: dpkg-reconfigure ansible.builtin.shell: > dpkg-reconfigure -f noninteractive keyboard-configuration && localectl set-locale LANG={{ locale }} LANGUAGE={{ locale }} && timedatectl set-timezone Europe/Paris && update-locale LANG={{ locale }} # Desktop resizing - name: Create desktop resizing logs directory ansible.builtin.file: path: /var/log/autores state: directory mode: "0700" - name: Copy desktop resizing script ansible.builtin.copy: src: files/resize.sh dest: /usr/local/bin/x-resize mode: "0755" - name: Create udev rule ansible.builtin.copy: content: > ACTION=="change",KERNEL=="card0", SUBSYSTEM=="drm", RUN+="/usr/local/bin/x-resize" dest: /etc/udev/rules.d/50-x-resize.rules mode: "0644" - name: Reload udev rules ansible.builtin.command: cmd: udevadm control --reload-rules # Prevent XFCE from creating random dirs in the user home - name: Create user config directory ansible.builtin.file: path: /home/vagrant/.config state: directory owner: vagrant group: vagrant - name: Configure XDG user dirs ansible.builtin.copy: src: files/user-dirs.dirs dest: /home/vagrant/.config/user-dirs.dirs owner: vagrant group: vagrant # Configure VM networks - name: Configure dummy module ansible.builtin.lineinfile: path: /etc/modprobe.d/local.conf create: true line: options dummy numdummies=0 - name: Configure bridge network devices loop: [0, 1] ansible.builtin.copy: content: | [NetDev] Name=br{{ item }} Kind=bridge dest: /etc/systemd/network/bridge{{ item }}.netdev - name: Configure bridge networks loop: - { id: 0, addr: "{{ back_net }}" } - { id: 1, addr: "{{ front_net }}" } ansible.builtin.copy: content: | [Match] Name=br{{ item.id }} [Network] ConfigureWithoutCarrier=yes LinkLocalAddressing=no Address={{ item.addr }}/24 dest: /etc/systemd/network/bridge{{ item.id }}.network - name: Configure dummy network devices loop: [0, 1] ansible.builtin.copy: content: | [NetDev] Name=dummy{{ item }} Kind=dummy dest: /etc/systemd/network/dummy{{ item }}.netdev - name: Enslave dummy network devices to bridges loop: [0, 1] ansible.builtin.copy: content: | [Match] Name=dummy{{ item }} [Network] Bridge=br{{ item }} ConfigureWithoutCarrier=yes dest: /etc/systemd/network/dummy{{ item }}.network - name: Ensure IPv4 forwarding is enabled ansible.builtin.lineinfile: path: /etc/systemd/network/eth0.network line: IPForward=ipv4 insertafter: ^\[Network\]$ - name: Configure NAT ansible.builtin.copy: src: files/iptables dest: /etc/iptables/rules.v4 # Configure DNS server - name: Disable IPv6 for Bind ansible.builtin.lineinfile: path: /etc/default/named line: OPTIONS="-u bind -4" regexp: ^OPTIONS= - name: Generate dynamic update key when: lookup( "env", "VMNET_BIND_KEY" ) == "" ansible.builtin.shell: cmd: >- tsig-keygen -a hmac-sha512 {{ update_key }}. > /etc/bind/tf-key.conf creates: /etc/bind/tf-key.conf - name: Copy dynamic update key when: lookup( "env", "VMNET_BIND_KEY" ) != "" ansible.builtin.template: src: files/tf-key.conf.j2 dest: /etc/bind/tf-key.conf - name: Configure Bind options ansible.builtin.template: src: files/bind-options dest: /etc/bind/named.conf.options owner: root group: bind - name: Configure local domains ansible.builtin.template: src: files/domains.j2 dest: /etc/bind/named.conf.local owner: root group: bind - name: Initialize zone for domain ansible.builtin.template: src: files/zf-domain.j2 dest: /var/lib/bind/db.{{ domain_name }} owner: bind group: bind - name: Initialize reverse DNS zones loop: - { arpa: "{{ back_arpa }}" , host: vm-host } - { arpa: "{{ front_arpa }}" , host: vm-host-f } ansible.builtin.template: src: files/zf-reverse.j2 dest: /var/lib/bind/db.{{ item.arpa }} owner: bind group: bind - name: Ensure resolution is enabled for external domains ansible.builtin.lineinfile: path: /etc/systemd/network/eth0.network line: Domains=~. insertafter: ^\[Network\]$ - name: Configure systemd-resolved so it queries the local DNS server ansible.builtin.template: src: files/resolved.conf.j2 dest: /etc/systemd/resolved.conf # Download and install delta. The asdf-provided version doesn't work on # Debian. - name: Download git-delta ansible.builtin.get_url: url: https://github.com/dandavison/delta/releases/download/{{ git_delta_version }}/git-delta-musl_{{ git_delta_version }}_amd64.deb dest: /root - name: Install git-delta ansible.builtin.command: cmd: dpkg -i /root/git-delta-musl_{{ git_delta_version }}_amd64.deb # Download and install atuin. - name: Download atuin ansible.builtin.get_url: url: https://github.com/atuinsh/atuin/releases/download/v{{ atuin_version }}/atuin-x86_64-unknown-linux-gnu.tar.gz dest: /tmp - name: Install atuin ansible.builtin.command: cmd: tar xzf /tmp/atuin-x86_64-unknown-linux-gnu.tar.gz --strip-components=1 atuin-x86_64-unknown-linux-gnu/atuin chdir: /usr/local/bin # Ensure virtualization and docker can be used - name: Add the vagrant user to various group ansible.builtin.user: name: vagrant groups: kvm,libvirt,docker append: true - name: Make the QEMU bridge helper setuid ansible.builtin.file: path: /usr/lib/qemu/qemu-bridge-helper group: kvm mode: "6750" - name: Allow QEMU tu use br0 ansible.builtin.copy: content: allow br0 dest: /etc/qemu/bridge.conf - name: Disable libvirt security driver ansible.builtin.lineinfile: path: /etc/libvirt/qemu.conf regexp: ^security_driver\s*= line: >- security_driver = "none" # Set the shell to zsh for the vagrant user - name: Set default shell ansible.builtin.user: name: vagrant shell: /bin/zsh # "Fix" X11 forwarding - name: Fix X11 forwarding through SSH ansible.builtin.copy: dest: /etc/ssh/sshd_config.d/x11forwarding.conf content: | X11UseLocalhost no - name: Configure user account become: false block: - name: Create directories for zsh and other tools loop: - .local/share/zsh - .local/bin - .config/atuin - .ssh ansible.builtin.file: state: directory path: /home/vagrant/{{ item }} mode: "0755" owner: vagrant group: vagrant # Install ASDF - name: Install ASDF ansible.builtin.git: repo: https://github.com/asdf-vm/asdf.git dest: /home/vagrant/.asdf single_branch: true version: v{{ asdf_version }} # Install and run Chezmoi - name: Install chezmoi ansible.builtin.shell: cmd: >- set -e ; export ASDF_DIR=/home/vagrant/.asdf ; . $ASDF_DIR/asdf.sh ; asdf plugin-add chezmoi ; asdf install chezmoi latest ; asdf global chezmoi latest chdir: /home/vagrant - name: Check for known host when: >- chezmoi_source is match( "^ssh://" ) check_mode: true ansible.builtin.lineinfile: path: /home/vagrant/.ssh/known_hosts line: "^{{ chezmoi_source | urlsplit( 'hostname' ) }} " regex: true state: absent register: ssh_key_present - name: Add SSH key for chezmoi's Git repo when: >- chezmoi_source is match( "^ssh://" ) and ssh_key_present is not changed ansible.builtin.shell: cmd: >- ssh-keyscan {{ chezmoi_source | urlsplit( 'hostname' ) }} \ >> /home/vagrant/.ssh/known_hosts - name: Check for chezmoi repo when: chezmoi_source != "" ansible.builtin.stat: path: /home/vagrant/.local/share/chezmoi register: chezmoi_stat - name: Initialize chezmoi when: chezmoi_source != "" and not chezmoi_stat.stat.exists ansible.builtin.shell: cmd: >- set -e ; export ASDF_DIR=/home/vagrant/.asdf ; . $ASDF_DIR/asdf.sh ; chezmoi init {{ chezmoi_source }} ; chezmoi apply chdir: /home/vagrant # If there's not .zshrc, use default config files - name: Check for zshrc ansible.builtin.stat: path: /home/vagrant/.zshrc register: zshrc_stat - when: not zshrc_stat.stat.exists block: # Create the zsh configuration for the vagrant user - name: Copy configuration files loop: - {s: antigen.zsh, d: .local/share/zsh/antigen.zsh} - {s: p10k.zsh, d: .local/share/zsh/p10k.zsh} - {s: atuin.toml, d: .config/atuin/config.toml} - {s: gitconfig, d: .gitconfig} ansible.builtin.copy: src: files/{{ item.s }} dest: /home/vagrant/{{ item.d }} mode: "0644" owner: vagrant group: vagrant - name: Update configuration files loop: - {s: zshrc, d: .zshrc} ansible.builtin.template: src: files/{{ item.s }} dest: /home/vagrant/{{ item.d }} mode: "0644" owner: vagrant group: vagrant # Initialize shell - name: Run shell initialization ansible.builtin.command: cmd: zsh .zshrc chdir: /home/vagrant # Install various tools using asdf - name: Install asdf plugins loop: "{{ asdf_tools }}" ansible.builtin.shell: cmd: >- source .zshrc && asdf plugin-add {{ item }} chdir: /home/vagrant executable: /bin/zsh register: asdf_out failed_when: >- asdf_out.rc != 0 and 'already added' not in asdf_out.stderr - name: Install tools using asdf loop: "{{ asdf_tools }}" ansible.builtin.shell: cmd: >- source .zshrc && asdf install {{ item }} {{ asdf_tool_versions[ item ] | default( "latest" ) }} && asdf global {{ item }} {{ asdf_tool_versions[ item ] | default( "latest" ) }} chdir: /home/vagrant executable: /bin/zsh # Install Ansible 9 and various packages in a Python venv - name: Ensure vagrant user has a .local directory loop: [ bin, share ] ansible.builtin.file: path: /home/vagrant/.local/{{ item }} state: directory - name: Create Ansible virtual environment ansible.builtin.shell: executable: /bin/zsh cmd: >- source .zshrc && { [ -d /home/vagrant/.local/share/ansible ] || python -m venv /home/vagrant/.local/share/ansible; } chdir: /home/vagrant - name: Install Ansible and related Python packages ansible.builtin.shell: executable: /bin/zsh cmd: >- source /home/vagrant/.zshrc && source /home/vagrant/.local/share/ansible/bin/activate && pip install "{{ python_packages | join('" "') }}" chdir: /home/vagrant/.local/share - name: List Ansible executables ansible.builtin.find: paths: /home/vagrant/.local/share/ansible/bin file_type: file patterns: [ "ansible*" ] register: ansible_exes - name: Create Ansible symlinks loop: "{{ ansible_exes.files }}" ansible.builtin.file: state: link src: "{{ item.path }}" dest: /home/vagrant/.local/bin/{{ item.path | basename }} # Configure SSH for the vagrant user - name: Check for a Chezmoi-provided SSH configuration ansible.builtin.stat: path: /home/vagrant/.ssh/config register: chezmoi_ssh_config - name: Configure SSH for the vagrant user when: not chezmoi_ssh_config.stat.exists ansible.builtin.template: src: files/ssh_config.j2 dest: /home/vagrant/.ssh/config # Configure Vim - name: Remove default vimrc ansible.builtin.file: path: /home/vagrant/.vimrc state: absent - name: Install vim configuration ansible.builtin.git: repo: https://git@git.nocternity.net/tseeker-pub/heavim.git dest: /home/vagrant/.vim - name: Create vim cache directory ansible.builtin.file: path: /home/vagrant/.cache/vim state: directory mode: "0700" - name: Create vim plugin directories loop: "{{ vim_plugins | map( 'dirname' ) | unique }}" ansible.builtin.file: path: /home/vagrant/.cache/vim/bundles/repos/github.com/{{ item }} state: directory - name: Clone vim plugins loop: "{{ vim_plugins }}" ansible.builtin.git: repo: https://github.com/{{ item }} dest: /home/vagrant/.cache/vim/bundles/repos/github.com/{{ item }} # Install Rust - name: Install Rust ansible.builtin.shell: cmd: >- set -e ; curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustinit.sh ; sh /tmp/rustinit.sh -y -c rust-analyzer,rust-src,rust-analysis