├── .github ├── release-please-manifest.json ├── dependabot.yml ├── workflows │ └── release-please.yml └── release-please-config.json ├── vars ├── Debian.yml ├── RedHat.yml ├── empty.yml ├── aarch64.yml └── x86_64.yml ├── .gitignore ├── handlers └── main.yml ├── templates ├── grub │ ├── centos.j2 │ └── debian.j2 └── talos.j2 ├── meta └── main.yml ├── defaults └── main.yml ├── LICENSE ├── CHANGELOG.md ├── README.md └── tasks └── main.yml /.github/release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "0.5.0" 3 | } -------------------------------------------------------------------------------- /vars/Debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | update_grub_cmd: /usr/sbin/update-grub 4 | -------------------------------------------------------------------------------- /vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | update_grub_cmd: /usr/sbin/grub2-mkconfig 4 | -------------------------------------------------------------------------------- /vars/empty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This file intentionally does not define any variables. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _cfgs 2 | facts/ 3 | tests/ 4 | ansible.cfg 5 | talos-patch.yaml 6 | talos.yaml 7 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update-grub 4 | shell: "{{ update_grub_cmd }}" 5 | become: yes 6 | -------------------------------------------------------------------------------- /templates/grub/centos.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec tail -n +3 $0 3 | 4 | menuentry "Talos" { 5 | linux ($root)/talos-kernel {{ talos_cmdline }} {{ talos_cmdline_net }} {{ talos_cmdline_addon }} 6 | initrd ($root)/talos-initrd.xz 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | commit-message: 7 | prefix: "chore:" 8 | open-pull-requests-limit: 8 9 | rebase-strategy: disabled 10 | schedule: 11 | interval: "monthly" 12 | day: "monday" 13 | time: "08:00" 14 | timezone: "UTC" 15 | -------------------------------------------------------------------------------- /vars/aarch64.yml: -------------------------------------------------------------------------------- 1 | 2 | talos_kernel: "https://github.com/siderolabs/talos/releases/download/v{{ talos_version }}/vmlinuz-arm64" 3 | talos_kernel_sha: sha256:f764a4095ff92c5733b3216c0eeab06dd95be0dfde28e1331e02d03c7d225869 4 | talos_initrd: "https://github.com/siderolabs/talos/releases/download/v{{ talos_version }}/initramfs-arm64.xz" 5 | talos_initrd_sha: sha256:d77df1e99e174bfeae0ea24de95f7f1ea1e1a94babd0ad1f2971384fee58c74a 6 | -------------------------------------------------------------------------------- /vars/x86_64.yml: -------------------------------------------------------------------------------- 1 | 2 | talos_kernel: "https://github.com/siderolabs/talos/releases/download/v{{ talos_version }}/vmlinuz-amd64" 3 | talos_kernel_sha: sha256:ea4140652a1fbcea16a4834aa0c13784d66475822b9a1bc33791a7178d675775 4 | talos_initrd: "https://github.com/siderolabs/talos/releases/download/v{{ talos_version }}/initramfs-amd64.xz" 5 | talos_initrd_sha: sha256:1e3c24f035acc64fb821967f8cdcf2da2284bc9b23f1da1193d1a59e18b717c4 6 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | galaxy_info: 4 | role_name: talos-boot 5 | author: Serge Logvinov 6 | description: Add Talo to the GRUB menu or execute it immediately. 7 | license: "license (BSD, MIT)" 8 | min_ansible_version: 2.4 9 | platforms: 10 | - name: Debian 11 | versions: 12 | - all 13 | - name: Ubuntu 14 | versions: 15 | - all 16 | galaxy_tags: 17 | - boot 18 | - system 19 | - talos 20 | - kubernetes 21 | 22 | dependencies: [] 23 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: Release please 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | release-please: 11 | name: Release please 12 | timeout-minutes: 5 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | steps: 19 | - name: Create release PR 20 | id: release 21 | uses: googleapis/release-please-action@v4 22 | with: 23 | config-file: .github/release-please-config.json 24 | manifest-file: .github/release-please-manifest.json 25 | -------------------------------------------------------------------------------- /templates/grub/debian.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec tail -n +3 $0 3 | 4 | {% set mounts = ansible_mounts | map(attribute='mount') | list %} 5 | {% if "/boot" in mounts %}{% set boot_folder = "" %}{% else %}{% set boot_folder = "/boot" %}{% endif %} 6 | menuentry "Talos" { 7 | linux {{ boot_folder }}/talos-kernel {{ talos_cmdline }} {{ talos_cmdline_net }} {{ talos_cmdline_addon }} 8 | initrd {{ boot_folder }}/talos-initrd.xz 9 | } 10 | menuentry "Reset Talos" { 11 | linux {{ boot_folder }}/talos-kernel {{ talos_cmdline }} {{ talos_cmdline_net }} {{ talos_cmdline_addon }} talos.experimental.wipe=system:EPHEMERAL,STATE 12 | initrd {{ boot_folder }}/talos-initrd.xz 13 | } 14 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | 2 | talos_version: 1.10.7 3 | 4 | talos_platform: metal 5 | talos_machineconfig: "" 6 | talos_machineconfig_node_labels: "" 7 | 8 | talos_console: console=tty1 console=ttyS0 consoleblank=0 9 | talos_cmdline: "init_on_alloc=1 slab_nomerge pti=on {{ talos_console }} nvme_core.io_timeout=4294967295 printk.devkmsg=on ima_template=ima-ng ima_appraise=fix ima_hash=sha512 talos.platform={{ talos_platform }} net.ifnames=0" 10 | 11 | talos_interface: "eth0" 12 | talos_cmdline_net: "ip={{ ansible_default_ipv4['address'] }}::{{ ansible_default_ipv4['gateway'] }}:{{ ansible_default_ipv4['netmask'] }}::{{ talos_interface }}:off" 13 | talos_cmdline_addon: "talos.dashboard.disabled=1" 14 | 15 | talos_disk: /dev/vda 16 | 17 | talos_grub: false 18 | talos_kexec: false 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Serge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "pull-request-header": ":robot: I have created a release", 4 | "pull-request-title-pattern": "chore: release v${version}", 5 | "group-pull-request-title-pattern": "chore: release v${version}", 6 | "packages": { 7 | ".": { 8 | "changelog-path": "CHANGELOG.md", 9 | "release-type": "simple", 10 | "skip-github-release": false, 11 | "bump-minor-pre-major": true, 12 | "include-v-in-tag": true, 13 | "draft": false, 14 | "draft-pull-request": true, 15 | "prerelease": false, 16 | "changelog-sections": [ 17 | { 18 | "type": "feat", 19 | "section": "Features", 20 | "hidden": false 21 | }, 22 | { 23 | "type": "fix", 24 | "section": "Bug Fixes", 25 | "hidden": false 26 | }, 27 | { 28 | "type": "*", 29 | "section": "Changelog", 30 | "hidden": false 31 | } 32 | ] 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.5.0](https://github.com/sergelogvinov/ansible-role-talos-boot/compare/v0.4.1...v0.5.0) (2025-10-23) 4 | 5 | 6 | ### Features 7 | 8 | * ignore interfaces ([cdc39ee](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/cdc39eecbba4f3c5aa0596d23ea7c96579b47d22)) 9 | * update vesion to 1.10.7 ([d19a7e6](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/d19a7e63d77d2219a9a4eac4d74eade4dddf8dcf)) 10 | 11 | ## [0.4.1](https://github.com/sergelogvinov/ansible-role-talos-boot/compare/v0.4.0...v0.4.1) (2024-11-02) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * ipv6 only network ([ee7af82](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/ee7af821e653e31497718c34fa77fdec00952295)) 17 | * talos apply ([b4b4bc3](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/b4b4bc31073981f4c8d80e6bb1eb9d9c1ec1d0ca)) 18 | 19 | ## [0.4.0](https://github.com/sergelogvinov/ansible-role-talos-boot/compare/v0.3.0...v0.4.0) (2024-10-13) 20 | 21 | 22 | ### Features 23 | 24 | * support link local ipv4 ([a6fcb03](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/a6fcb0353cb98d34f6a11b794780fffbf5d44435)) 25 | * talos machine config patch ([864c53d](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/864c53de28e20415ea8ed0b9f72b8dd4f021a700)) 26 | 27 | ## [0.3.0](https://github.com/sergelogvinov/ansible-role-talos-boot/compare/v0.2.0...v0.3.0) (2024-02-29) 28 | 29 | 30 | ### Features 31 | 32 | * add dependabot and release-please configurations ([c1271d5](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/c1271d5065deb4251df0def6ddf9bf73a05534f7)) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * debian boot menu ([0c5e102](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/0c5e1023107de4588505dc1d0e0af46c4d1d5458)) 38 | * grub menu ([b3ff05f](https://github.com/sergelogvinov/ansible-role-talos-boot/commit/b3ff05f84ad73ba272807b9ddc0c7bb846da4a67)) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible role talos-boot 2 | 3 | If your server does not have DHCP, PXE, or IPMI in your network, it can be challenging to automate the deployment of Talos operating systems. 4 | In such cases, you may want to consider using an Ansible role to simplify the process. 5 | This role downloads the Talos kernel and launch the OS by command `kexec -l --initrd= --append=`. 6 | It also sets the networks configuration in kernel parameters, witch helps you to boot OS with static IP. 7 | 8 | Unfortunately, not all servers support booting a kernel through kexec. 9 | Some servers may have hardware or firmware limitations that prevent the use of kexec, or the operating system may not have the necessary drivers or support for kexec. 10 | In such cases, you can set the boot menu to boot the Talos kernel on the next boot and then reboot the server. 11 | 12 | ## Install 13 | 14 | ```shell 15 | ansible-galaxy role install git+https://github.com/sergelogvinov/ansible-role-talos-boot.git,main 16 | ``` 17 | 18 | Or galaxy storage 19 | 20 | ```shell 21 | ansible-galaxy role install sergelogvinov.talos-boot 22 | ``` 23 | 24 | ## Options 25 | 26 | Useful variables 27 | 28 | 1. ```talos_version: 1.7.7``` - version on distributive 29 | 2. ```talos_grub: true``` - add boot menu to the grub (boot menu) 30 | 3. ```talos_kexec: true``` - dowwnload and run talos 31 | 4. ```talos_interface: eth0``` - network interface 32 | 33 | ## Launch Talos 34 | 35 | ```yaml 36 | # talos.yml 37 | 38 | - hosts: all 39 | vars: 40 | talos_grub: true 41 | talos_kexec: true 42 | 43 | # Stream logs 44 | # 45 | # talos_cmdline_addon: "talos.logging.kernel=udp://1.2.3.4:5044" 46 | 47 | # IPv4 network 48 | # Default network configuration, if you have ipv6-only network, comment ipv4 and uncomment ipv6 block 49 | # 50 | talos_cmdline_net: "ip={{ ansible_default_ipv4['address'] }}::{{ ansible_default_ipv4['gateway'] }}:{{ ansible_default_ipv4['netmask'] }}::{{ talos_interface }}:off" 51 | 52 | # IPv6 network 53 | # 54 | # talos_cmdline_net: "ip=[{{ ansible_default_ipv6['address'] }}]::[{{ ansible_default_ipv6['gateway'] }}]:{{ ansible_default_ipv6['prefix'] }}::{{ talos_interface }}:off:[2001:4860:4860::8888]:[2606:4700::1111]:[2606:4700:f1::1]" 55 | roles: 56 | - ansible-role-talos-boot 57 | ``` 58 | 59 | ```yaml 60 | # talos.ini 61 | 62 | [all] 63 | talos ansible_host=1.2.3.4 ansible_ssh_user=debian # ansible_port=112233 64 | ``` 65 | 66 | Deploy Talos to the server 67 | 68 | ```shell 69 | ansible-playbook -Dv -i talos.ini talos.yml 70 | ``` 71 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | 2 | - include_vars: "{{ item }}" 3 | with_first_found: 4 | - "../vars/{{ ansible_facts['os_family'] }}.yml" 5 | - "../vars/empty.yml" 6 | 7 | - include_vars: "{{ item }}" 8 | with_first_found: 9 | - "../vars/{{ ansible_facts['architecture'] }}.yml" 10 | - "../vars/empty.yml" 11 | 12 | - name: Get Talos 13 | become: true 14 | get_url: 15 | url: "{{ item.url }}" 16 | dest: "/boot/{{ item.name }}" 17 | checksum: "{{ item.checksum }}" 18 | with_items: 19 | - { url: "{{ talos_kernel }}", checksum: "{{ talos_kernel_sha }}", name: talos-kernel } 20 | - { url: "{{ talos_initrd }}", checksum: "{{ talos_initrd_sha }}", name: talos-initrd.xz } 21 | 22 | - name: Add grub menu (debian) 23 | become: true 24 | template: 25 | src: grub/debian.j2 26 | dest: /etc/grub.d/10_talos 27 | mode: 0755 28 | when: talos_grub and ansible_facts["os_family"] == "Debian" 29 | notify: Update-grub 30 | 31 | - name: Add grub menu (redhat) 32 | become: true 33 | template: 34 | src: grub/centos.j2 35 | dest: /etc/grub.d/10_talos 36 | mode: 0755 37 | when: talos_grub and ansible_facts["os_family"] == "RedHat" 38 | notify: Update-grub 39 | 40 | - name: talos network patch 41 | connection: local 42 | template: 43 | src: "talos.j2" 44 | dest: "{{ playbook_dir }}/../talos-patch.yaml" 45 | when: talos_machineconfig != "" 46 | 47 | - meta: flush_handlers 48 | 49 | - name: packages (debian) 50 | become: true 51 | apt: 52 | name: 'kexec-tools' 53 | update_cache: yes 54 | cache_valid_time: 3600 55 | when: talos_kexec and ansible_facts["os_family"] == "Debian" 56 | 57 | - name: Run Talos 58 | block: 59 | - name: Run kexec 60 | become: true 61 | command: "kexec {{ item }}" 62 | timeout: 30 63 | register: result 64 | failed_when: result.rc != 0 65 | with_items: 66 | - "-l /boot/talos-kernel --initrd=/boot/talos-initrd.xz --append=\"{{ talos_cmdline }} {{ talos_cmdline_net }} {{ talos_cmdline_addon }}\"" 67 | - "-e" 68 | when: talos_kexec 69 | 70 | rescue: 71 | - name: check server 72 | connection: local 73 | command: "ping -c 15 {{ ansible_facts['default_ipv4']['address'] }}" 74 | when: talos_kexec 75 | 76 | - name: Manual talos apply command 77 | ansible.builtin.debug: 78 | msg: talosctl apply-config --insecure --nodes {{ ansible_facts["default_ipv4"]["address"] }} --config-patch @talos-patch.yaml --file {{ talos_machineconfig }} 79 | when: talos_machineconfig != "" and ansible_facts["default_ipv4"]["gateway"] is defined 80 | 81 | - name: Wait for manual interruption 82 | ansible.builtin.pause: 83 | minutes: '1' 84 | when: talos_kexec and talos_machineconfig != "" 85 | 86 | - name: Apply talos machineconfig 87 | connection: local 88 | ansible.builtin.command: 89 | cmd: talosctl apply-config --insecure --nodes {{ ansible_facts["default_ipv4"]["address"] }} --config-patch @talos-patch.yaml --file {{ talos_machineconfig }} 90 | chdir: "{{ playbook_dir }}/.." 91 | when: talos_kexec and talos_machineconfig != "" and ansible_facts["default_ipv4"]["gateway"] is defined 92 | -------------------------------------------------------------------------------- /templates/talos.j2: -------------------------------------------------------------------------------- 1 | machine: 2 | kubelet: 3 | extraArgs: 4 | {% if talos_machineconfig_node_labels != '' %} 5 | node-labels: "{{ talos_machineconfig_node_labels }}" 6 | {% endif %} 7 | nodeIP: 8 | validSubnets: 9 | {% if ansible_default_ipv4 is defined and ansible_default_ipv4['address'] is defined %} 10 | - "{{ ansible_default_ipv4['address'] }}/32" 11 | {% endif %} 12 | {% if ansible_default_ipv6 is defined and ansible_default_ipv6['address'] is defined and ansible_default_ipv6['address'][:4] != 'fe80' %} 13 | - "{{ ansible_default_ipv6['address'] }}/128" 14 | {% endif %} 15 | network: 16 | hostname: "{{ server_hostname|default(inventory_hostname)|split('.')|first }}" 17 | interfaces: 18 | - interface: eth0 19 | dhcp: false 20 | addresses: 21 | {% set cidrv4 = ansible_default_ipv4['address'] + '/' + ansible_default_ipv4['prefix'] if ansible_default_ipv4 is defined and ansible_default_ipv4['address'] is defined %} 22 | {% set cidrv6 = ansible_default_ipv6['address'] + '/' + ansible_default_ipv6['prefix'] if ansible_default_ipv6 is defined and ansible_default_ipv6['address'] is defined %} 23 | {% if cidrv4 is defined and cidrv4 != "" %} 24 | - "{{ cidrv4 }}" 25 | {% endif %} 26 | {% if cidrv6 is defined and cidrv6 != "" and cidrv6['address'][:4] != 'fe80' %} 27 | - "{{ cidrv6 }}" 28 | {% endif %} 29 | routes: 30 | {% if ansible_default_ipv4 is defined and ansible_default_ipv4['address'] is defined %} 31 | {% if not (ansible_default_ipv4['gateway'] | ansible.utils.ipaddr(cidrv4)) %} 32 | - network: "{{ ansible_default_ipv4['gateway'] }}/32" 33 | {% endif %} 34 | - network: "0.0.0.0/0" 35 | gateway: "{{ ansible_default_ipv4['gateway'] }}" 36 | {% endif %} 37 | {% if ansible_default_ipv6 is defined and ansible_default_ipv6['address'] is defined and ansible_default_ipv6['address'][:4] != 'fe80' %} 38 | {% if ansible_default_ipv6['gateway'][:4] != 'fe80' and (not (ansible_default_ipv6['gateway'] | ansible.utils.ipaddr(cidrv6))) %} 39 | - network: "{{ ansible_default_ipv6['gateway'] }}/128" 40 | {% endif %} 41 | - network: "::/0" 42 | gateway: "{{ ansible_default_ipv6['gateway'] }}" 43 | {% endif %} 44 | {% for interface in ansible_interfaces|sort if not interface in [ansible_default_ipv4['alias'],'lo','eth0','dummy0','dummy1','wg0','wg1'] %}{% set iface = ansible_facts[interface] %} 45 | {% if iface.type is defined and iface.type == "ether" and iface.pciid is defined and iface.macaddress is defined 46 | and not iface.module in ['cdc_ether'] %} 47 | - interface: {{ interface }} 48 | ignore: true 49 | {% endif %} 50 | {% endfor %} 51 | kubespan: 52 | enabled: true 53 | install: 54 | image: ghcr.io/siderolabs/installer:v{{ talos_version }} 55 | bootloader: true 56 | wipe: true 57 | {% set mounts = ansible_facts.mounts | selectattr('mount', 'in', ['/boot', '/']) | map(attribute='device') | reject('regex', '^/dev/mapper/.+') | list %} 58 | {% if mounts | length > 0 %} 59 | disk: {{ mounts[0] | regex_replace('p?[0-9]+$', '') }} 60 | {% else %} 61 | disk: {{ talos_disk }} 62 | {% endif %} 63 | --------------------------------------------------------------------------------