Subversion Repositories ALCASAR

Rev

Rev 3322 | Details | Compare with Previous | Last modification | View Log

Rev Author Line No. Line
3294 rexy 1
#!/bin/bash
2
 
3
#########################
4
## ALCASAR replication ##
5
##       connect       ##
6
#########################
7
# The script is designed to connect instance to a remote ALCASAR.
8
 
9
# Constants
10
readonly ALCASAR_PWD="/root/ALCASAR-passwords.txt"
11
readonly LOCALHOST="127.0.0.1"
12
readonly DB_PORT=3306
13
 
14
# Dynamically generated constants
15
DB_ROOT_PWD="$(grep db_root "$ALCASAR_PWD" | cut -d '=' -f 2-)"
3318 rexy 16
REPL_DB_USER_PWD="$(grep db_replication_pwd "$ALCASAR_PWD" | cut -d '=' -f 2-)"
3294 rexy 17
readonly DB_ROOT_PWD;
3318 rexy 18
readonly REPL_DB_USER=db_replication
19
readonly REPL_DB_USER_PWD;
3294 rexy 20
 
21
# Variables
22
remote_name=""
23
remote_addr=""
24
remote_ssh_port=""
25
remote_ssh_user=""
26
remote_db_user=""
27
remote_db_pwd=""
28
remote_role=""
29
bind_port=""
30
 
31
 
32
# Revert modifications already made while adding remote
33
# $1: previous error code
34
abort() {
35
	error_code="$1"
3318 rexy 36
	echo "Abort script with $error_code error code"
3294 rexy 37
	# Revert FW
38
	tmp_disable_outbound_connection
3318 rexy 39
	# Remove REPLICA
40
	del_remote_as_primary
3294 rexy 41
	# Delete SSH tunnel service file
42
	service_file="replication-$remote_name.service"
43
	service_path="/etc/systemd/system/$service_file"
44
	[ -f "$service_file" ] && rm "$service_file"
3322 rexy 45
	sed -i "/^REPLICATION_TO=/s/$ip:$port,//" /usr/local/etc/alcasar.conf
3294 rexy 46
	return "$error_code"
47
}
48
 
49
# Add remote as primary
50
add_remote_as_primary() {
51
	echo "Adding '$remote_name' as primary..."
52
	exec_query "CHANGE MASTER '$remote_name' TO MASTER_HOST='$LOCALHOST', MASTER_PORT=$bind_port, MASTER_USER='$remote_db_user', MASTER_PASSWORD='$remote_db_pwd', MASTER_USE_GTID=replica_pos"
53
}
54
 
3318 rexy 55
# Delete remote as primary
56
del_remote_as_primary() {
57
	echo "Removing '$remote_name' as primary..."
58
	exec_query "RESET REPLICA '$remote_name' ALL"
59
}
60
 
3294 rexy 61
# Verify hostname and IP are not already used by other primary servers
62
check_availability() {
63
	attributes="$(/usr/local/bin/alcasar-replication-list.sh --all)"
64
 
65
	# Check for remote name availability
66
	echo "$attributes" | grep -q "$remote_name"
67
	if [ "$?" -eq 0 ]
68
	then
69
		echo "error: name '$remote_name' already used" >&2
70
		return 15
71
	fi
72
 
73
	# Check for remote IP availability
74
	echo "$attributes" | grep -q "$remote_addr"
75
	if [ "$?" -eq 0 ] && [ -n "$remote_addr" ]
76
	then
77
		echo "error: address '$remote_addr' already used" >&2
78
		return 16
79
	fi
80
 
81
	# Check for binding port availability
82
	echo "$attributes" | grep -q "$bind_port"
83
	if [ "$?" -eq 0 ] && [ -n "$bind_port" ]
84
	then
85
		echo "error: binding port '$bind_port' already used" >&2
86
		return 17
87
	fi
88
}
89
 
