Compare commits

...

65 Commits

Author SHA1 Message Date
fa1a621d03 --wip-- [skip ci] 2026-03-21 02:08:55 +01:00
08c1d0c605 feat: Added config validation 2026-02-20 05:33:50 +01:00
7b29763230 feat: Add yaml schema pre-commit check to patches 2026-02-20 05:21:59 +01:00
95de53206e feat: Add schema comments to patches
These comments ensure that we get proper yaml language server support
in the patch files.
Also fixes all the resulting language server errors.
2026-02-20 05:19:59 +01:00
940b01a7dc feat: Remove v version prefix in node config
This allows for better checking if the provided version string adheres
to semver.
2026-02-20 04:56:15 +01:00
b6c201775a feat: Added gateway api 2025-12-17 02:29:08 +01:00
be9dc8438b feat: Enable metrics server 2025-12-16 23:47:36 +01:00
873e73c310 feat: Added volume for local-path-provisioner 2025-12-09 02:38:51 +01:00
5c8cda5cc4 feat: Switched to longhorn 2025-12-09 02:21:43 +01:00
ac0d5244d3 feat: Added openebs patch 2025-12-09 02:21:42 +01:00
92345e5f1e chore: Added pre-commit hooks 2025-12-09 02:21:42 +01:00
47b85437e3 fix: Increase available resources 2025-12-09 02:21:37 +01:00
9c3c4005ed Configure talos for spegel 2025-12-02 02:45:26 +01:00
5eeba518a9 Added cluster variables for flux substitutions 2025-12-02 02:18:46 +01:00
f5798dae4c Added sops keys 2025-12-01 02:19:52 +01:00
1da24905ef Added helper function to load file content as base64 2025-12-01 01:59:08 +01:00
b0a1d04d7d Make route advertising configurable 2025-12-01 01:58:48 +01:00
7d5b09c623 Automatically add tailscale tag for cluster 2025-12-01 01:58:26 +01:00
e4f6c46fc3 Renamed production cluster to titan 2025-11-22 00:39:40 +01:00
b24feec37a Prepare cluster for cilium deployment 2025-11-22 00:16:31 +01:00
b57381afcb Added instruction for upgrading kubernetes 2025-11-22 00:16:14 +01:00
3200aaebaa Deepmerge node configs 2025-11-12 04:20:21 +01:00
f4d08c3516 Switched yaml to 2 space indent 2025-11-12 03:20:35 +01:00
b69ce72e79 Use uv for python 2025-11-12 03:18:43 +01:00
3b0a49f12e Added tailscale
Also routes the whole subnet of the cluster over tailscale so it can act
as an entry point to my home network even when not at home.
2025-11-11 04:18:18 +01:00
a75a0c8722 Access node parameters through node instead of directly 2025-11-11 04:17:47 +01:00
2dda3cc465 Also load config settings from secrets.yaml 2025-11-11 04:17:45 +01:00
c121533161 Pass config to node renderer 2025-11-11 03:41:59 +01:00
14e88a6734 Use ip in vm range 2025-11-11 02:27:39 +01:00
c406514cb1 Added option to auto install talos 2025-11-11 02:27:39 +01:00
3328fb053c PXE boot over http 2025-11-11 02:27:39 +01:00
cc421f69de Set correct endpoins in talosconfig 2025-11-09 05:10:26 +01:00
db43cf50fd Improve how talosconfig is set during config generation 2025-11-09 05:10:12 +01:00
73c8797dfc Fix vm ethernet interface name 2025-11-09 04:31:39 +01:00
b6633591bb Try to delete network even if vm does not exist 2025-11-09 04:15:19 +01:00
d9def74cf3 Made secrets file configurable 2025-11-09 04:15:19 +01:00
1931ab71ca Added yaml constructor that get the realpath of a file 2025-11-09 04:15:19 +01:00
498b0ba480 Improved how the cluster is defined 2025-11-09 04:15:18 +01:00
244c982b17 Fixed wornding of control plane 2025-11-09 04:15:18 +01:00
d3f3b8b972 Removed unneeded --- from patches 2025-11-09 04:15:18 +01:00
4b72bdb60d Added source script to set environment variables 2025-11-09 04:15:18 +01:00
ea3d1bf0fa Made repo root available for templates
This allows for embedding the repo root inside of, for example, scripts
to make them function properly no matter where they are run from.
2025-11-09 04:15:18 +01:00
81e861ef14 Find root of repo that contains the actual script
This makes it possible to run the render script from anywhere and have
it still function correctly.
2025-11-09 04:15:18 +01:00
453d952b8f Moved logic for getting clusters to render script 2025-11-09 04:15:18 +01:00
eb36060ef5 Made yaml template loader more generic 2025-11-09 04:15:18 +01:00
0f4fb2c5df Store template resolved nodes back in nodes object 2025-11-09 04:15:18 +01:00
83848a3624 Added template for config generation script 2025-11-09 04:15:18 +01:00
a477e5c4c2 Store patches as objects instead of strings 2025-11-09 04:15:17 +01:00
a8c51ce84b Added node types 2025-11-09 04:15:17 +01:00
969725ecff Added kubernetes version 2025-11-09 04:15:17 +01:00
7460bb19db Added jinja2 do extensions 2025-11-09 04:15:17 +01:00
69e7a46a3c Use consistent capitalization 2025-11-09 04:15:17 +01:00
e4644d1161 Moved around node config params 2025-11-09 04:15:17 +01:00
121f28e987 Make python script runnable from anywhere 2025-11-09 04:15:17 +01:00
d30b080098 Render all template using python and jinja 2025-11-09 04:15:17 +01:00
3290a76193 Turn talos patches into templates and resolve them 2025-11-08 05:47:24 +01:00
0642fde397 Prettyprint JSON 2025-11-08 04:28:45 +01:00
d163e0b9bc Improved how schematics are loaded 2025-11-08 04:26:32 +01:00
1b4eb34ec4 Add 1 git-crypt collaborator
New collaborators:

    CD17A34CBFB21DE9A73D47EB76BDEC4E165D8AD9
        Tim Huizinga <tim@huizinga.dev>
