diff --git a/Cargo.lock b/Cargo.lock index 4a14512..4fffec8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "1.0.0" @@ -224,6 +233,7 @@ dependencies = [ "netascii", "num_enum", "optional_struct", + "regex", "reqwest", "schemars", "semver", @@ -1387,10 +1397,33 @@ dependencies = [ ] [[package]] -name = "regex-automata" -version = "0.4.14" +name = "regex" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "repo_path" diff --git a/Cargo.toml b/Cargo.toml index 461c66f..d99e9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ minijinja = { version = "2.18.0", features = ["json", "loader"] } netascii = "0.1.0" num_enum = "0.7.6" optional_struct = "0.5.2" +regex = "1.13.1" reqwest = { version = "0.13.2", default-features = false, features = [ "blocking", "stream", diff --git a/bin/ipxe.pxe b/bin/ipxe.pxe new file mode 100644 index 0000000..d7f10f6 Binary files /dev/null and b/bin/ipxe.pxe differ diff --git a/helios.yaml b/helios.yaml new file mode 100644 index 0000000..ce190ff --- /dev/null +++ b/helios.yaml @@ -0,0 +1,210 @@ +version: v1alpha1 +debug: false +persist: true +machine: + type: controlplane + token: 6yf327.db3x2zfs5ptbxrst + ca: + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJQekNCOHFBREFnRUNBaEVBM0ZXN0JrK21pK3h5TFk4V29pNFNqVEFGQmdNclpYQXdFREVPTUF3R0ExVUUKQ2hNRmRHRnNiM013SGhjTk1qVXhNVEE1TURJek9USXdXaGNOTXpVeE1UQTNNREl6T1RJd1dqQVFNUTR3REFZRApWUVFLRXdWMFlXeHZjekFxTUFVR0F5dGxjQU1oQUJvSDVycnRxT3BaakRuaU5scXppOVRlT2c0OW4zMzBtcVp4CjVySXRYVEJJbzJFd1h6QU9CZ05WSFE4QkFmOEVCQU1DQW9Rd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3RUcKQ0NzR0FRVUZCd01DTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkkwcDJYT0pmTVE5SUV0eQpqZzlRYlk0VnJPbGZNQVVHQXl0bGNBTkJBQXkvYlFLYmxZSEtYb0QrZE1NWU1zZmY0QjkzWFJIR29ZWmpGM2lKCnp5R3lXdkh0SnYzdEY3aVJrOG9ibnpWZUR5dnlUOEM0QVAzZUkvZmlZQmFDaXcwPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM0Q0FRQXdCUVlESzJWd0JDSUVJTDg3ckFBbTgvUzlNSU1GQm9pZ2ZpUDg4Mmg3QlNUSGppUTFrSng2QmNEVAotLS0tLUVORCBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0K + certSANs: [] + kubelet: + image: ghcr.io/siderolabs/kubelet:v1.35.3 + extraArgs: + rotate-server-certificates: "true" + extraMounts: + - destination: /var/lib/longhorn + type: bind + source: /var/lib/longhorn + options: + - bind + - rshared + - rw + defaultRuntimeSeccompProfileEnabled: true + disableManifestsDirectory: true + network: {} + install: + disk: /dev/sda + image: factory.talos.dev/metal-installer/53edcacbd9153fcbce2ba04cc41e1c6867ef92b462b37344de8c420ceeb21816:v1.12.6 + wipe: false + grubUseUKICmdline: true + files: + - content: |- + [plugins."io.containerd.cri.v1.images"] + discard_unpacked_layers = false + permissions: 0o0 + path: /etc/cri/conf.d/20-customization.part + op: create + features: + diskQuotaSupport: true + kubePrism: + enabled: true + port: 7445 + hostDNS: + enabled: true + forwardKubeDNSToHost: false + nodeLabels: + node.kubernetes.io/exclude-from-external-load-balancers: "" +cluster: + id: CZ7M2eK-gs9qR50_MYzIL8653An9nHRGCR2R1rfIfSo= + secret: hnYuExJx1JeFfG6Ss3yYI1wuJRqokD9laXboLmI0A4Q= + controlPlane: + endpoint: https://10.0.2.1:6443 + clusterName: titan + network: + cni: + name: none + dnsDomain: cluster.local + podSubnets: + - 10.244.0.0/16 + serviceSubnets: + - 10.96.0.0/12 + token: 5607yr.7fbnk2hyaukxnkwx + secretboxEncryptionSecret: NSHASj2WMMmiZqluiHGKdiJuaxejaz6bmytD07ccp/A= + ca: + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpakNDQVRDZ0F3SUJBZ0lSQU4xVTVpY2FlY1B6QXRTeDFobjdCaU13Q2dZSUtvWkl6ajBFQXdJd0ZURVQKTUJFR0ExVUVDaE1LYTNWaVpYSnVaWFJsY3pBZUZ3MHlOVEV4TURrd01qTTVNakJhRncwek5URXhNRGN3TWpNNQpNakJhTUJVeEV6QVJCZ05WQkFvVENtdDFZbVZ5Ym1WMFpYTXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CCkJ3TkNBQVJPYmVEM3UzbWVoYk1NWE9xeHJKYzNFaGovZ1NrNENENm42cXhQcTBIVHdDbk1iY2l1cmNZUTk5VkkKM0ZPUU92ZCtGMGtKSFV1ZitkSDFlRU1GWno4eG8yRXdYekFPQmdOVkhROEJBZjhFQkFNQ0FvUXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWURWUjBPCkJCWUVGUExUWTYveUthQUFOUk1Ud0pmUDh2a2FEcXh5TUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFESjM4TloKcEZDMjJLK3pMRWlrQWp1YlNoMWNXcW5JZmU4c2VHbkZHckFWOXdJZ1cyLy9WS3BFSTNSaGtTaTkxWGVLNVduOAowRXdaVmlKRkZSWENVcEt6L0pFPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSURsQ3BaWThKYmx1Znl3cStWcmJGb25XRmlZYjJINDRXdG0xLzFSRWpubFNvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFVG0zZzk3dDVub1d6REZ6cXNheVhOeElZLzRFcE9BZytwK3FzVDZ0QjA4QXB6RzNJcnEzRwpFUGZWU054VGtEcjNmaGRKQ1IxTG4vblI5WGhEQldjL01RPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= + aggregatorCA: + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJYekNDQVFXZ0F3SUJBZ0lRVHBwakprbXRucktUM09yOEVsU2VPakFLQmdncWhrak9QUVFEQWpBQU1CNFgKRFRJMU1URXdPVEF5TXpreU1Gb1hEVE0xTVRFd056QXlNemt5TUZvd0FEQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxRwpTTTQ5QXdFSEEwSUFCS2cwOW4xcXExSkhBNTJmZXNkT1RVYTFHOUpHWUQ2WWF1aDdOSjdjbDl2dzQ4QUJORmJkCnA3YUdSQnNtNzdDZVNIemRiOWU4MmdoZHVkTUVRbVFnUCtXallUQmZNQTRHQTFVZER3RUIvd1FFQXdJQ2hEQWQKQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQVFZSUt3WUJCUVVIQXdJd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZApCZ05WSFE0RUZnUVVuNjhCcWJmUkZpaXpzTis0VUFKYkk0RUFaTXd3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloCkFOb3lXbFpXQkR5K0tONHRYdldmTDlYcXZGaGNabGxEYkhLNnlGQkE2Z0h1QWlBdUVFV1JCaXhrMU1VdVVrTXAKTkhOWHpSMXQ4TWVBNVBmY1BnakVramhjcmc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUgwZGkzc3hMZmxQZVZFNnplY1ppbk42QWM5SURaSjJvdHF1bWdJUTNnRnNvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFcURUMmZXcXJVa2NEblo5NngwNU5SclViMGtaZ1BwaHE2SHMwbnR5WDIvRGp3QUUwVnQybgp0b1pFR3lidnNKNUlmTjF2MTd6YUNGMjUwd1JDWkNBLzVRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= + serviceAccount: + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKSndJQkFBS0NBZ0VBcXlINkplaGQ0Qm9zQjJYK1dhRDV3N0tQa1RVc0dLQlQzclhjSVhYdGhvRkFyZFpGCmtyU1l5QVpwYTFrcFN1UndCaVRXbDExd2dkNzJvU2h6S1pHY1F3UC9VQ1F0eVhyMmNvVW1XMXJDRUJHRmduSnoKZWNsb3VKWENSUDJPOU11ZndqVHFoWjd5OXhoL0g0VkdCNitLcFBSeG9KQnR4KzFJWEE3a2hySzVFY0l0dmpOUAo2MTFyRXN5OFhEWmxGdHhCMVVzY0hoOXpTZUhNOVNPN2pXV2FvVUV5S3QraU9yRFBBNUk5TS9vZytISStKNE9wCk16SnVqejUxam9UaGRscVRBakRac0VxOGhqYnpGR3JoSmszQTROR1hoaGxLTzZHY1RwVVlRNmUxR0UyNTN3RFAKL0FlSnhmTzBQcFloS2pubXJPUk83SzdsdVE2THBpcGRVY3ZZbjd4NjNVQi9HUkVqenJIc21GS2tFMVV2T2FQZApLT1dhRGFlNGlaNmFCK1o0S0JFZUgyenRkeTR2S3BNOVFDZHdmRGRYT09qd0pnQmw0RDIrS1hyRmZMMGhCMnhmCmpoOFl4anFQeHB1aW0zdHNvbWx1eDRSZlVDaGJvbkgyaEFpd3FIUE12eWExZzk0dWdwM0VsSzVEQ2hoMkNCOFoKT0ovVE4wVnBLM0l0Nk5NVkt3NUZMQWJVa2M5NkhydVI2V0lER2JtaW1GQjJ0TGYwVGdWMmhWaTRENVNzTmF5SwpLZlNhYzllY29CZ1RlN2l4M1FJVEtTZ2F3cERBaWJDT0lPSi9RVTRHQUVmK2N2dVFiYklYU0RkY3VjYUNDZEYrCjV3MUo3YXNydUVINmttRUQvRTdFY3hLY21rTFBUQjBZL0MybXlMQzExWkhYQ0ZrdDM5Qjk1ZGJLSHBzQ0F3RUEKQVFLQ0FnQXZjVGx3ZGVWRWdlenYxaUkzeGMxSUtBSHVEeEFjK2FHV3JkTUxnYTVxS2YzODAxaXhUWmpIWnlPbAppMi9MbjkzQWpramhhSm1PbjNtKy9zQkNaMmdyR2ZvWFJSaGVjVlRPTmNTRzhmZTN6YXUyR0xXVHJ6YjFhU3NVCnhqaHgrOXRiMHlDckQ0QXE2V01nb1dsakt4S2tIVkk4NFoxVm5oMy95MjhtT25qZEIvaDNjTEU0dmtxM1NRUDEKQ20xbTg0UGF0anBpelhSQUtMV1J4aWl5RDlkWTJnS202UjJINk1sVEk2RU0va3hTcEJWQURodURvbmtOQkJOQwpBZXVoVElYUnh5b3FXZVcrelpBQStvcFZmSnU2bDlCS2NFdG9rYXFNRElCUURUZkN0NVdPVWFMWEsxM1IvM2hhCkczT3hTYUQrRUVNakVXK0ZLSmVsWnJ3OU4yWmptaFhVNFRWdXJENVNYTXFOS2l4NVFqdnljRFRNVmtlUjZIdFkKT01zU2dWYXhHK25oNTRpWlphYzhESzhCakFpRUI1dFhNT1VuUldWTUNRdWYwc0JwNUI1MGYyV25pS2N3eGRlMgpKbzJrNktuSlRUK2xUYmRXV1pRalR4bXhGSUh6YkFKVnMyMlVqOXlxRmtyckt3VEtZR1VRZmFjbTBxUGJxVkFVCjFMUFBmcll4dlRlWXExUyt6UzF4alpuOXFRMk8rMGFSOUJweElIaWJlbFNUc2Z2a2x6bFM2TnlpZGdOVlpnL20KNHVyRFJkVkkzN0J1dWZjUVJlanN2L1JoR3cwZ3RvSmJKcEc2M245WEZybHBxMUgxTDFiUjV0UlUxQk5INS9tbAo4bHBsRzBJNDJGUEhodGF2dzR5T1RnMnFWdU50OTdSMC9XUE9CQ3dEU3c5TktjUkg5UUtDQVFFQTJJZ2NuOXRyClJJQjlJMlRKaUJWRStkUmJLbkhnaXJGd1hRaWZyVmh0eHZiRDlEOU94VGI2b3doNVk5SjNIMmZFcUIrQzI0QngKWThueUtTZ25mZkNtTjdEbVBpNnNPZi9mMzRJMEtEYW5RWG9qeVAyb1NlUDhDbE9XdXpzcFpsczIrdGY0RzhlUAp3U25CejNXUGFINEdLcUxxY2xVK3Rxb3AvKzZWQ3BYRktRTmRZdTl1SkZwUTYrSmJMOC9WYXFPMWdnMlFiMGJECmRhZjVEOEpTY1p3ak9EcXVFMTZDWS9Tdkc5YjRObGJnSGg1SWFlNnhrRUsrdDd4NlJEdUMwZmdIdFlRV1VFRkcKR29sSkZHeUNTMlY3WjRFc09ycGcxY2UyV2x4dWYxaCswV2ZUaFlNd0l5ejE1RnFiRm1QQ0Y4VjllQzk3b0pqVgo5VHg3dlFIMVFGOGVGd0tDQVFFQXlsTncwNDZ4YXdqTmVQV2RRT2hVYzVaZ2lHaG9XSmxoeERadVoxQ2VQUnMrCmVCQWtDdmNLMHZYcVptNlg3UkpES0ZiR3JzczZFeGY1b1REbmpsRlM3bWZ4QlpTYmpUdmhUY2U0S3J1TS93K2cKZW0xTHROeDYvSlRJRGtabTNtWitXRFRtVEdIVFVhWlhkNTR1T2hpcC9PVWxabnp3c1F6V3ViRkJoVVE2aWliOQpKNmY1ZGFiMEFadmFsVDJETno5OWtXU2tNZm5CUFlvVlcyRCt3TzRiaUtyS2RobGVLTS9TZ2h3Nk9mWVF3M09pCmdIZGxuWVp4VzZwakh4Qk03U3BQUjEvYStnZkRaeGduNTdLV2RJalJWZ1ZPeEhBcm5EN3hMVzlwWVFLempDQUQKM0p6L29aZFRkWUtYK3FkdzV6SHRmMFF1TWE1T3kveEd0QzBHR0hHNkhRS0NBUUJZK0JSSzJxMUlpRUxpdUJiVwpnMmJFQUMwMVUzVWZoNmpUa1NlSzRqcTQ5Y3V1SUJVTmszbUlZNEw5TVEwaEtsSHpnbjhTRWNTRmJBVEVSR3QxCm85dW9KbGQwVlZrT3U4UVE0ajdzQndqU0xvMWxtRlA5aUYrWEhxRmdiUDZsdi9UblBMWUZOQW90SkNzdVF1K1oKejY1WTluZStZcWhTQmJrY0g3dTlqYXhsSGF6UmFlK25vRFIvdDVJZ0l4VmRxOFo0bVhvdlByUXhNOE55WmpYWgoySDNnTEx3NTJTdGFScjlzZ1BlYjNEWmFLYmMzVDR4RjZpYlM5KzZFK3E3VjkwT3pvWUpKbDRobWZ1STRNaDJYClBTdG5hL3Z3WllSRVVycnFoZkFoQXd1WW4wQ2FLejBzTnQwUjV1aXlXbXIxdVgvRVVqNUp6b0tiTVM3bWlsZHIKQzZvaEFvSUJBQnBqcjcrb1NQUFEvaHZjelZuOTg2WHY0ejluZnRTdmpOVHUxSStkTUVhQUd6SEZ5UzIwOHpCNAo2czNaZHU5eEJsbG9tQm1NWUZMRk9jWVFmdU5OTUNmd1hydC9tcHBkU1JIQUR3RmozUXdYbWJMZU5wV3NmRTRkCmljcHkzRS85eGxUeThXYmRDNWNHdnpuVzQraW45dVFKYjB0aTJISC9Yckp4c0Q0Vm5CMFE0TXdmV0taRDFHZXgKVUYxTzNLS0VJWGlwUzlGSTVpK0VweXg0QmtKYnpET1RNejBjS2UvZ3kzQTFCbE14Q1JhMUttamZLZDNWLzBzWgplancvQUFtZkR5QWNISStvRzVDQzBoR2lkWVZWb3hnNU1RRG1NSW44bTdCMWE2VS9tUE9aNldjSkF6NnNZakpkCmlqMGpRZEtsN0hoQkNGVnFpdFNJWHlCNUR1WjVUdkVDZ2dFQU9CVHpHelJHdGVzM1YvenFEaXNiSE04ZXdMMFMKaElqNnJ3ajlIRktmS04wY0E3QUR1UXBLbHFPcU8xZFJvSmZlYm1SWHpUeWxjQ1U2Y3RKQW1PWFF5V1U0dEhOaQpiNWpJRnlFaWRybVY5Z0d5VitIeFJicmQ2bGoyczl2MlEzNkJJbkJCYUxzR2cxeHN0OWU1ZzlyTndTY1RCZ25rCmthV1ZqMGdQcnY1VnNCUVF1Y2ZhMUVhVVhjZUpvY3BEcXcvS0hJbkxkYkNVN3dBKzZMRWZYZXZMbVpEdGluSkMKRWdZdUQrRWpOYUNrWDI4VTErVnk3NS81bXpBT1lCcFV4UVRQQ1NSb1NlLzBXWWJlelBDRjYycXBxZGhvdEZwVAo0U0RIQVZkemVBSXlOSFhUQXozeGMxcnRiUU03VGVWUXE1N3Y2cnkyWkIvZ1pqNUdwUHRvN1NmRzJRPT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K + apiServer: + image: registry.k8s.io/kube-apiserver:v1.35.3 + admissionControl: + - name: PodSecurity + configuration: + apiVersion: pod-security.admission.config.k8s.io/v1alpha1 + defaults: + audit: restricted + audit-version: latest + enforce: baseline + enforce-version: latest + warn: restricted + warn-version: latest + exemptions: + namespaces: + - kube-system + runtimeClasses: [] + usernames: [] + kind: PodSecurityConfiguration + auditPolicy: + apiVersion: audit.k8s.io/v1 + kind: Policy + rules: + - level: Metadata + controllerManager: + image: registry.k8s.io/kube-controller-manager:v1.35.3 + proxy: + disabled: true + image: registry.k8s.io/kube-proxy:v1.35.3 + scheduler: + image: registry.k8s.io/kube-scheduler:v1.35.3 + discovery: + enabled: true + registries: + kubernetes: + disabled: true + service: {} + etcd: + ca: + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJmakNDQVNTZ0F3SUJBZ0lSQU02WFZ6VTd6YlU5QjlqcTJZOU5IRzh3Q2dZSUtvWkl6ajBFQXdJd0R6RU4KTUFzR0ExVUVDaE1FWlhSalpEQWVGdzB5TlRFeE1Ea3dNak01TWpCYUZ3MHpOVEV4TURjd01qTTVNakJhTUE4eApEVEFMQmdOVkJBb1RCR1YwWTJRd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFRcUhzRllaNFA4CmNybTVzaFZEaWNvWWFweXJneGFsaWppcUJubWVMazVlOVM4RnVYMGFac2dwQXdpMkIwWUNNVndQM25SK3d3YVkKVVRtWGpOSDBhcXp2bzJFd1h6QU9CZ05WSFE4QkFmOEVCQU1DQW9Rd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSApBd0VHQ0NzR0FRVUZCd01DTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkxYMkJXSVdKU3diCk9zRW1JR21MTGI4aGp0VXhNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJRnBCbGdXUHB6Tkg0UkkrYTBrMEczcWIKSE42Z1pFSU5PZkhnWXV2MHNIYy9BaUVBc3p0eTd4ckdLVVYrOXF6bEpNRXBsWi9jVTJ4NFpQbVUzMmdraUQyagpDc1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUg1L2VmL0d0T1lKN2FuVWUzZnBQRW9ta21oRlJpOWM4eGtLN3ZTTGJPZy9vQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFS2g3QldHZUQvSEs1dWJJVlE0bktHR3FjcTRNV3BZbzRxZ1o1bmk1T1h2VXZCYmw5R21iSQpLUU1JdGdkR0FqRmNEOTUwZnNNR21GRTVsNHpSOUdxczd3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= + 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 + - https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml + 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: | + # created: 2025-12-01T02:18:04+01:00 + # public key: age1hktythzvsnth6u5en2lvag0tftnj9r03w7rpnzfgzgf5w95qxycq2azufj + AGE-SECRET-KEY-1ES2W2EQY2MXKF04QSNMWHERKFX0Y27CDMCWFX47Z3PV6H37R735SV8UDSZ + - 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: production + allowSchedulingOnControlPlanes: true +--- +apiVersion: v1alpha1 +kind: HostnameConfig +auto: "off" +hostname: helios +--- +apiVersion: v1alpha1 +kind: LinkConfig +name: enp2s0 +up: true +mtu: 9000 +addresses: + - address: 10.0.0.202/22 +routes: + - gateway: 10.0.0.1 +--- +apiVersion: v1alpha1 +kind: TimeSyncConfig +ntp: + servers: + - nl.pool.ntp.org +--- +apiVersion: v1alpha1 +kind: ResolverConfig +nameservers: + - address: 1.1.1.1 + - address: 8.8.8.8 +--- +apiVersion: v1alpha1 +kind: Layer2VIPConfig +name: 10.0.2.1 +link: enp2s0 +--- +apiVersion: v1alpha1 +kind: ExtensionServiceConfig +name: tailscale +environment: + - TS_AUTHKEY=hskey-auth-dn_t5EiIfKGe-GHafxPQRMudp4G_ygs4Rmc5a6TSzdxV4HzKklwOEy-gOiIovOxHfoFlrTt0cpiWp + - TS_EXTRA_ARGS=--login-server https://headscale.huizinga.dev + - TS_ROUTES= +--- +apiVersion: v1alpha1 +kind: UserVolumeConfig +name: longhorn +provisioning: + diskSelector: + match: system_disk + grow: true + maxSize: 2000GB +--- +apiVersion: v1alpha1 +kind: UserVolumeConfig +name: local-path-provisioner +volumeType: directory +--- +apiVersion: v1alpha1 +kind: VolumeConfig +name: EPHEMERAL +provisioning: + maxSize: 30GB \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 4a8d76d..e7d93b1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -49,4 +49,6 @@ pub enum Commands { Generate, /// Generate completions for your current shell ShellCompletions, + /// Test + Serve, } diff --git a/src/cluster.rs b/src/cluster.rs index bce3f70..b7b1b19 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::net::Ipv4Addr; use std::path::PathBuf; use std::process::Command; @@ -73,7 +74,7 @@ struct ClusterDeserialize { pub struct Cluster { pub(crate) name: String, pub(crate) version: Version, - nodes: Vec, + nodes: HashMap, cluster_env: ClusterEnv, pub(crate) control_plane_ip: Ipv4Addr, pub(crate) secrets_file: PathBuf, @@ -114,21 +115,24 @@ impl Cluster { let cluster = Self { name: cluster_name.into(), version: cluster.version, - nodes: vec![], + nodes: HashMap::new(), cluster_env: cluster.cluster_env, control_plane_ip: cluster.control_plane_ip, secrets_file, }; let nodes = nodes - .iter() - .map(|name| Node::get(name, env, &cluster, &default, &base)) + .into_iter() + .map(|name| { + let node = Node::get(&name, env, &cluster, &default, &base); + (node.hostname().into(), node) + }) .collect(); Self { nodes, ..cluster } } - pub fn get_all() -> Vec { + pub fn get_all() -> HashMap { let env = PathEnvironment::new_patches(); WalkDir::new(get_talos_path().join("clusters")) @@ -145,13 +149,19 @@ impl Cluster { return None; } - Some(Cluster::get(&name, &env)) + let cluster = Cluster::get(&name, &env); + + Some((name, cluster)) }) .collect() } - pub fn nodes(&self) -> &[Node] { - &self.nodes + pub fn nodes(&self) -> impl Iterator { + self.nodes.values() + } + + pub fn node(&self, name: &str) -> Option<&Node> { + self.nodes.get(name) } pub fn talosctl_gen_config_command(&self) -> Command { diff --git a/src/lib.rs b/src/lib.rs index 7685d78..e59ec8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(impl_trait_in_assoc_type, path_absolute_method)] +#![feature(impl_trait_in_assoc_type, path_absolute_method, never_type)] use std::path::{Path, PathBuf}; use std::sync::OnceLock; diff --git a/src/main.rs b/src/main.rs index b69af7f..278dede 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,10 @@ use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; -use crete::{get_configs_path, get_repo_path, set_repo_path}; +use crete::tftp::ServerError; +use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; use minijinja::context; +use regex::Regex; use thiserror::Error; use crate::cli::{Cli, Commands, GlobalOpts}; @@ -16,10 +18,15 @@ use crate::cli::{Cli, Commands, GlobalOpts}; enum Error { #[error("No clusters where found")] NoClustersFound, + #[error("Server error: {0}")] + Server(#[from] ServerError), } -fn run_command(mut command: Command) { - let output = command.output().unwrap(); +async fn run_command(mut command: Command) { + let output = tokio::task::spawn_blocking(move || command.output().unwrap()) + .await + .unwrap(); + if !output.stdout.is_empty() { println!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); } @@ -28,7 +35,7 @@ fn run_command(mut command: Command) { } } -fn generate(opts: &GlobalOpts) -> Result<(), Error> { +async fn generate(opts: &GlobalOpts) -> Result<(), Error> { set_repo_path(&opts.repo); let clusters = Cluster::get_all(); @@ -50,7 +57,7 @@ fn generate(opts: &GlobalOpts) -> Result<(), Error> { let content = template .render(context! { - clusters, + clusters => clusters.values().collect::>(), root => opts.repo }) .unwrap(); @@ -66,30 +73,68 @@ fn generate(opts: &GlobalOpts) -> Result<(), Error> { std::fs::create_dir(&path).unwrap(); // Generate config files - for cluster in clusters { + for cluster in clusters.values() { for node in cluster.nodes() { - run_command(node.talosctl_gen_config_command(&cluster)); + let config = node.talosctl_gen_config(cluster).await; + let path = node.config_path(cluster); + std::fs::create_dir_all(path.parent().expect("Config should have parent directory")) + .unwrap(); + + std::fs::write(path, config).unwrap(); } - run_command(cluster.talosctl_gen_config_command()); - run_command(cluster.talosctl_add_endpoint_command()); - run_command(cluster.talosctl_merge_command()); + run_command(cluster.talosctl_gen_config_command()).await; + run_command(cluster.talosctl_add_endpoint_command()).await; + run_command(cluster.talosctl_merge_command()).await; } Ok(()) } -fn main() -> Result<(), Error> { +#[tokio::main] +async fn main() -> Result<(), Error> { let cli = Cli::parse(); match cli.command { - Commands::Generate => generate(&cli.global_opts)?, + Commands::Generate => generate(&cli.global_opts).await.unwrap(), Commands::ShellCompletions => generate_complete( Shell::from_env().unwrap_or(Shell::Bash), &mut Cli::command(), "crete", &mut std::io::stdout(), ), + Commands::Serve => { + set_repo_path(&cli.global_opts.repo); + + tftp::serve(|filename| { + let filename = filename.to_owned(); + + async move { + let re = Regex::new(r"talos/(?.+)/(?.+)\.ya?ml") + .expect("Regex should be valid"); + + if let Some(caps) = re.captures(&filename) { + let cluster = &caps["cluster"]; + let node = &caps["node"]; + + let clusters = Cluster::get_all(); + let cluster = clusters.get(cluster)?; + let node = cluster.node(node)?; + + let config = node.talosctl_gen_config(cluster).await; + + Some(config.into()) + } else { + match filename.as_str() { + "ipxe.pxe" => Some(include_bytes!("../bin/ipxe.pxe").into()), + "test" => Some(vec![1, 2, 3, 4]), + _ => None, + } + } + } + }) + .await?; + } }; Ok(()) diff --git a/src/node.rs b/src/node.rs index ab5c166..bbf7e0f 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,10 +1,11 @@ use std::net::Ipv4Addr; -use std::process::Command; +use std::path::PathBuf; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use tokio::process::Command; use crate::cluster::{Base, Cluster}; use crate::patch::{OptionalPatches, OptionalPatchesString, Patches}; @@ -12,6 +13,17 @@ use crate::schematic::Schematic; use crate::secret::Secret; use crate::{get_configs_path, get_talos_path}; +async fn get_output(mut command: Command) -> String { + let output = command.output().await.unwrap(); + if !output.stderr.is_empty() { + println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); + } + + String::from_utf8_lossy(&output.stdout) + .trim_end() + .to_owned() +} + #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "camelCase")] enum NodeType { @@ -177,10 +189,11 @@ impl Node { Node { patches, ..node } } - pub fn talosctl_gen_config_command(&self, cluster: &Cluster) -> Command { - let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); - path.add_extension("yaml"); + pub fn hostname(&self) -> &str { + &self.hostname + } + pub async fn talosctl_gen_config(&self, cluster: &Cluster) -> String { let mut command = Command::new("talosctl"); command.args([ "gen", @@ -204,7 +217,7 @@ impl Node { "--with-docs=false", "--with-examples=false", "-o", - path.to_str().expect("Path should be valid utf-8"), + "-", // path.to_str().expect("Path should be valid utf-8"), ]); for patch in &self.patches.all { @@ -218,7 +231,14 @@ impl Node { ]); } - command + get_output(command).await + } + + pub fn config_path(&self, cluster: &Cluster) -> PathBuf { + let mut path = get_configs_path().join(&cluster.name).join(&self.hostname); + path.add_extension("yaml"); + + path } } diff --git a/src/schematic.rs b/src/schematic.rs index e6f35d7..eacb6f2 100644 --- a/src/schematic.rs +++ b/src/schematic.rs @@ -1,5 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize}; +use tokio::runtime::Handle; use crate::get_talos_path; @@ -11,7 +12,7 @@ where let path = get_talos_path().join("schematics").join(name); let content = std::fs::read_to_string(path).unwrap().trim().to_owned(); - let client = reqwest::blocking::ClientBuilder::new() + let client = reqwest::ClientBuilder::new() .user_agent(format!( "{}/{}", std::env!("CARGO_PKG_NAME"), @@ -20,19 +21,27 @@ where .build() .unwrap(); - let res = client - .post("https://factory.talos.dev/schematics") - .body(content) - .send() - .map_err(serde::de::Error::custom)?; + let res = tokio::task::block_in_place(|| { + Handle::current().block_on(async { + client + .post("https://factory.talos.dev/schematics") + .body(content) + .send() + .await + }) + }) + .map_err(serde::de::Error::custom)?; #[derive(Debug, Deserialize)] struct Response { id: String, } - let response: Response = serde_json::from_str(&res.text().map_err(serde::de::Error::custom)?) - .map_err(serde::de::Error::custom)?; + let text = + tokio::task::block_in_place(|| Handle::current().block_on(async { res.text().await })) + .map_err(serde::de::Error::custom)?; + + let response: Response = serde_json::from_str(&text).map_err(serde::de::Error::custom)?; Ok(response.id) } diff --git a/src/tftp/server.rs b/src/tftp/server.rs index 1586c09..9f5ad81 100644 --- a/src/tftp/server.rs +++ b/src/tftp/server.rs @@ -28,11 +28,15 @@ pub enum Error { Timeout, } -pub async fn handle_connection( +pub async fn handle_connection( src: SocketAddr, packet: Packet, - callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, -) -> Result<(), Error> { + callback: F, +) -> Result<(), Error> +where + F: Fn(&str) -> Fut, + Fut: Future>>, +{ let socket = UdpSocket::bind("127.0.0.1:0").await?; println!( "{src}: Connecting from {}", @@ -50,7 +54,7 @@ pub async fn handle_connection( let filename = request.filename(); println!("{src}: {filename} [{:?}]", request.mode()); - let Some(data) = callback(filename) else { + let Some(data) = callback(filename).await else { Packet::error(ErrorCode::FileNotFound, "Unknown file") .send_to(&socket, &src) .await?; @@ -141,9 +145,11 @@ pub async fn handle_connection( Ok(()) } -pub async fn serve( - callback: impl (Fn(&str) -> Option>) + Send + Clone + 'static, -) -> Result<(), Error> { +pub async fn serve(callback: F) -> Result +where + F: (Fn(&str) -> Fut) + Clone + Send + 'static, + Fut: Future>> + Send + 'static, +{ let socket = UdpSocket::bind("127.0.0.1:69").await?; println!("Server started"); diff --git a/test b/test new file mode 100644 index 0000000..82090ee --- /dev/null +++ b/test @@ -0,0 +1 @@ + \ No newline at end of file