90
# Check script args
91
# $@: script args
92
check_args() {
93
	# Parse args
94
	args="$(getopt --longoptions "to-primary,to-secondary,name:,address:,port:,user:,db-user:,db-password:,bind-port:,help" --options "n:,a:,p:,u:,h" -- "$@")"
95
 
96
	# Reset script args list
97
	eval set -- "$args"
98
 
99
	# Print help
100
	if [ "$#" -eq 1 ]
101
	then
102
		usage
103
		return 1
104
	fi
105
 
106
	# Loop over all args
107
	while true
108
	do
109
		case "$1" in
110
			--to-primary)
111
				echo "Remote role: primary"
112
				remote_role="primary"
113
				;;
114
			--to-secondary)
115
				echo "Remote role: secondary"
116
				remote_role="secondary"
117
				;;
118
			--name | -n)
119
				echo "Remote name: $2"
120
				remote_name="$2"
121
				shift
122
				;;
123
			--address | -a)
124
				echo "Remote address: $2"
125
				remote_addr="$2"
126
				shift
127
				;;
128
			--port | -p)
129
				echo "Remote SSH port: $2"
130
				remote_ssh_port="$2"
131
				shift
132
				;;
133
			--user | -u)
134
				echo "Remote user: $2"
135
				remote_ssh_user="$2"
136
				shift
137
				;;
138
			--db-user)
139
				echo "Remote database user: $2"
140
				remote_db_user="$2"
141
				shift
142
				;;
143
			--db-password)
144
				echo "Remote database user password: $2"
145
				remote_db_pwd="$2"
146
				shift
147
				;;
148
			--bind-port)
149
				echo "Local binding port: $2"
150
				bind_port="$2"
151
				shift
152
				;;
153
			--help | -h)
154
				usage
155
				return 2
156
				;;
157
			--)
158
				# End of args
159
				break
160
				;;
161
			*)
162
				echo "error: unknown $1" >&2
163
				return 3
164
				break
165
				;;
166
		esac
167
		shift
168
	done
169
 
170
	# All fields must be filled
171
	case "$remote_role" in
172
		primary)
173
			# Needed args to be passed
174
			if [ -z "$remote_name"     ] ||
175
			   [ -z "$remote_addr"     ] ||
176
			   [ -z "$remote_ssh_port" ] ||
177
			   [ -z "$remote_ssh_user" ] ||
178
			   [ -z "$remote_db_user"  ] ||
179
			   [ -z "$remote_db_pwd"   ]
180
			then
181
				echo "error: some args are missing" >&2
182
				return 4
183
			fi
184
			;;
185
		secondary)
186
			# Needed args to be passed
187
			if [ -z "$remote_name"     ] ||
188
			   [ -z "$bind_port"       ] ||
189
			   [ -z "$remote_db_user"  ] ||
190
			   [ -z "$remote_db_pwd"   ]
191
			then
192
				echo "error: some args are missing" >&2
193
				return 5
194
			fi
195
			;;
196
		*)
197
			echo "error: remote role is missing" >&2
198
			return 6
199
			;;
200
	esac
201
}
202
 
3321 rexy 203
# Test connection to remote system and remote database before creating SSH tunnel.
3294 rexy 204
check_primary_credentials() {
205
	# Test SSH credentials
206
	if ! /usr/bin/ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=no -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" exit
207
	then
208
		echo "error: cannot SSH with '$remote_ssh_user' to $remote_addr:$remote_ssh_port" >&2
209
		echo "hint: have you deployed root pubkey on the remote?"
210
		return 7
211
	fi
3314 rexy 212
	echo "Successfully connected with '$remote_ssh_user' to primary ($remote_addr:$remote_ssh_port)"
3294 rexy 213
 
3321 rexy 214
	# Retrieve remote db_replication pwd
3324 rexy 215
	if ! /usr/bin/scp -q -P "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr":/home/replication/local-db_replication-pwd.txt /home/replication/primary-db_replication-pwd.txt
3319 rexy 216
	then
3321 rexy 217
		echo "error: cannot retrieve remote primary db_user pwd" >&2
3319 rexy 218
		return 7
219
	fi
3324 rexy 220
	remote_db_pwd=$(cat /home/replication/primary-db_replication-pwd.txt)
221
	rm /home/replication/primary-db_replication-pwd.txt
3319 rexy 222
	echo "Successfully retrieve remote primary db_user pass"
223
 
3294 rexy 224
	# Test database credentials
225
	if ! /usr/bin/ssh -q -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" -- /usr/bin/mariadb --user="$remote_db_user" --password="$remote_db_pwd" --execute="QUIT"
226
	then
227
		echo "error: cannot connect with '$remote_db_user' to remote database" >&2
228
		return 8
229
	fi
230
	echo "Successfully connected with '$remote_db_user' to remote database"
231
}
232
 
