├── LICENSE ├── README.md └── talos-bootstrap /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # talos-bootstrap 2 | 3 | An interactive script for bootstrapping Kubernetes clusters on Talos OS. 4 | 5 | *Example: bootstrap full-feature Kubernetes cluster in 5 minutes*: 6 | [![screencast](https://raw.githubusercontent.com/cozystack/talos-bootstrap/2cc7b82065747e373cd914c87c8cd5c6582c5c6c/images/627123.gif)](https://asciinema.org/a/gwK85Tdr577GsxjXWo7otPFjv) 7 | 8 | # Installation 9 | 10 | Install dependencies: 11 | - `talosctl` (>=1.6.0) 12 | - `dialog` 13 | - `nmap` 14 | 15 | Install talos-bootstrap: 16 | 17 | ``` 18 | curl -LO https://github.com/cozystack/talos-bootstrap/raw/master/talos-bootstrap 19 | chmod +x ./talos-bootstrap 20 | sudo mv ./talos-bootstrap /usr/local/bin/talos-bootstrap 21 | ``` 22 | 23 | # Usage 24 | 25 | - Boot your nodes with Talos in maintenance mode. 26 | (booting from [ISO](https://www.talos.dev/v1.5/talos-guides/install/bare-metal-platforms/iso/) or PXE using [matchbox](https://www.talos.dev/latest/talos-guides/install/bare-metal-platforms/matchbox/) is the best option) 27 | - Create a directory for holding your cluster configuration. 28 | - Run `talos-bootstrap` command for every node in your cluster. 29 | 30 | 31 | ### Options 32 | 33 | ``` 34 | USAGE: 35 | talos-bootstrap ACTION [OPTIONS] 36 | 37 | ACTIONS: 38 | help Show this help message. 39 | install Setup a node for a new or existing cluster. 40 | upgrade Upgrade a node in an existing cluster. 41 | reset Reset and remove a node from an existing cluster. 42 | reboot Reboot a node. 43 | shutdown Shutdown a node. 44 | dashboard Open dashboard for a node. 45 | 46 | OPTIONS: 47 | -n, --node
Node address 48 | ``` 49 | 50 | ### Customizations 51 | 52 | You can specify your customizations in one of the following files: 53 | 54 | - `patch.yaml` - common settings used for all nodes. 55 | - `patch-controlplane.yaml` - settings used for controlplane nodes only 56 | - `patch-worker.yaml` - settings used for worker nodes only 57 | 58 | Read the [Configuration Patches](https://www.talos.dev/latest/talos-guides/configuration/patching/) documentation for more details. 59 | 60 | ### Advanced configuration management 61 | 62 | Looking for enhanced, non-interactive version of talos-bootstrap? 63 | 64 | Take a look at [Talm](https://github.com/cozystack/talm) project. 65 | 66 | # Copyright 67 | 68 | Andrei Kvapil 69 | Licensed under Apache 2.0 License 70 | -------------------------------------------------------------------------------- /talos-bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2023 Andrei Kvapil. All rights reserved. 4 | # SPDX-License-Identifier: Apache2.0 5 | # 6 | # This code should (try to) follow Google's Shell Style Guide 7 | # - https://google.github.io/styleguide/shell.xml 8 | # 9 | 10 | show_help() { 11 | printf "USAGE:\n\t%s\n" "talos-bootstrap ACTION [OPTIONS]" 12 | printf "\nACTIONS:\n" 13 | printf "\t%s\t\t%s\n" "help" "Show this help message." 14 | printf "\t%s\t\t%s\n" "install" "Setup a node for a new or existing cluster." 15 | printf "\t%s\t\t%s\n" "upgrade" "Upgrade a node in an existing cluster." 16 | printf "\t%s\t\t%s\n" "reset" "Reset and remove a node from an existing cluster." 17 | printf "\t%s\t\t%s\n" "reboot" "Reboot a node." 18 | printf "\t%s\t%s\n" "shutdown" "Shutdown a node." 19 | printf "\t%s\t%s\n" "dashboard" "Open dashboard for a node." 20 | printf "\nOPTIONS:\n" 21 | printf "\t%s <%s>\t%s\n" "-n, --node" "address" "Node address" 22 | } 23 | 24 | node= 25 | OP= 26 | while [ $# -gt 0 ]; do 27 | key="$1" 28 | case $key in 29 | -h|--help) 30 | show_help 31 | exit 0 32 | ;; 33 | -n | --node) 34 | node="$2" 35 | shift 36 | shift 37 | ;; 38 | -*) 39 | echo "flag provided but not defined: ${1}" >&2 40 | exit 1 41 | ;; 42 | *) 43 | if [ -z "$OP" ]; then 44 | OP="${1}" 45 | shift 46 | else 47 | echo "exactly one action required" >&2 48 | exit 1 49 | fi 50 | ;; 51 | esac 52 | done 53 | 54 | case "$OP" in 55 | install) 56 | OPTS="-i" 57 | ;; 58 | upgrade|dashboard|reboot|shutdown|reset) 59 | OPTS="--talosconfig=talosconfig" 60 | ;; 61 | *) 62 | show_help 63 | exit 1 64 | ;; 65 | esac 66 | 67 | # Version check 68 | command -V dialog || exit 1 69 | command -V talosctl || exit 1 70 | command -V awk || exit 1 71 | command -V nmap || exit 1 72 | 73 | VERSION_REQUIRED=v1.8 74 | if [ $(echo "${VERSION_REQUIRED}\n$(talosctl version --client | awk '$1 == "Tag:" {print $2}')" | sort -V | tail -n 1) = "${VERSION_REQUIRED}" ]; then 75 | echo "talos-bootstrap requires talosctl version ${VERSION_REQUIRED} or higher. Please check 'talosctl version --client'" >&2 76 | exit 1 77 | fi 78 | 79 | # Load cluster configuration 80 | if [ -f cluster.conf ]; then 81 | for key in BOOTSTRAP_ETCD CLUSTER_NAME KUBERNETES_API_ENDPOINT VIP_ADDRESS; do 82 | val=$(awk "/^${key}=/ "'{sub(/^[^=]+="?/, ""); sub(/"$/, ""); print}' cluster.conf) 83 | export "CONFIG_${key}=${val}" 84 | done 85 | fi 86 | 87 | if [ "$OP" != install ]; then 88 | if [ ! -f cluster.conf ] || [ ! -f secrets.yaml ]; then 89 | echo "Error: ./cluster.conf and ./secrets.yaml are required for $OP operation" >&2 90 | exit 1 91 | fi 92 | # Generate talosconfig 93 | talosctl gen config "$CONFIG_CLUSTER_NAME" "$CONFIG_KUBERNETES_API_ENDPOINT" --with-secrets=secrets.yaml --force -t talosconfig || exit $? 94 | fi 95 | 96 | # Screen: Enter cluster name 97 | if [ -n "${CONFIG_CLUSTER_NAME}" ]; then 98 | cluster_name="${CONFIG_CLUSTER_NAME}" 99 | else 100 | should_bootstrap=1 101 | default_cluster_name="$(basename "${PWD}")" 102 | if [ -z "${default_cluster_name}" ] || [ "${default_cluster_name}" = "/" ]; then 103 | default_cluster_name=talos 104 | fi 105 | cluster_name=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter cluster name:" 8 40 "${default_cluster_name}" 3>&1 1>&2 2>&3) || exit 0 106 | fi 107 | 108 | if [ -n "${node}" ]; then 109 | talosctl -e "${node}" -n "${node}" get machinestatus ${OPTS} >/dev/null || exit $? 110 | else 111 | # Screen: Enter networks to scan 112 | default_scan_networks=$(ip -o route | awk '$3 !~ /^(docker|cni)/ && $2 == "dev" {print $1}' | awk '$1=$1' RS=" " OFS=" ") 113 | scan_networks=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter networks to scan:" 8 80 "${default_scan_networks}" 3>&1 1>&2 2>&3) || exit 0 114 | scan_networks=$(echo "${scan_networks}" | awk -F, '{$1=$1}1' OFS=' ') 115 | 116 | node_list_file=$(mktemp) 117 | 118 | # Screen: Seatching Talos nodes 119 | { 120 | printf "%s\nXXX\n%s\nXXX\n" "10" "Searching Talos nodes in ${scan_networks}..." 121 | candidate_nodes=$(nmap -Pn -n -p 50000 ${scan_networks} -vv | awk '/Discovered open port/ {print $NF}') 122 | 123 | #echo found: 124 | #printf " - %s\n" $candidate_nodes 125 | 126 | if [ "$OP" != install ]; then 127 | printf "%s\nXXX\n%s\nXXX\n" "40" "Filtering nodes in the cluster..." 128 | else 129 | printf "%s\nXXX\n%s\nXXX\n" "40" "Filtering nodes in maintenance mode..." 130 | fi 131 | nodes= 132 | for node in ${candidate_nodes}; do 133 | if talosctl -e "${node}" -n "${node}" get machinestatus ${OPTS} >/dev/null 2>/dev/null; then 134 | nodes="${nodes} ${node}" 135 | fi 136 | done 137 | 138 | #echo filtered: 139 | #printf " - %s\n" $nodes 140 | 141 | printf "%s\nXXX\n%s\nXXX\n" "60" "Collecting information about the nodes..." 142 | seen= 143 | for node in ${nodes}; do 144 | mac=$(talosctl -e "${node}" -n "${node}" get hardwareaddresses.net.talos.dev first ${OPTS} -o jsonpath='{.spec.hardwareAddr}') 145 | case " ${seen} " in *" ${mac} "*) continue ;; esac # remove duplicated nodes 146 | seen="${seen} ${mac}" 147 | name="${node}" 148 | hostname=$(talosctl -e "${node}" -n "${node}" get hostname ${OPTS} -o jsonpath='{.spec.hostname}') 149 | if [ -n "${hostname}" ]; then 150 | name="${name} (${hostname})" 151 | fi 152 | manufacturer=$(talosctl -e "${node}" -n "${node}" get cpu ${OPTS} -o jsonpath='{.spec.manufacturer}' | head -n1) 153 | cpu=$(talosctl -e "${node}" -n "${node}" get cpu ${OPTS} -o jsonpath='{.spec.threadCount}' | awk '{sum+=$1;} END{print sum "-core";}') 154 | ram=$(talosctl -e "${node}" -n "${node}" get ram -o json ${OPTS} | awk '/"sizeMiB":/ {sub(",", ""); sum+=$2} END{print sum/1024 "GB"}') 155 | disks=$(talosctl -e "${node}" -n "${node}" get disks ${OPTS} | awk 'sub(/^.*runtime +Disk +/, "", $0) {print $0}' | awk '$1 !~ "^(zd|drbd|loop|sr)" {print $1 ":" $3$4}' | awk '$1=$1' RS="," OFS=",") 156 | echo "\"${name}\"" "\"${mac}, ${cpu} ${manufacturer:-CPU}, RAM: ${ram}, Disks: [${disks}]\"" >> "${node_list_file}" 157 | done 158 | 159 | } | dialog --keep-tite --title talos-bootstrap --gauge "Please wait" 10 70 0 3>&1 1>&2 2>&3 || exit 0 160 | 161 | node_list=$(cat "${node_list_file}") 162 | 163 | if [ -z "${node_list}" ]; then 164 | if [ "$OP" != install ]; then 165 | message="No Talos nodes found in the cluster!" 166 | else 167 | message="No Talos nodes in maintenance mode found!" 168 | fi 169 | dialog --keep-tite --title talos-bootstrap --msgbox "$message 170 | 171 | Searched networks: ${scan_networks}" 10 60 172 | exit 1 173 | fi 174 | 175 | # Screen: Node list 176 | node=$(echo "${node_list}" | dialog --keep-tite --title talos-bootstrap --menu "Select node to $OP" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 177 | # cut hostname 178 | node=$(echo "${node}" | awk '{print $1}') 179 | fi 180 | 181 | # --- Management flows beginning 182 | 183 | # Run dashboard flow 184 | if [ "$OP" = dashboard ]; then 185 | talosctl -e "${node}" -n "${node}" ${OPTS} dashboard 186 | exit $? 187 | fi 188 | # Run reset flow 189 | if [ "$OP" = reset ]; then 190 | reset_option=$(dialog --keep-tite --title talos-bootstrap --menu "Select reset option" 0 0 0 \ 191 | 1 "graceful reset and reboot" \ 192 | 2 "graceful reset and shutdown" \ 193 | 3 "force reset and reboot" \ 194 | 4 "force reset and shutdown" 3>&1 1>&2 2>&3) || exit 0 195 | reset_opt= 196 | case ${reset_option} in 197 | 1) reset_opt="--graceful=true --reboot=true" ;; 198 | 2) reset_opt="--graceful=true --reboot=false" ;; 199 | 3) reset_opt="--graceful=false --reboot=true" ;; 200 | 4) reset_opt="--graceful=false --reboot=false" ;; 201 | esac 202 | wipe_mode=$(dialog --keep-tite --title talos-bootstrap --menu "Select wipe option" 15 60 4 \ 203 | "all" "Wipe all disks" \ 204 | "system-disk" "Wipe system disk" \ 205 | "user-disks" "Wipe user disks" 3>&1 1>&2 2>&3) || exit 0 206 | talosctl -e "${node}" -n "${node}" ${OPTS} ${reset_opt} --wipe-mode="${wipe_mode}" reset 207 | exit $? 208 | fi 209 | # Run reboot flow 210 | if [ "$OP" = reboot ]; then 211 | reboot_mode=$(dialog --keep-tite --title talos-bootstrap --menu "Select reboot option" 15 60 4 \ 212 | "default" "Default mode" \ 213 | "powercycle" "Skips kexec" 3>&1 1>&2 2>&3) || exit 0 214 | talosctl -e "${node}" -n "${node}" ${OPTS} ${reboot_opt} --mode="${reboot_mode}" reboot 215 | exit $? 216 | fi 217 | # Run shutdown flow 218 | if [ "$OP" = shutdown ]; then 219 | shutdown_option=$(dialog --keep-tite --title talos-bootstrap --menu "Select shutdown option" 15 60 4 \ 220 | "1" "Default mode" \ 221 | "2" "Force a node to shutdown without a cordon/drain" 3>&1 1>&2 2>&3) || exit 0 222 | case ${shutdown_option} in 223 | 1) shutdown_opt="--force=false" ;; 224 | 2) shutdown_opt="--force=true" ;; 225 | esac 226 | talosctl -e "${node}" -n "${node}" ${OPTS} ${shutdown_opt} shutdown 227 | exit $? 228 | fi 229 | 230 | # --- Management flows end 231 | 232 | # Screen: Select role 233 | default_role=$(talosctl -e "${node}" -n "${node}" get machinetype machine-type ${OPTS} -o jsonpath={.spec}) 234 | role=$(dialog --keep-tite --title talos-bootstrap --default-item "${default_role}" --menu "Select role" 0 0 0 \ 235 | "controlplane" "Responsible for running cluster components" \ 236 | "worker" "Responsible for running workloads" 3>&1 1>&2 2>&3) || exit 0 237 | 238 | # Screen: Select hostname 239 | default_hostname=$(talosctl -e "${node}" -n "${node}" get hostname ${OPTS} -o jsonpath='{.spec.hostname}') 240 | hostname=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter hostname:" 8 40 "${default_hostname}" 3>&1 1>&2 2>&3) || exit 0 241 | 242 | # Screen: Select disk to install 243 | disks_list=$(talosctl -e "${node}" -n "${node}" get disks ${OPTS} | awk 'sub(/^.*Disk +/, "", $0) {print $0}' | awk '$1 !~ "^(zd|drbd|loop|sr)" {f=$1;$2=$1=""; sub(/^ +/, "", $0); print "\"" f "\" \"" $0 "\""}') 244 | default_disk=$(talosctl get systemdisk -n ${node} -e ${node} system-disk ${OPTS} -o jsonpath={.spec.diskID}) 245 | disk=$(echo "${disks_list}" | dialog --keep-tite --title talos-bootstrap --default-item "${default_disk}" --menu "Select disk to install" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 246 | 247 | # Screen: Select interface 248 | link_list=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} | awk -F' +' 'NR>1 && $4 ~ /^(ID|eno|eth|enp|enx|ens|bond)/ {print $4 "|" $(NF-2)}') 249 | address_list=$(talosctl -e "${node}" -n "${node}" get addresses ${OPTS} | awk 'NR>1 {print $NF " " $(NF-1)}') 250 | 251 | default_addresses=$(talosctl -e "${node}" -n "${node}" get nodeaddress default ${OPTS} -o jsonpath='{.spec.addresses[*]}' | awk '$1=$1' RS=, OFS=,) 252 | default_interface=$(talosctl -e "${node}" -n "${node}" get addresses ${OPTS} | awk "BEGIN { split(\"${default_addresses}\", arr, \",\"); } {for (i in arr) if (\$(NF-1) == arr[i]) print \$NF}" | awk 'NR==1') 253 | 254 | interface_list=$( 255 | for link_mac in ${link_list}; do 256 | link="${link_mac%%|*}" 257 | mac="${link_mac#*|}" 258 | ips=$(echo "${address_list}" | awk "\$1 == \"${link}\" {print \$2}" | awk '$1=$1' RS=, OFS=,) 259 | details="${mac}" 260 | if [ -n "${ips}" ]; then 261 | details="${mac} (${ips})" 262 | fi 263 | echo "${link} \"${details}\"" 264 | done 265 | ) 266 | 267 | interface=$(echo "${interface_list}" | dialog --keep-tite --title talos-bootstrap --default-item "${default_interface}" --menu "Select interface:" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 268 | 269 | interface_kind=$(talosctl -e "${node}" -n "${node}" get link "${interface}" ${OPTS} -o jsonpath='{.spec.kind}') || exit $? 270 | 271 | if [ "$interface_kind" = bond ]; then 272 | # Screen: Select slave interfaces for bonding 273 | interface_index=$(talosctl -e "${node}" -n "${node}" get link "${interface}" ${OPTS} -o jsonpath='{.spec.index}') || exit $? 274 | default_slave_interfaces=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.metadata.id}{.spec}' | \ 275 | awk '{sub("}", "\n"); print}' ORS=""| awk -F '{' '/masterIndex/ {sub("{.*masterIndex", ""); print}' | awk '{sub(",.*", ""); sub("\":", ""); if ($2 == '"${interface_index}"') print $1}') 276 | slave_interfaces_list=$(echo "$interface_list" | awk '$1 !~ /^bond/' | while read link details; do 277 | echo "$link" "$details" "$(echo "$default_slave_interfaces" | awk "\$1 == \"$link\" {found=1; exit} END {print found ? \"on\" : \"off\"}")" 278 | done) 279 | slave_interfaces=$(echo "${slave_interfaces_list}" | dialog --keep-tite --title talos-bootstrap --checklist "Select interfaces for bonding:" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 280 | slave_interfaces=$(echo "${slave_interfaces}" | awk '{$1=$1}1' OFS=",") 281 | 282 | if [ -z "$slave_interfaces" ]; then 283 | echo "Error: at least one slave must be specified for bonding interface!" >&2 284 | exit 1 285 | fi 286 | 287 | # Screen: Select bond mode 288 | default_bond_mode=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.mode}' "${interface}") 289 | bond_mode=$(dialog --keep-tite --title talos-bootstrap --default-item "${default_bond_mode}" --menu "Select bond mode" 0 0 0 \ 290 | "balance-rr" "Balance Round-Robin" \ 291 | "active-backup" "Active-Backup" \ 292 | "balance-xor" "Balance-xor" \ 293 | "broadcast" "Broadcast" \ 294 | "802.3ad" "Dynamic link aggregation" \ 295 | "balance-tlb" "Adaptive transmit load balancing" \ 296 | "balance-alb" "Adaptive load balancing" 3>&1 1>&2 2>&3) || exit 0 297 | 298 | if [ "${bond_mode}" = "802.3ad" ]; then 299 | # Screen: Select bond lacp rate 300 | default_bond_lacp_rate=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.lacpRate}' "${interface}") 301 | bond_lacp_rate=$(dialog --keep-tite --title talos-bootstrap --default-item "${default_bond_lacp_rate}" --menu "Select bond lacp rate" 0 0 0 \ 302 | "slow" "transmit LACPDUs every 30 seconds" \ 303 | "fast" "transmit LACPDUs every 1 second" 3>&1 1>&2 2>&3) || exit 0 304 | fi 305 | 306 | # Screen: Select bond xmitHashPolicy 307 | default_bond_xmit_hash_policy=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.xmitHashPolicy}' "${interface}") 308 | bond_lacp_xmit_hash_policy=$(dialog --keep-tite --title talos-bootstrap --default-item "${default_bond_xmit_hash_policy}" --menu "Select bond xmit_hash_policy" 0 0 0 \ 309 | "layer2" "uses XOR of hardware MAC addresses and packet type ID field to generate the hash." \ 310 | "layer2+3" "uses a combination of layer2 and layer3 protocol information to generate the hash." \ 311 | "layer3+4" "uses upper layer protocol information, when available, to generate the hash." 3>&1 1>&2 2>&3) || exit 0 312 | 313 | # Screen: Enter bond miimon 314 | default_bond_miimon=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.miimon}' "${interface}") 315 | bond_miimon=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter bond miimon:" 8 40 "${default_bond_miimon:-0}" 3>&1 1>&2 2>&3) || exit 0 316 | 317 | # Screen: Enter bond updelay 318 | default_bond_updelay=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.updelay}' "${interface}") 319 | bond_updelay=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter bond updelay:" 8 40 "${default_bond_updelay:-0}" 3>&1 1>&2 2>&3) || exit 0 320 | 321 | # Screen: Enter bond downdelay 322 | default_bond_downdelay=$(talosctl -e "${node}" -n "${node}" get link ${OPTS} -o jsonpath='{.spec.bondMaster.downdelay}' "${interface}") 323 | bond_downdelay=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter bond downdelay:" 8 40 "${default_bond_downdelay:-0}" 3>&1 1>&2 2>&3) || exit 0 324 | fi 325 | 326 | # Screen: Configure networks 327 | default_addresses=$(talosctl -e "${node}" -n "${node}" get nodeaddress default ${OPTS} -o jsonpath='{.spec.addresses[*]}' | awk '$1=$1' RS=, OFS=,) 328 | addresses=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter addresses:" 8 40 "${default_addresses}" 3>&1 1>&2 2>&3) || exit 0 329 | addresses=$(echo "${addresses}" | awk '{$1=$1}1' OFS=",") 330 | # select first address 331 | address=$(echo "${addresses}" | awk -F/ '{print $1}') 332 | 333 | # Screen: Configure default gateway 334 | default_gateway=$(talosctl -e "${node}" -n "${node}" get routes ${OPTS} -o json | awk '{gsub(/[{}]/, "\n"); printf $0}' | awk -F', *' '$0 ~ /"dst": *""/ && $0 !~ /"gateway": *""/ {for(i=1;i<=NF;i++) if ($i ~ /"gateway":/) {split($i,a,"\""); print a[4]; exit}}') 335 | gateway=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter gateway:" 8 40 "${default_gateway}" 3>&1 1>&2 2>&3) || exit 0 336 | 337 | # Screen: Configure DNS servers 338 | default_dns_servers=$(talosctl -e "${node}" -n "${node}" get resolvers resolvers ${OPTS} -o jsonpath='{.spec.dnsServers[*]}' | awk '$1=$1' RS=" " OFS=" ") 339 | dns_servers=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter DNS servers:" 8 80 "${default_dns_servers}" 3>&1 1>&2 2>&3) || exit 0 340 | dns_servers=$(echo "${dns_servers}" | awk '{$1=$1}1' OFS=",") 341 | 342 | # Screen: Configure VIP 343 | vip_address="${CONFIG_VIP_ADDRESS}" 344 | if [ "${role}" = controlplane ]; then 345 | vip_address=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter virtual shared IP address, or leave blank to skip:" 8 40 "${vip_address}" 3>&1 1>&2 2>&3) || exit 0 346 | fi 347 | 348 | # Screen: Configure Kubernetes endpoint 349 | default_k8s_endpoint="${CONFIG_KUBERNETES_API_ENDPOINT:-https://${vip_address:-${address}}:6443}" 350 | k8s_endpoint=$(dialog --keep-tite --title talos-bootstrap --inputbox "Enter Kubernetes endpoint:" 8 40 "${default_k8s_endpoint}" 3>&1 1>&2 2>&3) || exit 0 351 | 352 | # Generating config patch... 353 | machine_config=$(cat < "${file}" 403 | 404 | dialog --keep-tite --title talos-bootstrap --ok-label "OK" --extra-button --extra-label "Cancel" --textbox "${file}" 0 0 || exit 0 405 | rm -f "${file}" 406 | trap '' EXIT 407 | 408 | # Generating configuration... 409 | if [ ! -f secrets.yaml ]; then 410 | talosctl gen secrets || exit $? 411 | fi 412 | patches="" 413 | if [ -f patch.yaml ]; then 414 | patches="${patches} --config-patch=@patch.yaml" 415 | fi 416 | if [ -f patch-controlplane.yaml ]; then 417 | patches="${patches} --config-patch-control-plane=@patch-controlplane.yaml" 418 | fi 419 | if [ -f patch-worker.yaml ]; then 420 | patches="${patches} --config-patch-worker=@patch-worker.yaml" 421 | fi 422 | talosctl gen config "${cluster_name}" "${k8s_endpoint}" --with-secrets=secrets.yaml ${patches} --config-patch="${machine_config}" --force || exit $? 423 | 424 | # Screen: Select upgrade option 425 | if [ "$OP" = upgrade ]; then 426 | upgrade_option=$(dialog --keep-tite --title talos-bootstrap --menu "Select upgrade option" 0 0 0 \ 427 | 1 "apply config on live and skip upgrade" \ 428 | 2 "apply config and perform upgrade (mode: default)" \ 429 | 3 "apply config and perform upgrade (mode: powercycle)" \ 430 | 4 "apply config and perform upgrade with force (mode: default)" \ 431 | 5 "apply config and perform upgrade with force (mode: powercycle)" \ 432 | 6 "apply config and schedule upgrade to perform it after a reboot" 3>&1 1>&2 2>&3) || exit 0 433 | 434 | should_upgrade=1 435 | upgrade_opt="" 436 | case ${upgrade_option} in 437 | 1) should_upgrade=0 ;; 438 | 2) upgrade_opt="--reboot-mode default" ;; 439 | 3) upgrade_opt="--reboot-mode powercycle" ;; 440 | 4) upgrade_opt="--force --reboot-mode default" ;; 441 | 5) upgrade_opt="--force --reboot-mode powercycle" ;; 442 | 6) upgrade_opt="--stage" ;; 443 | esac 444 | fi 445 | 446 | # Swap IP addresses 447 | bootstrap_ip=${node} 448 | node="${address}" 449 | 450 | # Try applying config before install 451 | talosctl apply -e "${bootstrap_ip}" -n "${bootstrap_ip}" -f "${role}.yaml" ${OPTS} --dry-run || exit $? 452 | 453 | # Screen: Installation process 454 | { 455 | printf "%s\nXXX\n%s\nXXX\n" "1" "Applying configuration..." 456 | talosctl apply -e "${bootstrap_ip}" -n "${bootstrap_ip}" -f "${role}.yaml" ${OPTS} >/dev/null 2>&1 457 | 458 | if [ "$OP" = upgrade ] && [ "${should_upgrade}" = 1 ]; then 459 | image=$(talosctl -e "${node}" -n "${node}" get machineconfig -o jsonpath="{.spec.machine.install.image}" ${OPTS}) 460 | printf "%s\nXXX\n%s\n%s\nXXX\n" "10" "Scheduling upgrade..." "(image: $image)" 461 | talosctl -e "${node}" -n "${node}" upgrade ${upgrade_opt} -i "${image}" --wait=false ${OPTS} || exit $? 462 | fi 463 | 464 | if [ "$OP" = upgrade ]; then 465 | printf "%s\nXXX\n%s\nXXX\n" "10" "Installing..." 466 | else 467 | printf "%s\nXXX\n%s\n%s\nXXX\n" "20" "Upgrading..." "(this will take a while)" 468 | fi 469 | 470 | old_is_up=1 471 | old_is_pingable=1 472 | new_is_pingable=0 473 | new_is_up=0 474 | until [ "${new_is_up}" = 1 ] ; do 475 | sleep 0.2 476 | if [ "${new_is_pingable}" = 0 ]; then 477 | if [ "${old_is_up}" = 1 ]; then 478 | status=$(timeout 3 talosctl --talosconfig=talosconfig -e "${node}" -n "${node}" get machinestatus ${OPTS} -o jsonpath={.spec.stage} 2>/dev/null) 479 | if [ $? = 124 ]; then 480 | old_is_up=0 481 | fi 482 | if [ "$status" = upgrading ] || [ "$status" = rebooting ]; then 483 | continue 484 | fi 485 | else 486 | if ! ping -W1 -c1 "${node}" >/dev/null 2>&1; then 487 | if ! ping -W1 -c1 "${node}" >/dev/null 2>&1; then # TODO dirty hack 488 | old_is_pingable=0 489 | fi 490 | fi 491 | fi 492 | fi 493 | 494 | if [ "${old_is_pingable}" = 0 ]; then 495 | if ping -W1 -c1 "${node}" >/dev/null 2>&1; then 496 | new_is_pingable=1 497 | fi 498 | fi 499 | 500 | if timeout 3 talosctl --talosconfig=talosconfig -e "${node}" -n "${node}" get machinestatus >/dev/null 2>&1; then 501 | new_is_up=1 502 | fi 503 | 504 | case ${old_is_up}${old_is_pingable}${new_is_pingable} in 505 | 110) printf "%s\nXXX\n%s\n%s\nXXX\n" "20" "Installing..." "(port is open at ${node})" ;; 506 | 010) printf "%s\nXXX\n%s\n%s\nXXX\n" "50" "Rebooting..." "(node is pingable at ${node})" ;; 507 | 000) printf "%s\nXXX\n%s\n%s\nXXX\n" "70" "Rebooting..." "(node is not pingable at ${node})" ;; 508 | 001) printf "%s\nXXX\n%s\n%s\nXXX\n" "80" "Rebooting..." "(node is pingable again at ${node})" ;; 509 | esac 510 | done 511 | } | dialog --keep-tite --title talos-bootstrap --gauge "Please wait" 10 70 0 3>&1 1>&2 2>&3 || exit 1 512 | 513 | # Screen: Should we bootstrap etcd? 514 | if [ "${CONFIG_BOOTSTRAP_ETCD}" = true ]; then 515 | should_bootstrap=1 516 | elif [ "${CONFIG_BOOTSTRAP_ETCD}" = false ]; then 517 | should_bootstrap=0 518 | else 519 | dialog --stdout --keep-tite --title talos-bootstrap \ 520 | --yesno "It seems this is a first node in a cluster. Should we bootstrap etcd on it?" 7 60 3>&1 1>&2 2>&3 521 | response=$? 522 | case ${response} in 523 | 0) should_bootstrap=1 ;; 524 | 1) should_bootstrap=0 ;; 525 | *) exit 0 ;; 526 | esac 527 | fi 528 | 529 | if [ "${should_bootstrap}" = 1 ]; then 530 | count=0 531 | max_retries=20 532 | while ! nmap -Pn ${vip_address} -p 50000 | grep -q 'open' && [ ${count} -lt ${max_retries} ]; do 533 | count=$((count+1)) 534 | sleep 5 535 | talosctl --talosconfig=talosconfig -e "${node}" -n "${node}" bootstrap 536 | done 537 | if [ ${count} -ge ${max_retries} ]; then 538 | dialog --keep-tite --title "talos-bootstrap" --msgbox "Port 50000 closed, ETCD is not installed!" 5 26 539 | exit 1 540 | fi 541 | fi 542 | 543 | 544 | # Saving cluster configuration 545 | cat > cluster.conf <