2025-11-08 04:15:16 +01:00
ecc0060fba Added README with some basic commands 2025-11-08 04:15:16 +01:00
ba3db01cb4 Added some basic talos config patches and secrets 2025-11-08 04:15:16 +01:00
21f98dd5e3 Add intel gpu microcode 2025-11-08 04:15:15 +01:00
49b310eff5 Renamed clusters 2025-11-08 04:15:15 +01:00
2963735810 Use arrays for kernel args and dns 2025-11-08 04:15:15 +01:00
8c53b59671 Cleanup and improvements 2025-11-08 04:15:15 +01:00
65 changed files with 733 additions and 290 deletions

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
root = true
[*]
indent_style = tab
[*.yaml]
indent_style = space
indent_size = 2

4
.git-crypt/.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
# Do not edit this file. To specify the files to encrypt, create your own
# .gitattributes file in the directory where your files are.
* !filter !diff
*.gpg binary

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.key filter=git-crypt diff=git-crypt
secrets.yaml filter=git-crypt diff=git-crypt

5
.gitignore vendored
View File

@@ -1,3 +1,4 @@
ipxe/ .ipxe/
rendered/ rendered/
tftp/ configs/
*.egg-info

48
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,48 @@
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/jmlrt/check-yamlschema
rev: v0.0.7
hooks:
- id: check-yamlschema
files: ^patches/.*\.yaml$
- 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

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"bracketSpacing": false
}

2
.secretsignore Normal file
View File

@@ -0,0 +1,2 @@
secrets.yaml
*.key

View File