233
# Test connection to remote database through SSH tunnel
234
check_secondary_credentials() {
235
	if ! /usr/bin/mariadb --host="$LOCALHOST" --port="$bind_port" --user="$remote_db_user" --password="$remote_db_pwd" --execute="QUIT"
236
	then
237
		echo "error: cannot connect with '$remote_db_user' to remote database" >&2
238
		return 9
239
	fi
3314 rexy 240
	echo "Successfully connected with '$remote_db_user' to remote secondary database on port $bind_port"
3294 rexy 241
}
242
 
3321 rexy 243
# Add a systemd unit to create SSH tunnel to remote primary
3294 rexy 244
create_ssh_tunnel() {
245
	# Find a common binding port
246
	find_common_free_port || return 11
247
	service_file="replication-$remote_name.service"
248
	service_path="/etc/systemd/system/$service_file"
249
 
250
	# Write down SSH tunnel service file
251
	echo "[Unit]
252
Description=Setup a secure bidirectional tunnel with $remote_name
253
After=network.target
254
 
255
[Service]
256
ExecStart=/usr/bin/ssh -NT -4 -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -p $remote_ssh_port -L $bind_port:localhost:$DB_PORT -R $bind_port:localhost:$DB_PORT $remote_ssh_user@$remote_addr
257
RestartSec=5
258
Restart=always
259
 
260
[Install]
261
WantedBy=multi-user.target
262
" > "$service_path"
263
 
264
	# Start and enable SSH tunnel
265
	echo "Enabling $remote_name service..."
266
	/usr/bin/systemctl enable "$service_file"
267
	echo "Starting $remote_name service..."
268
	/usr/bin/systemctl start "$service_file"
269
}
270
 
