├── 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 | [](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 <