Compare commits

..

2 Commits

Author SHA1 Message Date
Dreaded_X fae6ceff86 Added option to auto install talos 2025-11-11 02:20:43 +01:00
Dreaded_X 33fc7ef33d PXE boot over http 2025-11-11 02:20:23 +01:00
70 changed files with 494 additions and 440 deletions
+5 -1
View File
@@ -5,4 +5,8 @@ indent_style = tab
[*.yaml] [*.yaml]
indent_style = space indent_style = space
indent_size = 2 indent_size = 4
[{*.py,tools/render}]
indent_style = space
indent_size = 4
+1 -2
View File
@@ -1,2 +1 @@
*.key filter=git-crypt diff=git-crypt _secrets.yaml filter=git-crypt diff=git-crypt
secrets.yaml filter=git-crypt diff=git-crypt
-1
View File
@@ -1,4 +1,3 @@
.ipxe/ .ipxe/
rendered/ rendered/
configs/ configs/
.vagrant/
-67
View File
@@ -1,67 +0,0 @@
default_install_hook_types:
- pre-commit
- commit-msg
default_stages:
- pre-commit
repos:
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: builtin
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-executables-have-shebangs
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.1
hooks:
- id: check-jsonschema
files: ^talos/patches/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json",
]
- id: check-jsonschema
files: ^talos/nodes/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json",
]
- id: check-jsonschema
files: ^talos/clusters/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json",
]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
- repo: https://github.com/crate-ci/typos
rev: v1.40.0
hooks:
- id: typos
- repo: https://github.com/sirwart/ripsecrets
rev: v0.1.11
hooks:
- id: ripsecrets-system
- repo: https://github.com/crate-ci/committed
rev: v1.1.8
hooks:
- id: committed
-2
View File
@@ -1,2 +0,0 @@
secrets.yaml
*.key
-6
View File
@@ -65,9 +65,3 @@ Upgrading talos or changing the schematic:
```bash ```bash
talosctl upgrade --nodes <node_id> --image factory.talos.dev/metal-installer/<schematic_id>:<version> talosctl upgrade --nodes <node_id> --image factory.talos.dev/metal-installer/<schematic_id>:<version>
``` ```
To upgrade kubernetes or inline manifests, first apply the updated controlplane configs, then run:
```bash
talosctl upgrade-k8s
```
Vendored
-28
View File
@@ -1,28 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.define "talos-vm" do |vm|
vm.vm.network :private_network,
:type => "dhcp",
:libvirt__network_address => "192.168.1.0",
:libvirt__netmask => "255.255.255.0",
# :libvirt__dhcp_bootp_file => "ipxe.pxe"
:libvirt__dhcp_bootp_file => "http://192.168.1.1:8000/ipxe.pxe"
vm.vm.hostname = "talos"
vm.vm.provider :libvirt do |domain|
domain.cpus = 6
domain.memory = 16 * 1024
domain.storage :file, :size => '100G', :type => 'raw'
domain.mgmt_attach = false
domain.boot "hd"
domain.boot "network"
domain.sysinfo = {
"system": {
"serial": "talos-vm"
}
}
end
end
end
-2
View File
@@ -1,2 +0,0 @@
style = "conventional"
ignore_author_re = "Flux"
+3
View File
@@ -0,0 +1,3 @@
server:
tftpIp: 10.0.0.3
httpUrl: http://10.0.0.3:8000
-2
View File
@@ -1,2 +0,0 @@
[env]
VAGRANT_DEFAULT_PROVIDER = "libvirt"
+30
View File
@@ -0,0 +1,30 @@
schematicId: !schematic default
arch: amd64
talosVersion: v1.11.3
kubernesVersion: v1.34.1
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
extraKernelArgs: []
dns:
- 1.1.1.1
- 8.8.8.8
ntp: nl.pool.ntp.org
install: true
autoInstall: false
patches:
- !patch hostname
- !patch install-disk
- !patch network
- !patch vip
patchesControlPlane:
- !patch allow-control-plane-workloads
+7
View File
@@ -0,0 +1,7 @@
netmask: 255.255.252.0
gateway: 10.0.0.1
installDisk: /dev/sda
cluster:
name: hellas
controlPlaneIp: 10.0.2.1
secretsFile: !realpath _secrets.yaml
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
serial: 5CZ7NX2
interface: enp2s0
ip: 10.0.0.202
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: F3PKRH2
interface: enp3s0
ip: 10.0.0.201
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: J33CHY2
interface: enp2s0
ip: 10.0.0.203
type: "controlplane"
+8
View File
@@ -0,0 +1,8 @@
netmask: 255.255.255.0
gateway: 192.168.1.1
installDisk: /dev/vda
autoInstall: true
cluster:
name: testing
controlPlaneIp: 192.168.1.100
secretsFile: !realpath _secrets.yaml
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
serial: talos-vm
interface: enp1s0
ip: 192.168.1.2
type: "controlplane"
@@ -0,0 +1,2 @@
cluster:
allowSchedulingOnControlPlanes: true
+3
View File
@@ -0,0 +1,3 @@
machine:
network:
hostname: {{hostname}}
+3
View File
@@ -0,0 +1,3 @@
machine:
install:
disk: {{installDisk}}
+10
View File
@@ -0,0 +1,10 @@
machine:
network:
interfaces:
- interface: {{interface}}
dhcp: false
addresses:
- {{ip}}
routes:
- network: 0.0.0.0/0
gateway: {{gateway}}
+6
View File
@@ -0,0 +1,6 @@
machine:
network:
interfaces:
- interface: {{interface}}
vip:
ip: {{cluster.controlPlaneIp}}
+4
View File
@@ -0,0 +1,4 @@
PyYAML==6.0.3
requests==2.32.5
Jinja2==3.1.6
GitPython==3.1.45
+7
View File
@@ -0,0 +1,7 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
-52
View File
@@ -1,52 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
version:
kubernetes: 1.35.3
talos: 1.12.6
base:
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
patches:
all:
- system/hostname.yaml
- system/install-disk.yaml
- system/network.yaml.jinja
- system/ntp.yaml
- system/dns.yaml.jinja
- networking/vip.yaml
- networking/tailscale.yaml
- networking/cilium.yaml
- spegel.yaml
- storage/longhorn.yaml
- storage/longhorn/user-volume.yaml
- storage/local-path-provisioner/user-volume.yaml
- storage/limit-ephemeral.yaml
- metrics/all.yaml
controlPlane:
- system/allow-control-plane-workloads.yaml
- sops.yaml
- flux/cluster-variables.yaml
- metrics/control-plane.yaml
- networking/gateway-api.yaml
default:
arch: amd64
schematic: default.yaml
network:
dns:
- 1.1.1.1
- 8.8.8.8
tailscale:
server: https://headscale.huizinga.dev
ntp: nl.pool.ntp.org
install:
auto: true
-20
View File
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: staging
controlPlaneIp: 192.168.1.100
secretsFile: testing/secrets.yaml
nodes:
- testing/talos-vm
- testing/phobos
default:
network:
interface: ens5
netmask: 255.255.255.0
gateway: 192.168.1.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: testing/age.key
install:
disk: /dev/vda
-20
View File
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: production
controlPlaneIp: 10.0.2.1
secretsFile: titan/secrets.yaml
nodes:
- titan/hyperion
- titan/helios
- titan/selene
default:
network:
netmask: 255.255.252.0
gateway: 10.0.0.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: titan/age.key
install:
disk: /dev/sda
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: worker
network:
ip: 192.168.178.77
netmask: 255.255.255.0
gateway: 192.168.178.1
install:
disk: /dev/sda
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: talos-vm
network:
ip: 192.168.1.2
tailscale:
advertiseRoutes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: 5CZ7NX2
network:
interface: enp2s0
ip: 10.0.0.202
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: F3PKRH2
network:
interface: enp3s0
ip: 10.0.0.201
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: J33CHY2
network:
interface: enp2s0
ip: 10.0.0.203
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: cluster-variables
contents: |
---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-variables
namespace: flux-system
data:
cluster_env: {{ cluster.clusterEnv }}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraArgs:
rotate-server-certificates: "true"
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
-12
View File
@@ -1,12 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
features:
hostDNS:
# This option is enabled by default and causes issues with cilium
forwardKubeDNSToHost: false
cluster:
network:
cni:
name: none
proxy:
disabled: true
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: tailscale
environment:
- TS_AUTHKEY={{ node.network.tailscale.authKey }}
- TS_EXTRA_ARGS={% if node.network.tailscale.server %}--login-server {{ node.network.tailscale.server }}{% endif +%}
- TS_ROUTES={% if node.network.tailscale.advertiseRoutes %}{{node.network.ip}}/{{ node.network.netmask | to_prefix }}{% endif %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: Layer2VIPConfig
name: "{{ cluster.controlPlaneIp }}"
link: "{{ node.network.interface }}"
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: sops-key
contents: |
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: Secret
metadata:
name: sops-gpg
namespace: flux-system
stringData:
age.agekey: |
{{ node.sops | indent(6*2) }}
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
files:
- path: /etc/cri/conf.d/20-customization.part
op: create
content: |
[plugins."io.containerd.cri.v1.images"]
discard_unpacked_layers = false
@@ -1,6 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
maxSize: 30GB
@@ -1,9 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: local-path-provisioner
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 10GB
-11
View File
@@ -1,11 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraMounts:
- destination: /var/lib/longhorn
type: bind
source: /var/lib/longhorn
options:
- bind
- rshared
- rw
@@ -1,9 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: longhorn
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 2000GB
-17
View File
@@ -1,17 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
# This is only needed on nodes that will have storage
sysctls:
vm.nr_hugepages: "1024"
nodeLabels:
openebs.io/engine: mayastor
# This is needed on ALL nodes
kubelet:
extraMounts:
- destination: /var/local
type: bind
source: /var/local
options:
- bind
- rshared
- rw
@@ -1,3 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
allowSchedulingOnControlPlanes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ResolverConfig
nameservers:
{% for dns in node.network.dns %}
- address: {{ dns }}
{% endfor %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: HostnameConfig
hostname: "{{node.hostname}}"
auto: "off"
-4
View File
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
install:
disk: "{{node.install.disk}}"
-10
View File
@@ -1,10 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: LinkConfig
name: "{{node.network.interface}}"
up: true
mtu: 9000
addresses:
- address: "{{node.network.ip}}/{{ node.network.netmask | to_prefix }}"
routes:
- gateway: "{{node.network.gateway}}"
-5
View File
@@ -1,5 +0,0 @@
apiVersion: v1alpha1
kind: TimeSyncConfig
ntp:
servers:
- "{{ node.ntp }}"
-8
View File
@@ -1,8 +0,0 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
- siderolabs/tailscale
-9
View File
@@ -1,9 +0,0 @@
overlay:
name: rpi_generic
image: siderolabs/sbc-raspberrypi
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/tailscale
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -9
View File
@@ -1,4 +1,3 @@
{% set httpUrl = "http://192.168.1.1:8000" -%}
#!ipxe #!ipxe
dhcp dhcp
@@ -8,15 +7,18 @@ echo Starting ${serial}
goto node_${serial} || exit goto node_${serial} || exit
# Default behavior (non install mode) is to exit iPXE script # Default behavior (non install mode) is to exit iPXE script
{% for cluster in clusters%} {% for node in nodes %}
{% for node in cluster.nodes %} {%- if node.install -%}
{%- if node.install.serial -%} # {{ node.filename }}
# {{ cluster.name }}/{{ node.hostname }} :node_{{ node.serial }}
:node_{{ node.install.serial }} {% set ipArg = "ip=" ~ [node.ip, "" , node.gateway, node.netmask, node.hostname, node.interface, "", node.dns[0], node.dns[1], node.ntp]|join(":") -%}
{% set kernelArgs = [ipArg, node.kernelArgs|join(" "), node.extraKernelArgs|join(" ")] -%}
{% if node.autoInstall %}
{% do kernelArgs.append("talos.config=" ~ config.server.httpUrl ~ "/configs/" ~ node.filename ~ ".yaml") %}
{% endif %}
imgfree imgfree
kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/configs/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%} kernel https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/kernel-{{ node.arch }} {{ kernelArgs|join(" ") }}
initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz initrd https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/initramfs-{{ node.arch }}.xz
boot boot
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfor %}
+1 -1
View File
@@ -1,4 +1,4 @@
{% set tftpIp = "192.168.1.1" -%} {% set tftpIp = config.server.tftpIp -%}
enable-tftp enable-tftp
tftp-root=/tftproot tftp-root=/tftproot
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIGS={{ root }}/configs
# Generate the configuration for each node
{% for node in nodes -%}
talosctl gen config {{ node.cluster.name }} https://{{ node.cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ node.cluster.secretsFile }} \
--talos-version {{ node.talosVersion }} \
--kubernetes-version {{ node.kubernesVersion }} \
--output-types {{ node.type }} \
--install-image factory.talos.dev/metal-installer/{{ node.schematicId }}:{{ node.talosVersion }} \
{% for patch in node.patches -%}
{# The double call to tojson is needed to properly escape the patch (object -> json -> string) -#}
--config-patch {{ patch|tojson|tojson }} \
{% endfor -%}
{% for patch in node.patchesControlPlane -%}
--config-patch-control-plane {{ patch|tojson|tojson }} \
{% endfor -%}
--with-docs=false \
--with-examples=false \
-o ${CONFIGS}/{{ node.filename }}.yaml
{% endfor %}
# Generate the talosconfig file for each cluster
{% for cluster in clusters -%}
talosctl gen config {{ cluster.name }} https://{{ cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ cluster.secretsFile }} \
--output-types talosconfig \
-o ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
# Create merged talosconfig
TALOSCONFIG=${CONFIGS}/talosconfig
rm -f ${TALOSCONFIG}
{% for cluster in clusters -%}
talosctl config --talosconfig=${CONFIGS}/{{ cluster.name }}/talosconfig endpoint {{ cluster.controlPlaneIp }}
talosctl config --talosconfig=${TALOSCONFIG} merge ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
+5 -1
View File
@@ -1,2 +1,6 @@
export TALOSCONFIG={{ root }}/configs/talosconfig export TALOSCONFIG={{ root }}/configs/talosconfig
export KUBECONFIG={{ clusters|map(attribute='name')|kubeconfig|join(":") }} {% set paths = [] %}
{%- for cluster in clusters -%}
{%- do paths.append(root ~ "/configs/" ~ cluster.name ~ "/kubeconfig") -%}
{% endfor -%}
export KUBECONFIG={{ paths|join(":") }}
Executable
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
# Adapted from: https://enix.io/en/blog/pxe-talos/
import functools
import json
import pathlib
import sys
import git
import requests
import yaml
from jinja2 import Environment, FileSystemLoader, StrictUndefined, Template
REPO = git.Repo(sys.path[0], search_parent_directories=True)
assert REPO.working_dir is not None
ROOT = pathlib.Path(REPO.working_dir)
NODES = ROOT.joinpath("nodes")
SCHEMATICS = ROOT.joinpath("schematics")
RENDERED = ROOT.joinpath("rendered")
EXTENSIONS = ["jinja2.ext.do"]
PATCHES = Environment(
loader=FileSystemLoader(ROOT.joinpath("patches")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
TEMPLATES = Environment(
loader=FileSystemLoader(ROOT.joinpath("templates")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
def render_templates(node: dict):
class Inner(json.JSONEncoder):
def default(self, o):
if isinstance(o, Template):
try:
rendered = o.render(node)
except Exception as e:
e.add_note(f"While rendering for: {node['hostname']}")
raise e
# Parse the rendered yaml
return yaml.safe_load(rendered)
return super().default(o)
return Inner
@functools.cache
def get_schematic_id(schematic: str):
"""Lookup the schematic id associated with a given schematic"""
r = requests.post("https://factory.talos.dev/schematics", data=schematic)
r.raise_for_status()
data = r.json()
return data["id"]
def schematic_constructor(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
"""Load specified schematic file and get the assocatied schematic id"""
schematic_name = loader.construct_yaml_str(node)
try:
schematic = SCHEMATICS.joinpath(schematic_name).with_suffix(".yaml").read_text()
return get_schematic_id(schematic)
except Exception:
raise yaml.MarkedYAMLError("Failed to load schematic", node.start_mark)
def template_constructor(environment: Environment):
def inner(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
patch_name = loader.construct_scalar(node)
try:
template = environment.get_template(f"{patch_name}.yaml")
return template
except Exception:
raise yaml.MarkedYAMLError("Failed to load patch", node.start_mark)
return inner
def realpath_constructor(directory: pathlib.Path):
def inner(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
try:
realpath = directory.joinpath(loader.construct_scalar(node)).resolve(
strict=True
)
return str(realpath)
except Exception:
raise yaml.MarkedYAMLError("Failed to get real path", node.start_mark)
return inner
def get_loader(directory: pathlib.Path):
"""Add special constructors to yaml loader"""
loader = yaml.SafeLoader
loader.add_constructor("!realpath", realpath_constructor(directory))
loader.add_constructor("!schematic", schematic_constructor)
loader.add_constructor("!patch", template_constructor(PATCHES))
return loader
@functools.cache
def get_defaults(directory: pathlib.Path, root: pathlib.Path):
"""Compute the defaults from the provided directory and parents."""
try:
with open(directory.joinpath("_defaults.yaml")) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader(directory))
except OSError:
yml_data = {}
# Stop recursion when reaching root directory
if directory != root:
return get_defaults(directory.parent, root) | yml_data
else:
return yml_data
def walk_files(root: pathlib.Path):
"""Get all files that do not start with and underscore"""
for dirpath, _dirnames, filenames in root.walk():
for fn in filenames:
if not fn.startswith("_"):
yield dirpath.joinpath(fn)
def main():
nodes = []
for fullname in walk_files(NODES):
filename = str(fullname.relative_to(NODES).parent) + "/" + fullname.stem
with open(fullname) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader(fullname.parent))
yml_data = get_defaults(fullname.parent, NODES) | yml_data
yml_data["hostname"] = fullname.stem
yml_data["filename"] = filename
nodes.append(yml_data)
# Quick and dirty way to resolve all the templates using a custom encoder
nodes = list(
map(
lambda node: json.loads(json.dumps(node, cls=render_templates(node))), nodes
)
)
# Get all clusters
# NOTE: This assumes that all nodes in the cluster use the same definition for the cluster
clusters = [
dict(s) for s in set(frozenset(node["cluster"].items()) for node in nodes)
]
with open(ROOT.joinpath("config.yaml")) as fyaml:
config = yaml.safe_load(fyaml)
RENDERED.mkdir(exist_ok=True)
for template_name in TEMPLATES.list_templates():
template = TEMPLATES.get_template(template_name)
rendered = template.render(
nodes=nodes, clusters=clusters, config=config, root=ROOT
)
with open(RENDERED.joinpath(template_name), "w") as f:
f.write(rendered)
if __name__ == "__main__":
main()
+7 -3
View File
@@ -6,7 +6,7 @@ IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi) IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi)
IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION} IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION}
HTTP_URL="http://192.168.1.1:8000" HTTP_URL=$(cat ${ROOT}/config.yaml | yq .server.httpUrl)
function download_ipxe() { function download_ipxe() {
base_dir=$(dirname ${IPXE_DIR}) base_dir=$(dirname ${IPXE_DIR})
@@ -26,7 +26,7 @@ function patch_ipxe() {
#!ipxe #!ipxe
dhcp dhcp
chain ${HTTP_URL}/boot.ipxe || shell chain http://10.0.0.3:8000/boot.ipxe || shell
# chain boot.ipxe || shell # chain boot.ipxe || shell
EOF EOF
@@ -44,6 +44,10 @@ function build_ipxe() {
cd - > /dev/null cd - > /dev/null
} }
function render() {
${ROOT}/tools/render
${ROOT}/rendered/generate_configs.sh
}
function host_tftp() { function host_tftp() {
TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX) TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX)
@@ -88,5 +92,5 @@ function host_http() {
download_ipxe download_ipxe
patch_ipxe patch_ipxe
build_ipxe build_ipxe
crete generate render
host_http host_http
Executable
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
VM_NAME="talos-vm"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=talos
CONNECTION="qemu:///system"
function define_network() {
config_file=$(mktemp)
cat > ${config_file} << EOF
<network>
<name>${NETWORK}</name>
<bridge name="talos0" stp="on" delay="0"/>
<forward mode='nat'>
<nat/>
</forward>
<ip address="192.168.1.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.1.2" end="192.168.1.254"/>
<bootp file='http://192.168.1.1:8000/ipxe.pxe'/>
</dhcp>
</ip>
</network>
EOF
function cleanup() {
rm ${config_file}
}
trap cleanup EXIT
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${config_file}"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
trap - EXIT
cleanup
}
function create() {
define_network
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") == "0" ]]; then
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--sysinfo system.serial=${VM_NAME} \
--network network="${NETWORK}"
else
echo -n "VM already exists, start it with:
${0} start
"
exit -1
fi
}
function start() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo -n "VM doest not exists yet, create it with:
${0} create
"
exit -1
fi
}
function connect() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function stop() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}
WAIT=240
for i in $(seq 0 1 ${WAIT}); do
echo -en "\rWaiting for VM to shutdown... (${i}/${WAIT})"
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") == "0" ]]; then
echo -e "\nVM successfully shutdown"
exit
fi
sleep 1
done
echo -e "\nDestroying VM"
virsh --connect="${CONNECTION}" destroy ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function delete() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
fi
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
fi
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
fi
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"
fi
}
function help() {
echo -n "Available commands:
start
stop
remove
connect
"
}
COMMAND=${1:-}
case ${COMMAND} in
create)
create Create the vm and perform first install
;;
start)
start Start the vm
;;
stop)
stop Stop the vm
;;
delete)
delete Delete the vm
;;
connect)
connect Connect to an already running vm
;;
*)
help
;;
esac