271
# Execute SQL query
272
# $1: query
273
# $2: user (default: root)
274
# $3: password (default: root pwd)
275
# $4: host (default: localhost)
276
# $5: port (default: 3306)
277
exec_query() {
278
	# Check args
279
	if [ $# -lt 1 ]
280
	then
281
		echo "usage: $0 \"SQL query\" <DB user> <DB password> <SQL server address> <SQL server port>"
282
		return 12
283
	fi
284
	# Execute the query
285
	/usr/bin/mariadb --host="${4:-localhost}" --port="${5:-$DB_PORT}" --user="${2:-root}" --password="${3:-$DB_ROOT_PWD}" --execute="$1"
286
}
287
 
3321 rexy 288
# create, retrieve & import remote primary database
3319 rexy 289
retrieve_primary_database() {
3318 rexy 290
	# creation of a fresh dump
291
	if ! /usr/bin/ssh -q -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" -- "sudo /usr/local/bin/alcasar-mariadb.sh -d &&  cp -f /var/Save/base/\$(ls -t /var/Save/base/ | head -n 1) /home/replication/alcasar-users-database-primary.sql.gz"
292
	then
293
		echo "error: cannot create a fresh primary database dump" >&2
294
		return 18
295
	fi
296
	echo "Primary database dump created"
3319 rexy 297
	if ! scp -q -P "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr":alcasar-users-database-primary.sql.gz /tmp/
3318 rexy 298
	then
299
		echo "error: cannot retrieve localy the fresh primary database dump" >&2
300
		return 19
301
	fi
302
	echo "Primary database dump locally copied"
3319 rexy 303
	alcasar-mariadb.sh --import /tmp/alcasar-users-database-primary.sql.gz
3318 rexy 304
	rm -f /tmp/alcasar-users-database-primary.sql.gz
3324 rexy 305
	/usr/bin/ssh -q -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" -- "rm -f /home/replication/alcasar-users-database-primary.sql.gz"
3318 rexy 306
}
307
 
3322 rexy 308
add_symmetric_replication() {
3324 rexy 309
#	hostname="$(/usr/local/bin/alcasar-replication-ssh-keys-management.sh --show-pubkey | cut -d' ' -f3 | cut -d'@' -f2 |cut -d'.' -f1)"
3321 rexy 310
	active_db_port="$(/usr/local/bin/alcasar-replication-list.sh --all |grep Master_Port|cut -d" " -f2)"
3324 rexy 311
	echo "adding data for symetrical replication (--bind-port=$active_db_port)"
312
	if ! /usr/bin/scp -q -P "$remote_ssh_port" /home/replication/local-db_replication-pwd.txt "$remote_ssh_user"@"$remote_addr":/home/replication/"$active_db_port"-db_replication-pwd.txt
3321 rexy 313
	then
3324 rexy 314
		echo "error: cannot copy local db_replication-pwd to primary" >&2
3321 rexy 315
		return 7
316
	fi
3324 rexy 317
#	if ! /usr/bin/ssh -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" -- "sudo /usr/local/bin/alcasar-replication-add.sh --to-secondary --name=$hostname --bind-port=$active_db_port --db-user=db_replication --db-password=`cat /home/replication/"$hostname"-"$active_db_port"-db_replication-pwd.txt`"
318
#	then
319
#		echo "error: cannot add symmetric replication" >&2
320
#		return 7
321
#	fi
322
#	echo "Successfully add symmetric replication"
3321 rexy 323
}
324
 
3294 rexy 325
find_common_free_port() {
326
	remote_busy_ports_file=/tmp/remote_busy_ports
327
	local_busy_ports_file=/tmp/local_busy_ports
328
	common_busy_ports_file=/tmp/common_busy_ports
329
	ports_list_file=/tmp/ports_list
330
	free_ports_file=/tmp/free_ports
331
 
332
	# Get remote busy ports
333
	/usr/bin/ssh -q -p "$remote_ssh_port" "$remote_ssh_user"@"$remote_addr" -- /usr/sbin/ss --listening --numeric --ipv4 | tail -n +2 | cut -d ':' -f 2 | cut -d ' ' -f 1 | sort -u > "$remote_busy_ports_file"
334
	if [ "$?" -ne 0 ]
335
	then
336
		echo "error: cannot SSH with '$remote_ssh_user' to $remote_addr:$remote_ssh_port" >&2
337
		return 13
338
	fi
339
 
340
	# Get local busy ports
341
	/usr/sbin/ss --listening --numeric --ipv4 | tail -n +2 | cut -d ':' -f 2 | cut -d ' ' -f 1 | sort -u > "$local_busy_ports_file"
342
 
343
	# List ports range from system
344
	read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
345
 
346
	# Write ports in a file
347
	echo -n > "$ports_list_file"
348
	for port in $(seq "$lower_port" "$upper_port")
349
	do
350
		echo "$port" >> "$ports_list_file"
351
	done
352
 
353
	# Merge busy ports
354
	/usr/bin/cat "$remote_busy_ports_file" "$local_busy_ports_file" > "$common_busy_ports_file"
355
	# Sorts ports
356
	/usr/bin/sort -u -o "$common_busy_ports_file" "$common_busy_ports_file"
357
	/usr/bin/sort -o "$ports_list_file" "$ports_list_file"
358
	# Substract available ports in common
359
	/usr/bin/comm --check-order -3 "$ports_list_file" "$common_busy_ports_file" | cut -f 1 | sed "/^$/d" > "$free_ports_file"
360
 
361
	# Verify at least one free port have been found
362
	if [ ! -s "$free_ports_file" ]
363
	then
364
		echo "error: no common port found for binding" >&2
365
		return 14
366
	fi
367
 
368
	# Pick the first common port
369
	bind_port="$(head -n 1 "$free_ports_file")"
370
	echo "Both machines binded on port $bind_port->$DB_PORT"
371
	echo "Please take note about the binding port for primary's connection setup."
372
 
373
	# Remove tmp files
374
	rm "$remote_busy_ports_file"
375
	rm "$local_busy_ports_file"
376
	rm "$common_busy_ports_file"
377
	rm "$ports_list_file"
378
	rm "$free_ports_file"
379
}
380
 
381
# Allow outbound connection for testing connection
382
tmp_allow_outbound_connection() {
383
	/usr/sbin/iptables -A OUTPUT -d "$remote_addr" -p tcp --dport "$remote_ssh_port" -j ACCEPT
384
}
385
 
3313 rexy 386
# Disable outbound connection which that was used to test test connection
3294 rexy 387
tmp_disable_outbound_connection() {
388
	/usr/sbin/iptables -D OUTPUT -d "$remote_addr" -p tcp --dport "$remote_ssh_port" -j ACCEPT
389
}
390
 
391
# Print help message
392
usage() {
393
	echo "usage: $0 ROLE OPTIONS"
394
	echo
395
	echo "ROLE"
396
	echo "	--to-primary"
397
	echo "		remote server is a primary"
398
	echo "	--to-secondary"
399
	echo "		remote server is a secondary"
400
	echo
401
	echo "OPTIONS"
402
	echo "	--name=NAME, -n NAME"
403
	echo "		friendly name given to the remote"
404
	echo "	--address=ADDRESS, -a ADDRESS"
405
	echo "		remote IP address"
406
	echo "	--port=PORT, -p PORT"
407
	echo "		remote SSH port"
408
	echo "	--user=USER, -u USER"
409
	echo "		remote SSH user"
410
	echo "	--db-user=USER"
411
	echo "		remote database replication user"
412
	echo "	--db-password=PASSWORD"
413
	echo "		remote database replication user password"
414
	echo "	--bind-port=PORT"
415
	echo "		used from primary: local port binded to remote database. It has been displayed during secondary connection to primary"
416
	echo "	--help, -h"
417
	echo "		print this help message"
418
	echo
419
	echo "ROLE OPTIONS"
420
	echo "	--to-primary: needs name, address, port, user, db-user, db-password"
421
	echo "	--to-secondary: needs name, bind-port, db-user, db-password"
422
}
423
 
424
# Main
425
check_args "$@" || exit
426
 
427
check_availability || exit
428
 
429
case "$remote_role" in
430
	primary)