@@ -1,40 +0,0 @@
FROM docker.io/library/debian:stable AS builder-ipxe
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
liblzma-dev \
genisoimage
ARG IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
RUN curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz
WORKDIR /ipxe-${IPXE_VERSION}/src
# Enable HTTPS
RUN sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
RUN mkdir /build
RUN make -j$(nproc) bin/ipxe.pxe && cp bin/ipxe.pxe /build
RUN make -j$(nproc) bin-x86_64-efi/ipxe.efi && cp bin-x86_64-efi/ipxe.efi /build
FROM docker.io/library/python:3.13-slim AS config-renderer
COPY --from=docker.io/hairyhenderson/gomplate:v4.3 /gomplate /bin/gomplate
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
COPY ./generate.sh /generate.sh
COPY ./tools /tools
COPY ./nodes /nodes
COPY ./templates /templates
RUN ./generate.sh
FROM docker.io/library/alpine:3.22.2 AS runtime
RUN apk add dnsmasq
COPY --from=builder-ipxe /build/ipxe.pxe /tftproot/
COPY --from=builder-ipxe /build/ipxe.efi /tftproot/
COPY --from=config-renderer /rendered/boot.ipxe /tftproot/
COPY --from=config-renderer /rendered/dnsmasq.conf /dnsmasq.conf
EXPOSE 67/udp
EXPOSE 69/udp
CMD ["dnsmasq", "--conf-file=/dnsmasq.conf", "--keep-in-foreground", "--user=root", "--log-facility=-", "--port=0"]

73
README.md Normal file
View File

@@ -0,0 +1,73 @@
# Talos
To decrypt the secrets file:
```
git-crypt unlock
```
Generate the config files:
```bash
talosctl gen config <cluster_name> https://<controlplane_ip>:6443 -f \
--with-secrets secrets.yaml \
--config-patch @<path_to_patch> \
--config-patch-control-plane @<path_to_controlplane_patch> \
--install-image factory.talos.dev/metal-installer/<schematic_id>:<version> \
-o configs
```
Set TALOSCONFIG:
```bash
export TALOSCONFIG=$(realpath configs/talosconfig)
```
Apply the configs for each node, use worker.yaml for worker nodes:
```bash
talosctl apply-config --insecure --nodes <node_id> --file configs/controlplane.yaml
```
Set endpoint to one of the nodes:
```bash
talosctl config endpoint <node_ip>
```
Bootstrap Kubernetes:
```bash
talosctl -n <node_id> bootstrap
```
Set endpoint to control plane:
```bash
talosctl config endpoint <controlplane_ip>
```
Get kubeconfig and set KUBECONFIG:
```bash
talosctl -n 192.168.1.100 kubeconfig $PWD/configs/kubeconfig
export KUBECONFIG=$(realpath configs/kubeconfig)
```
For applying updated config to node:
```bash
talosctl apply-config --nodes <node_id> --file configs/controlplane.yaml
```
Upgrading talos or changing the schematic:
```bash
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
```

2
committed.toml Normal file
View File

@@ -0,0 +1,2 @@
style = "conventional"
ignore_author_re = "Flux"

View File

@@ -1 +0,0 @@
tftpIp: 10.0.0.3

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euxo pipefail
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
${SCRIPT_DIR}/tools/merge.py ./nodes | gomplate -d nodes=stdin://nodes.json -d dhcp=${SCRIPT_DIR}/dhcp.yaml --input-dir ${SCRIPT_DIR}/templates --output-dir ${SCRIPT_DIR}/rendered

View File

@@ -1,9 +0,0 @@
schematicID: !schematic "_schematic.yaml"
arch: amd64
talosVersion: v1.11.3
kernelArgs: talos.platform=metal console=tty0 init_on_alloc=1 slab_nomerge pti=on consoleblank=0 nvme_core.io_timeout=4294967295 printk.devkmsg=on selinux=1 lockdown=confidentiality
dns0: 1.1.1.1
dns1: 8.8.8.8
ntp: nl.pool.ntp.org
install: false
upgradeIPXE: false

View File

@@ -1,3 +0,0 @@
netmask: 255.255.252.0
gateway: 10.0.0.1
install: true

View File

@@ -1,3 +0,0 @@
serial: 5CZ7NX2
interface: enp2s0
ip: 10.0.0.202

View File

@@ -1,3 +0,0 @@
serial: F3PKRH2
interface: enp3s0
ip: 10.0.0.201

View File

@@ -1,3 +0,0 @@
serial: J33CHY2
interface: enp2s0
ip: 10.0.0.203

View File

@@ -1,3 +0,0 @@
netmask: 255.255.255.0
gateway: 192.168.1.1
upgradeIPXE: ipxe.pxe

View File

@@ -1,4 +0,0 @@
serial: vm
interface: enp1s0
ip: 192.168.1.2
install: true

View File

