-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcluster-sync.sh
More file actions
365 lines (317 loc) · 12.3 KB
/
Copy pathcluster-sync.sh
File metadata and controls
365 lines (317 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#!/bin/bash
# =============================================================================
# Cluster HAT v2.0 Auto-Setup & Time Sync
# Version: 1.2
# Author: UntrustedTech
# GitHub: https://github.com/UntrustedTech/ClusterCtrl-Time-Sync
# =============================================================================
# -----------------------------
# Colors & Constants
# -----------------------------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'
BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
SPINNER='⣾⣽⣻⢿⡿⣟⣯⣷'
NODES=("p1" "p2" "p3" "p4")
# -----------------------------
# Global State
# -----------------------------
ERRORS=""
UPDATE_LOG="$HOME/.clusterctrl_update.log"
TODAY=$(date +%Y-%m-%d)
MASTER_NAME=$(hostname)
MASTER_MODEL=$(cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unknown")
MASTER_IP=$(ip route get 1.1.1.1 | awk '{print $7}' | head -1)
# -----------------------------
# UI: Spinner & Status
# -----------------------------
spinner() {
local pid=$1 msg=$2
local i=0 delay=0.12
while kill -0 $pid 2>/dev/null; do
printf "\r ${MAGENTA}${SPINNER:i++%8:1}${NC} %s" "$msg"
sleep $delay
done
printf "\r"
}
status() { echo -e " ${GREEN}Success $1${NC}"; }
warning() { echo -e " ${YELLOW}Warning $1${NC}"; }
error() { echo -e " ${RED}Failed $1${NC}"; }
log_error() { ERRORS+="$1\n"; }
# -----------------------------
# Safe Execution: APT & SSH
# -----------------------------
run_cmd() {
local cmd="$1" desc="$2" node="${3:-}"
local prefix="${node:+${node}: }"
local spinner_pid exit_code
printf " %s" "${prefix}${desc}"
eval "$cmd" &>/dev/null &
spinner_pid=$!
spinner $spinner_pid "$desc..."
wait $spinner_pid
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo -e "\r ${prefix}${GREEN}Success $desc${NC} "
return 0
else
echo -e "\r ${prefix}${YELLOW}Warning Retrying...${NC} "
sleep 2
eval "$cmd" &>/dev/null &
spinner_pid=$!
spinner $spinner_pid "$desc (retry)..."
wait $spinner_pid
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo -e "\r ${prefix}${GREEN}Success $desc (retry)${NC} "
return 0
else
echo -e "\r ${prefix}${RED}Failed $desc failed${NC} "
log_error "$node: $desc failed (code: $exit_code)"
return 1
fi
fi
}
run_apt() { run_cmd "$1" "$2"; }
run_ssh() { run_cmd "$1" "$2" "$3"; }
# -----------------------------
# Dependency: sshpass
# -----------------------------
ensure_sshpass() {
command -v sshpass &>/dev/null && return
print_header "Installing sshpass"
echo -e "${YELLOW}Required: sshpass (automated login)${NC}\n"
run_apt "sudo apt update" "Updating package list" || {
echo -e "\n${RED}APT update failed. Check internet or sources.${NC}"
exit 1
}
run_apt "sudo apt install -y sshpass" "Installing sshpass" || {
echo -e "\n${RED}Failed to install sshpass.${NC}"
exit 1
}
echo
}
# -----------------------------
# UI: Header
# -----------------------------
print_header() {
clear
echo -e "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${BLUE}║${NC} ${CYAN}Cluster HAT v2.0 Auto-Setup & Time Sync${NC} ${BOLD}${BLUE}║${NC}"
echo -e "${BOLD}${BLUE}║${NC} ${DIM}Author: UntrustedTech${NC} • ${DIM}v1.2${NC} • ${DIM}github.com/UntrustedTech${NC} ${BOLD}${BLUE}║${NC}"
echo -e "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}\n"
[[ -n "$1" ]] && echo -e "${BOLD}$1${NC}\n"
}
# -----------------------------
# 1. Master Node
# -----------------------------
setup_master() {
print_header "MASTER NODE"
echo -e " ${CYAN}Hardware:${NC} $MASTER_MODEL"
echo -e " ${CYAN}Hostname:${NC} $MASTER_NAME\n"
[[ -f "$UPDATE_LOG" && $(cat "$UPDATE_LOG") == "$TODAY" ]] && {
status "Already updated today"; return
}
read -p " $(echo -e "${YELLOW}Update master? (y/n): ${NC}")" -n 1 -r; echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { warning "Skipped"; return; }
run_apt "sudo apt update" "Updating master"
run_apt "sudo apt full-upgrade -y" "Upgrading master"
echo "$TODAY" > "$UPDATE_LOG"
status "Update logged"
read -p " $(echo -e "${YELLOW}Reboot? (y/n): ${NC}")" -n 1 -r; echo
[[ $REPLY =~ ^[Yy]$ ]] && { echo -e "\n${GREEN}Rebooting...${NC}"; sleep 3; sudo reboot; }
}
# -----------------------------
# 2. Network Check
# -----------------------------
check_network() {
print_header "NETWORK CHECK"
status "Master ($MASTER_NAME) online"
local offline=()
for node in "${NODES[@]}"; do
ping -c 1 -W 1 "$node.local" &>/dev/null && status "$node.local online" || {
error "$node.local offline"; offline+=("$node")
}
done
[[ ${#offline[@]} -eq 0 ]] && return
read -p " $(echo -e "${CYAN}Power on? (all/specific/none): ${NC}")" action
case "$action" in
all) clusterctrl on p1 p2 p3 p4 &>/dev/null & spinner $! "Powering all..." ;;
specific)
read -p " Nodes: " nodes
clusterctrl on $nodes &>/dev/null & spinner $! "Powering $nodes..."
;;
*) warning "Skipped"; return ;;
esac
echo -e "\n${CYAN}Waiting for boot...${NC}"
for node in "${offline[@]}"; do
echo -n " $node.local: "
while ! ping -c 1 -W 1 "$node.local" &>/dev/null; do
for c in ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏; do
printf "$MAGENTA$c$NC"; sleep 0.1; printf "\b"
done
done
echo -e "\r $node.local: ${GREEN}Success online${NC}"
done
}
# -----------------------------
# 3. SSH Credentials
# -----------------------------
get_credentials() {
print_header "SSH CREDENTIALS"
read -p " $(echo -e "${CYAN}Same for all? (y/n): ${NC}")" -n 1 -r; echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
read -p " Username: " USER
read -s -p " Password: " PASS; echo
USERS=("$USER" "$USER" "$USER" "$USER")
PASSES=("$PASS" "$PASS" "$PASS" "$PASS")
status "Shared credentials"
else
USERS=(); PASSES=()
for node in "${NODES[@]}"; do
read -p " Username for $node: " u
read -s -p " Password for $node: " p; echo
USERS+=("$u"); PASSES+=("$p")
done
status "Individual credentials"
fi
}
# -----------------------------
# 4. SSH Key Setup
# -----------------------------
setup_ssh_keys() {
print_header "SSH KEY SETUP"
local keys_ok=true
for i in {0..3}; do
! ssh -o BatchMode=yes -o ConnectTimeout=5 "${USERS[$i]}@${NODES[$i]}.local" "exit" 2>/dev/null && keys_ok=false
done
$keys_ok && { status "Passwordless ready"; return; }
read -p " $(echo -e "${CYAN}Set up keys? (y/n): ${NC}")" -n 1 -r; echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { warning "Skipped"; return; }
[[ ! -f ~/.ssh/id_rsa ]] && {
ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa &>/dev/null &
spinner $! "Generating key..."
}
echo -e "\n${GREEN}Distributing key...${NC}"
for i in {0..3}; do
local node=${NODES[$i]} user=${USERS[$i]} pass=${PASSES[$i]}
run_ssh "sshpass -p '$pass' ssh-copy-id -i ~/.ssh/id_rsa.pub '$user@$node.local'" "$node" "Copying key"
done
}
# -----------------------------
# 5. PARALLEL WORKER UPDATES
# -----------------------------
update_workers() {
print_header "PARALLEL WORKER UPDATES"
read -p " $(echo -e "${CYAN}Update all in parallel? (y/n): ${NC}")" -n 1 -r; echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { warning "Skipped"; return; }
local pids=()
for i in {0..3}; do
local node=${NODES[$i]} user=${USERS[$i]} pass=${PASSES[$i]}
local label="p$((i+1))"
local ssh_cmd="ssh $user@$node.local"
ssh -o BatchMode=yes "$user@$node.local" "exit" 2>/dev/null || ssh_cmd="sshpass -p '$pass' ssh $user@$node.local"
(
echo -e "${BOLD}STARTING $label.local${NC}"
run_ssh "$ssh_cmd 'sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove -y'" "$label" "Updating" || true
echo -n " $label: Rebooting"
$ssh_cmd "sudo reboot" &>/dev/null
while ! ping -c 1 -W 1 "$node.local" &>/dev/null; do
printf "."; sleep 2
done
echo -e " ${GREEN}Success back online${NC}"
) &
pids+=($!)
done
echo -e "\n${CYAN}Waiting for all updates...${NC}"
for pid in "${pids[@]}"; do
wait $pid && status "Update $pid done" || log_error "Update $pid failed"
done
}
# -----------------------------
# 6. PARALLEL NTP (CHRONY) SYNC
# -----------------------------
setup_chrony() {
print_header "PARALLEL NTP SYNC (CHRONY)"
# Master
run_apt "sudo apt install -y chrony" "Installing chrony (master)"
sudo sed -i 's/^pool/#pool/' /etc/chrony/chrony.conf 2>/dev/null
echo -e "local stratum 10\nallow 172.19.181.0/24" | sudo tee -a /etc/chrony/chrony.conf > /dev/null
sudo systemctl restart chrony &>/dev/null
status "Master NTP: $MASTER_IP"
# Workers (Parallel)
local pids=()
for i in {0..3}; do
local node=${NODES[$i]} user=${USERS[$i]} pass=${PASSES[$i]}
local label="p$((i+1))"
local ssh_cmd="ssh $user@$node.local"
ssh -o BatchMode=yes "$user@$node.local" "exit" 2>/dev/null || ssh_cmd="sshpass -p '$pass' ssh $user@$node.local"
(
run_ssh "$ssh_cmd 'sudo apt install -y chrony'" "$label" "Installing" || true
run_ssh "$ssh_cmd 'sudo sed -i \"s/^pool/#pool/\" /etc/chrony/chrony.conf 2>/dev/null'" "$label" "Disabling pools" || true
run_ssh "$ssh_cmd 'echo \"server $MASTER_IP iburst\" | sudo tee /etc/chrony/chrony.conf > /dev/null'" "$label" "Setting master" || true
run_ssh "$ssh_cmd 'sudo systemctl restart chrony'" "$label" "Restarting" || true
) &
pids+=($!)
done
echo -e "\n${CYAN}Waiting for NTP sync...${NC}"
for pid in "${pids[@]}"; do
wait $pid && status "NTP $pid done" || log_error "NTP $pid failed"
done
}
# -----------------------------
# 7. Final Health Check
# -----------------------------
final_check() {
print_header "HEALTH CHECK"
echo -e "${CYAN}Connectivity:${NC}"
status "Master ($MASTER_NAME) online"
for node in "${NODES[@]}"; do
ping -c 1 -W 1 "$node.local" &>/dev/null && status "$node.local online" || {
error "$node.local offline"; log_error "Final: $node offline"
}
done
echo -e "\n${CYAN}NTP Synchronization:${NC}"
for i in {0..3}; do
local node=${NODES[$i]} user=${USERS[$i]} pass=${PASSES[$i]}
local label="p$((i+1))"
local ssh_cmd="ssh $user@$node.local"
ssh -o BatchMode=yes "$user@$node.local" "exit" 2>/dev/null || ssh_cmd="sshpass -p '$pass' ssh $user@$node.local"
local sync=$($ssh_cmd "chronyc tracking 2>/dev/null | grep -i 'Reference ID' || echo 'none'" 2>/dev/null)
[[ "$sync" == *"(172.19.181.1)"* ]] && status "$label NTP synced" || {
error "$label NTP failed"; log_error "NTP sync failed: $label"
}
done
}
# -----------------------------
# 8. Summary
# -----------------------------
show_summary() {
print_header "SETUP COMPLETE"
if [ -z "$ERRORS" ]; then
echo -e "${GREEN}Cluster ready in record time!${NC}\n"
echo -e "${YELLOW}ClusterCtrl Time Sync v1.0${NC}"
echo -e "${DIM}by UntrustedTech • https://github.com/UntrustedTech${NC}"
else
echo -e "${RED}ISSUES:${NC}"
echo -e "${RED}$ERRORS${NC}\n"
echo -e "${YELLOW}Fix and re-run.${NC}"
fi
}
# -----------------------------
# Main
# -----------------------------
main() {
ensure_sshpass
setup_master
check_network
get_credentials
setup_ssh_keys
update_workers
setup_chrony
final_check
show_summary
}
main
# Copyright (c) 2025 UntrustedTech. All rights reserved.
# Unauthorized copying, modification, or distribution prohibited.