431
		tmp_allow_outbound_connection || abort "$?" || exit
432
		check_primary_credentials || abort "$?" || exit
433
		create_ssh_tunnel || abort "$?" || exit
3319 rexy 434
		retrieve_primary_database || abort "$?" || exit
3314 rexy 435
		add_remote_as_primary || abort "$?" || exit
436
		echo -n "Allowing outbound connection to remote SSH "
437
		# Get remote IP and port from its name
438
		port="$(grep "ExecStart" "$service_path" | cut -d ' ' -f 9)"
439
		ip="$(grep "ExecStart" "$service_path" | cut -d ' ' -f 14 | cut -d '@' -f2)"
3322 rexy 440
		echo "$ip:$port"
3314 rexy 441
		/usr/bin/sed -i -E "/^REPLICATION_TO=/s/=(.*)/=\1$ip:$port,/" /usr/local/etc/alcasar.conf
442
		/usr/local/bin/alcasar-iptables.sh
3322 rexy 443
		add_symmetric_replication || abort "$?" || exit
3294 rexy 444
		;;
445
	secondary)
446
		check_secondary_credentials || exit
3314 rexy 447
		add_remote_as_primary || abort "$?" || exit # In a federation, primary/secondary is define by SSH role (sshd-server=primary; ssh-client=secondary)
3294 rexy 448
		;;
449
esac
450
 
3313 rexy 451
# Set Netfilter
452
 
3314 rexy 453