@@ -1,2 +0,0 @@
PyYAML==6.0.3
requests==2.32.5

View File

@@ -0,0 +1,53 @@
# yaml-language-server: $schema=../../schemas/cluster.json
version:
kubernetes: 1.34.1
talos: 1.11.3
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
- 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
authKey:
file: tailscale.key
advertiseRoutes: true
ntp: nl.pool.ntp.org
install:
auto: true

View File

@@ -0,0 +1,16 @@
# yaml-language-server: $schema=../../schemas/cluster.json
clusterEnv: staging
controlPlaneIp: 192.168.1.100
secretsFile: testing/secrets.yaml
nodes:
- testing/talos-vm
default:
network:
interface: enp1s0
netmask: 255.255.255.0
gateway: 192.168.1.1
sops:
file: testing/age.key
install:
disk: /dev/vda

17
talos/clusters/titan.yaml Normal file
View File

@@ -0,0 +1,17 @@
# yaml-language-server: $schema=../../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
sops:
file: titan/age.key
install:
disk: /dev/sda

View File

@@ -0,0 +1,6 @@
# yaml-language-server: $schema=../../../schemas/node.json
type: controlPlane
install:
serial: talos-vm
network:
ip: 192.168.1.2

View File

@@ -0,0 +1,7 @@
# yaml-language-server: $schema=../../../schemas/node.json
type: controlPlane
install:
serial: 5CZ7NX2
network:
interface: enp2s0
ip: 10.0.0.202

View File

@@ -0,0 +1,7 @@
# yaml-language-server: $schema=../../../schemas/node.json
type: controlPlane
install:
serial: F3PKRH2
network:
interface: enp3s0
ip: 10.0.0.201

View File

@@ -0,0 +1,7 @@
# yaml-language-server: $schema=../../../schemas/node.json
type: controlPlane
install:
serial: J33CHY2
network:
interface: enp2s0
ip: 10.0.0.203

View File

@@ -0,0 +1,18 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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 }}

View File

@@ -0,0 +1,5 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
kubelet:
extraArgs:
rotate-server-certificates: "true"

View File

@@ -0,0 +1,5 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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

View File

@@ -0,0 +1,12 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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

View File

@@ -0,0 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
cluster:
extraManifests:
- https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml

View File

@@ -0,0 +1,8 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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 %}--advertise-tags=tag:cluster-{{ cluster.name }}
- TS_ROUTES={% if node.network.tailscale.advertiseRoutes %}{{node.network.ip}}/{{ node.network.netmask | to_prefix }}{% endif %}

View File

@@ -0,0 +1,7 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
network:
interfaces:
- interface: "{{node.network.interface}}"
vip:
ip: "{{cluster.controlPlaneIp}}"

18
talos/patches/sops.yaml Normal file
View File

@@ -0,0 +1,18 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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
data:
age.agekey: |
{{ node.sops | indent(6*2) }}

View File

@@ -0,0 +1,8 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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

View File

@@ -0,0 +1,6 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
maxSize: 30GB

View File

@@ -0,0 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: local-path-provisioner
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 10GB

View File

@@ -0,0 +1,11 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
kubelet:
extraMounts:
- destination: /var/lib/longhorn
type: bind
source: /var/lib/longhorn
options:
- bind
- rshared
- rw

View File

@@ -0,0 +1,9 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: longhorn
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 2000GB

View File

@@ -0,0 +1,17 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/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

View File

@@ -0,0 +1,3 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
cluster:
allowSchedulingOnControlPlanes: true

View File

@@ -0,0 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
network:
hostname: "{{node.hostname}}"

View File

@@ -0,0 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
install:
disk: "{{node.install.disk}}"

View File

@@ -0,0 +1,11 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.11/website/content/v1.11/schemas/config.schema.json
machine:
network:
interfaces:
- interface: "{{node.network.interface}}"
dhcp: false
addresses:
- "{{node.network.ip}}"
routes:
- network: 0.0.0.0/0
gateway: "{{node.network.gateway}}"

View File

@@ -3,3 +3,6 @@ customization:
officialExtensions: officialExtensions:
- siderolabs/iscsi-tools - siderolabs/iscsi-tools
- siderolabs/util-linux-tools - siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
- siderolabs/tailscale

BIN
talos/secrets/tailscale.key Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
talos/secrets/titan/age.key Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,36 +1,23 @@
{% set httpUrl = "http://192.168.1.1:8000" -%}
#!ipxe #!ipxe
dhcp dhcp
echo Starting ${serial}
:start :start
# Is a known serial is set, execute that goto node_${serial} || exit
# If an unknown serial is set, exit
# If no serial is set, ask the user
goto node_${serial} || goto manual
# Default behavior (non install mode) is to exit iPXE script # Default behavior (non install mode) is to exit iPXE script
{{ range (datasource "nodes" | jsonArray) }} {% for cluster in clusters%}
{{- if .install }} {% for node in cluster.nodes %}
# {{ .filename }} {%- if node.install.serial -%}
:node_{{ .serial }} # {{ cluster.name }}/{{ node.hostname }}
{{- $ipArg := printf "ip=%s::%s:%s:%s:%s::%s:%s:%s" .ip .gateway .netmask .hostname .interface .dns0 .dns1 .ntp }} :node_{{ node.install.serial }}
{{- $kernelArgs := printf "%s %s" $ipArg .kernelArgs }} {% set ipArg = "ip=" ~ [node.network.ip, "" , node.network.gateway, node.network.netmask, node.hostname, node.network.interface, "", node.network.dns[0], node.network.dns[1], node.ntp]|join(":") -%}
imgfree imgfree
kernel https://pxe.factory.talos.dev/image/{{ .schematicID }}/{{ .talosVersion }}/kernel-{{ .arch }} {{ $kernelArgs }} {{- if .upgradeIPXE }} || boot {{ .upgradeIPXE }} {{- end }} kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ ipArg }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/configs/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%}
initrd https://pxe.factory.talos.dev/image/{{ .schematicID }}/{{ .talosVersion }}/initramfs-{{ .arch }}.xz initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz
boot boot
{{- end }} {% endif %}
{{ end }} {% endfor %}
{% endfor %}
:manual
menu Select node
{{ range (datasource "nodes" | jsonArray) }}
item {{ .serial }} {{ .hostname }}
{{ end }}
choose selected || goto cancel
goto node_${selected}
:cancel
echo Type exit to restart script
shell
goto start

View File

@@ -1,4 +1,4 @@
{{ $tftpIp := (ds "dhcp").tftpIp -}} {% set tftpIp = "192.168.1.1" -%}
enable-tftp enable-tftp
tftp-root=/tftproot tftp-root=/tftproot
@@ -9,9 +9,9 @@ dhcp-vendorclass=UEFI,PXEClient:Arch:00007
dhcp-vendorclass=UEFI64,PXEClient:Arch:00009 dhcp-vendorclass=UEFI64,PXEClient:Arch:00009
# 1st stage: pxe rom boot on ipxe # 1st stage: pxe rom boot on ipxe
dhcp-boot=net:BIOS,ipxe.pxe,{{ $tftpIp }},{{ $tftpIp }} dhcp-boot=net:BIOS,ipxe.pxe,{{ tftpIp }},{{ tftpIp }}
dhcp-boot=net:UEFI,ipxe.efi,{{ $tftpIp }},{{ $tftpIp }} dhcp-boot=net:UEFI,ipxe.efi,{{ tftpIp }},{{ tftpIp }}
dhcp-boot=net:UEFI64,ipxe.efi,{{ $tftpIp }},{{ $tftpIp }} dhcp-boot=net:UEFI64,ipxe.efi,{{ tftpIp }},{{ tftpIp }}
# Based on logic in https://gist.github.com/robinsmidsrod/4008017 # Based on logic in https://gist.github.com/robinsmidsrod/4008017
# iPXE sends a 175 option, checking suboptions # iPXE sends a 175 option, checking suboptions
@@ -30,11 +30,11 @@ tag-if=set:ipxe-ok,tag:ipxe-http,tag:ipxe-https
# these create option 43 cruft, which is required in proxy mode # these create option 43 cruft, which is required in proxy mode
# TFTP IP is required on all dhcp-boot lines (unless dnsmasq itself acts as tftp server?) # TFTP IP is required on all dhcp-boot lines (unless dnsmasq itself acts as tftp server?)
pxe-service=tag:!ipxe-ok,X86PC,PXE,undionly.kpxe,{{ $tftpIp }} pxe-service=tag:!ipxe-ok,X86PC,PXE,undionly.kpxe,{{ tftpIp }}
pxe-service=tag:!ipxe-ok,IA32_EFI,PXE,snponlyx32.efi,{{ $tftpIp }} pxe-service=tag:!ipxe-ok,IA32_EFI,PXE,snponlyx32.efi,{{ tftpIp }}
pxe-service=tag:!ipxe-ok,BC_EFI,PXE,snponly.efi,{{ $tftpIp }} pxe-service=tag:!ipxe-ok,BC_EFI,PXE,snponly.efi,{{ tftpIp }}
pxe-service=tag:!ipxe-ok,X86-64_EFI,PXE,snponly.efi,{{ $tftpIp }} pxe-service=tag:!ipxe-ok,X86-64_EFI,PXE,snponly.efi,{{ tftpIp }}
# later match overrides previous, keep ipxe script last # later match overrides previous, keep ipxe script last
# server address must be non zero, but can be anything as long as iPXE script is not fetched over TFTP # server address must be non zero, but can be anything as long as iPXE script is not fetched over TFTP
dhcp-boot=tag:ipxe-ok,boot.ipxe,,{{ $tftpIp }} dhcp-boot=tag:ipxe-ok,boot.ipxe,,{{ tftpIp }}

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIGS={{ root }}/configs
TALOSCONFIG=${CONFIGS}/talosconfig
rm -f ${CONFIGS}
# Generate the configuration for each node
{% for cluster in clusters %}
{% for node in cluster.nodes -%}
talosctl gen config {{ cluster.name }} https://{{ cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ cluster.secretsFile }} \
--talos-version v{{ cluster.version.talos }} \
--kubernetes-version v{{ cluster.version.kubernetes }} \
--output-types {{ node.type }} \
--install-image factory.talos.dev/metal-installer/{{ node.schematic }}:v{{ cluster.version.talos }} \
{% for patch in node.patches.all -%}
{# 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.patches.controlPlane -%}
--config-patch-control-plane {{ patch|tojson|tojson }} \
{% endfor -%}
--with-docs=false \
--with-examples=false \
-o ${CONFIGS}/{{ cluster.name }}/{{ node.hostname }}.yaml
{% endfor %}
# Generate the talosconfig file for each cluster
talosctl gen config {{ cluster.name }} https://{{ cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ cluster.secretsFile }} \
--output-types talosconfig \
-o ${CONFIGS}/{{ cluster.name }}/talosconfig
# Create merged talosconfig
talosctl config --talosconfig=${CONFIGS}/{{ cluster.name }}/talosconfig endpoint {{ cluster.controlPlaneIp }}
talosctl config --talosconfig=${TALOSCONFIG} merge ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}

2
templates/source.sh Normal file
View File

@@ -0,0 +1,2 @@
export TALOSCONFIG={{ root }}/configs/talosconfig
export KUBECONFIG={{ clusters|map(attribute='name')|kubeconfig|join(":") }}

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env python3
# Adapted from: https://enix.io/en/blog/pxe-talos/
import argparse
import functools
import json
import pathlib
import requests
import yaml
@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(directory: pathlib.Path):
"""Load specified schematic file and get the assocatied schematic id"""
def constructor(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
filename = str(loader.construct_scalar(node))
try:
schematic = directory.joinpath(filename).read_text()
return get_schematic_id(schematic)
except Exception:
raise yaml.MarkedYAMLError("Failed to load schematic", node.start_mark)
return constructor
def get_loader(directory: pathlib.Path):
"""Add special constructors to yaml loader"""
loader = yaml.SafeLoader
loader.add_constructor("!schematic", schematic_constructor(directory))
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():
parser = argparse.ArgumentParser()
parser.add_argument("directory", type=pathlib.Path)
parser.add_argument("-f", "--filter")
args = parser.parse_args()
data = []
for fullname in walk_files(args.directory):
filename = (
str(fullname.relative_to(args.directory).parent) + "/" + fullname.stem
)
if args.filter is not None and not filename.startswith(args.filter):
continue
with open(fullname) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader(fullname.parent))
yml_data = get_defaults(fullname.parent, args.directory) | yml_data
yml_data["hostname"] = fullname.stem
yml_data["filename"] = filename
data.append(yml_data)
# Dump everything to json
print(json.dumps(data))
if __name__ == "__main__":
main()

96
tools/server Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi)
IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION}
HTTP_URL=$(cat ${ROOT}/config.yaml | yq .server.httpUrl)
function download_ipxe() {
base_dir=$(dirname ${IPXE_DIR})
# Download the iPXE source if needed
if [ ! -d "${IPXE_DIR}" ]; then
mkdir -p "${base_dir}"
curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz -C "${base_dir}"
fi
}
function patch_ipxe() {
# Apply patches to iPXE source
cd "${IPXE_DIR}/src"
sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
cat > embed.ipxe << EOF
#!ipxe
dhcp
chain ${HTTP_URL}/boot.ipxe || shell
# chain boot.ipxe || shell
EOF
cd - > /dev/null
}
function build_ipxe() {
cd "${IPXE_DIR}/src"
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
if [ ! -f "${path}" ]; then
make -j$(nproc) ${bin} EMBED=embed.ipxe
fi
done
cd - > /dev/null
}
function render() {
${ROOT}/tools/render
${ROOT}/rendered/generate_configs.sh
}
function host_tftp() {
TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX)
chmod 755 ${TFTP_DIR}
function cleanup() {
rm -rf ${TFTP_DIR}
}
trap cleanup EXIT
cp ${ROOT}/rendered/boot.ipxe ${TFTP_DIR}
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
cp ${path} ${TFTP_DIR}
done
echo "Starting tftpd"
sudo in.tftpd --verbosity 100 --permissive -L --secure ${TFTP_DIR}
}
function host_http() {
HTTP_DIR=$(mktemp --tmpdir -d http.XXX)
chmod 755 ${HTTP_DIR}
function cleanup() {
rm -rf ${HTTP_DIR}
}
trap cleanup EXIT
ln -s ${ROOT}/rendered/boot.ipxe ${HTTP_DIR}
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
ln -s ${path} ${HTTP_DIR}
done
ln -s ${ROOT}/configs ${HTTP_DIR}
echo "Starting http"
cd ${HTTP_DIR}
python -m http.server 8000
cd -
}
download_ipxe
patch_ipxe
build_ipxe
render
host_http

153
tools/vm Executable file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
VM_NAME="talos-vm"
VCPUS="6"
RAM_MB="16384"
DISK_GB="100"
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

View File

@@ -1,13 +0,0 @@
<network xmlns:dnsmasq='http://libvirt.org/schemas/network/dnsmasq/1.0'>
<name>cluster-vm</name>
<bridge name="cluster0" 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='boot.ipxe'/>
</dhcp>
</ip>
</network>

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${SCRIPT_DIR}/${NETWORK}.xml"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--network network="${NETWORK}"

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"

View File

@@ -1,10 +0,0 @@
set -euxo pipefail
VM_NAME="test"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=cluster-vm
CONNECTION="qemu:///system"
IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=bin/ipxe.pxe

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
TFTP_DIR=${SCRIPT_DIR}/../tftp
rm -rf "${TFTP_DIR}"
mkdir -p "${TFTP_DIR}"
IPXE_DIR=${SCRIPT_DIR}/../ipxe
IPXE_FILE=${IPXE_DIR}/ipxe-${IPXE_VERSION}/src/${IPXE_BIN}
if [ ! -f "${IPXE_FILE}" ]; then
mkdir -p "${IPXE_DIR}"
rm -rf "${IPXE_DIR}/ipxe-${IPXE_VERSION}"
curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz -C "${IPXE_DIR}"
cd "${IPXE_DIR}/ipxe-${IPXE_VERSION}/src"
sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
make -j$(nproc) ${IPXE_BIN}
cd -
fi
${SCRIPT_DIR}/../generate.sh
cp ${SCRIPT_DIR}/../rendered/boot.ipxe ${TFTP_DIR}
cp ${IPXE_FILE} ${TFTP_DIR}
sudo in.tftpd -L --secure ./tftp