Valentin Lab
4 years ago
2 changed files with 358 additions and 9 deletions
-
24README.org
-
343bin/0km
@ -0,0 +1,343 @@ |
|||
#!/bin/bash |
|||
|
|||
|
|||
. /etc/shlib |
|||
|
|||
include common |
|||
include parse |
|||
include cmdline |
|||
include config |
|||
|
|||
|
|||
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && SOURCED=true |
|||
|
|||
version=0.1 |
|||
desc='Manage 0k related installs' |
|||
help="" |
|||
|
|||
|
|||
is-port-open() { |
|||
local host="$1" port="$2" timeout=5 |
|||
start="$SECONDS" |
|||
debug "Testing if $host's port $2 is open ..." |
|||
while true; do |
|||
timeout 1 bash -c "</dev/tcp/${host}/${port}" >/dev/null 2>&1 && break |
|||
sleep 0.2 |
|||
if [ "$((SECONDS - start))" -gt "$timeout" ]; then |
|||
return 1 |
|||
fi |
|||
done |
|||
} |
|||
|
|||
resolve() { |
|||
local ent hostname="$1" |
|||
debug "Resolving $1 ..." |
|||
if ent=$(getent ahosts "$hostname"); then |
|||
ent=$(echo "$ent" | egrep ^"[0-9]+.[0-9]+.[0-9]+.[0-9]+\s+" | \ |
|||
head -n 1 | awk '{ print $1 }') |
|||
debug " .. resolved $1 to $ent." |
|||
echo "$ent" |
|||
else |
|||
debug " .. couldn't resolve $1." |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
|
|||
set_errlvl() { return "${1:-1}"; } |
|||
|
|||
export master_pid=$$ |
|||
ssh:open() { |
|||
local hostname ssh_cmd ssh_options |
|||
ssh_cmd=(ssh) |
|||
ssh_options=() |
|||
while [ "$#" != 0 ]; do |
|||
case "$1" in |
|||
"--stdin-password") |
|||
ssh_cmd=(sshpass "${ssh_cmd[@]}") |
|||
;; |
|||
-o) |
|||
ssh_options+=("$1" "$2") |
|||
shift |
|||
;; |
|||
*) |
|||
[ -z "$hostname" ] && hostname="$1" || { |
|||
err "Surnumerous positional argument '$1'. Expecting only hostname." |
|||
return 1 |
|||
} |
|||
;; |
|||
esac |
|||
shift |
|||
done |
|||
"${ssh_cmd[@]}" -o ControlPath=/tmp/ssh-control-master-${master_pid} \ |
|||
-o ControlMaster=auto -o ControlPersist=900 \ |
|||
-o ConnectTimeout=5 -o "StrictHostKeyChecking=no" \ |
|||
"${ssh_options[@]}" \ |
|||
"$hostname" "$@" -- true || { |
|||
err Failed: ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \ |
|||
-o ControlMaster=auto -o ControlPersist=900 \ |
|||
"$hostname" "$@" -- true |
|||
return 1 |
|||
} |
|||
trap_add EXIT,INT 'ssh:quit "$hostname"' |
|||
} |
|||
|
|||
|
|||
ssh:open-try() { |
|||
local opts hostnames |
|||
opts=() |
|||
hostnames=() |
|||
while [ "$#" != 0 ]; do |
|||
case "$1" in |
|||
-o) |
|||
opts+=("$1" "$2") |
|||
shift |
|||
;; |
|||
*) |
|||
hostnames+=("$1") |
|||
;; |
|||
esac |
|||
shift |
|||
done |
|||
password='' |
|||
for host in "${hostnames[@]}"; do |
|||
debug "Trying $host with publickey." |
|||
ssh:open -o PreferredAuthentications=publickey \ |
|||
"${opts[@]}" \ |
|||
"$host" >/dev/null 2>&1 && { |
|||
echo "$host"$'\n'"$password"$'\n' |
|||
return 0 |
|||
} |
|||
debug " .. failed connecting to $host with publickey." |
|||
done |
|||
local times=0 password |
|||
while [ "$((++times))" -le 3 ]; do |
|||
read -sp "$HOST's password: " password |
|||
errlvl="$?" |
|||
echo >&2 |
|||
if [ "$errlvl" -gt 0 ]; then |
|||
exit 1 |
|||
fi |
|||
for host in "${hostnames[@]}"; do |
|||
debug "Trying $host with password ($times/3)" |
|||
echo "$password" | ssh:open -o PreferredAuthentications=password \ |
|||
--stdin-password \ |
|||
"${opts[@]}" \ |
|||
"$host" >/dev/null 2>&1 && { |
|||
echo "$host"$'\n'"$password"$'\n' |
|||
return 0 |
|||
} |
|||
debug " .. failed connecting to $host with password." |
|||
done |
|||
err "login failed. Try again... ($((times+1))/3)" |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
|
|||
ssh:run() { |
|||
local hostname="$1" ssh_options cmd |
|||
shift |
|||
|
|||
ssh_options=() |
|||
cmd=() |
|||
while [ "$#" != 0 ]; do |
|||
case "$1" in |
|||
"--") |
|||
shift |
|||
cmd+=("$@") |
|||
break |
|||
;; |
|||
*) |
|||
ssh_options+=("$1") |
|||
;; |
|||
esac |
|||
shift |
|||
done |
|||
#echo "$DARKCYAN$hostname$NORMAL $WHITE\$$NORMAL" "$@" |
|||
debug "Running cmd: ${cmd[@]}" |
|||
for arg in "${cmd[@]}"; do |
|||
debug "$arg" |
|||
done |
|||
{ |
|||
{ |
|||
ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \ |
|||
-o ControlMaster=auto -o ControlPersist=900 \ |
|||
-o "StrictHostKeyChecking=no" \ |
|||
"$hostname" "${ssh_options[@]}" -- "${cmd[@]}" |
|||
} 3>&1 1>&2 2>&3 | sed -r "s/^/$DARKCYAN$hostname$NORMAL $DARKRED\!$NORMAL /g" |
|||
set_errlvl "${PIPESTATUS[0]}" |
|||
} 3>&1 1>&2 2>&3 |
|||
} |
|||
|
|||
ssh:quit() { |
|||
local hostname="$1" |
|||
shift |
|||
ssh -o ControlPath=/tmp/ssh-control-master-${master_pid} \ |
|||
-o ControlMaster=auto -o ControlPersist=900 -O exit \ |
|||
"$hostname" 2>/dev/null |
|||
} |
|||
|
|||
|
|||
is_ovh_domain_name() { |
|||
local domain="$1" |
|||
|
|||
[[ "$domain" == *.ovh.net ]] && return 0 |
|||
[[ "$domain" == "ns"*".ip-"*".eu" ]] && return 0 |
|||
return 1 |
|||
} |
|||
|
|||
is_ovh_hostname() { |
|||
local domain="$1" |
|||
|
|||
[[ "$domain" =~ ^vps-[0-9a-f]*$ ]] && return 0 |
|||
[[ "$domain" =~ ^vps[0-9]*$ ]] && return 0 |
|||
return 1 |
|||
} |
|||
|
|||
|
|||
[ "$SOURCED" ] && return 0 |
|||
|
|||
## |
|||
## Command line processing |
|||
## |
|||
|
|||
|
|||
cmdline.spec.gnu |
|||
cmdline.spec.reporting |
|||
|
|||
cmdline.spec.gnu vps-setup |
|||
|
|||
cmdline.spec::cmd:vps-setup:run() { |
|||
|
|||
: :posarg: HOST 'Target host to check/fix ssh-access' |
|||
|
|||
depends sshpass shyaml |
|||
|
|||
KEY_PATH="ssh-access.public-keys" |
|||
local keys=$(config get-value -y "ssh-access.public-keys") || true |
|||
if [ -z "$keys" ]; then |
|||
err "No ssh publickeys configured in config file." |
|||
echo " Looking for keys in ${WHITE}${KEY_PATH}${NORMAL}" \ |
|||
"in config file." >&2 |
|||
config:exists --message 2>&1 | prefix " " |
|||
if [ "${PIPESTATUS[0]}" == "0" ]; then |
|||
echo " Config file found in $(config:filename)" |
|||
fi |
|||
return 1 |
|||
fi |
|||
local tkey=$(e "$keys" | shyaml get-type) |
|||
if [ "$tkey" != "sequence" ]; then |
|||
err "Value type of ${WHITE}${KEY_PATH}${NORMAL} unexpected (is $tkey, expecting sequence)." |
|||
echo " Check content of $(config:filename), and make sure to use a sequence." >&2 |
|||
return 1 |
|||
fi |
|||
|
|||
local IP NAME keys host_pass_connected |
|||
if ! IP=$(resolve "$HOST"); then |
|||
err "'$HOST' name unresolvable." |
|||
exit 1 |
|||
fi |
|||
|
|||
NAME="$HOST" |
|||
if [ "$IP" != "$HOST" ]; then |
|||
NAME="$HOST ($IP)" |
|||
fi |
|||
|
|||
if ! is-port-open "$IP" "22"; then |
|||
err "$NAME unreachable or port 22 closed." |
|||
exit 1 |
|||
fi |
|||
debug "Host $IP's port 22 is open." |
|||
if ! host_pass_connected=$(ssh:open-try \ |
|||
{root,debian}@"$HOST"); then |
|||
err "Could not connect to {root,debian}@$HOST with publickey nor password." |
|||
exit 1 |
|||
fi |
|||
|
|||
read-0a host password <<<"$host_pass_connected" |
|||
|
|||
sudo_if_necessary= |
|||
if [ "$password" -o "${host%%@*}" != "root" ]; then |
|||
if ! ssh:run "$host" -- sudo -n true >/dev/null 2>&1; then |
|||
err "Couldn't do a password-less sudo from $host." |
|||
echo " This is not yet supported." |
|||
exit 1 |
|||
else |
|||
sudo_if_necessary=sudo |
|||
fi |
|||
fi |
|||
|
|||
Section Checking access |
|||
while read-0 key; do |
|||
prefix="${key%% *}" |
|||
if [ "$prefix" != "ssh-rsa" ]; then |
|||
err "Unsupported key:"$'\n'"$key" |
|||
return 1 |
|||
fi |
|||
label="${key##* }" |
|||
Elt "considering adding key ${DARKYELLOW}$label${NORMAL}" |
|||
dest="/root/.ssh/authorized_keys" |
|||
if ssh:run "$host" -- $sudo_if_necessary grep "\"$key\"" "$dest" >/dev/null 2>&1; then |
|||
print_info "already present" |
|||
print_status noop |
|||
Feed |
|||
else |
|||
if echo "$key" | ssh:run "$host" -- $sudo_if_necessary tee -a "$dest" >/dev/null; then |
|||
print_info added |
|||
else |
|||
echo |
|||
Feedback failure |
|||
return 1 |
|||
fi |
|||
Feedback success |
|||
fi |
|||
done < <(e "$keys" | shyaml get-values-0) |
|||
|
|||
Section Checking ovh hostname file |
|||
Elt "Checking /etc/ovh-hostname" |
|||
if ! ssh:run "$host" -- $sudo_if_necessary [ -e "/etc/ovh-hostname" ]; then |
|||
print_info "creating" |
|||
ssh:run "$host" -- $sudo_if_necessary cp /etc/hostname /etc/ovh-hostname |
|||
ovhname=$(ssh:run "$host" -- $sudo_if_necessary cat /etc/ovh-hostname) |
|||
Elt "Checking /etc/ovh-hostname: $ovhname" |
|||
Feedback || return 1 |
|||
else |
|||
ovhname=$(ssh:run "$host" -- $sudo_if_necessary cat /etc/ovh-hostname) |
|||
Elt "Checking /etc/ovh-hostname: $ovhname" |
|||
print_info "already present" |
|||
print_status noop |
|||
Feed |
|||
fi |
|||
|
|||
if ! is_ovh_domain_name "$HOST" && ! str_is_ipv4 "$HOST"; then |
|||
Section Checking hostname |
|||
Elt "Checking /etc/hostname..." |
|||
if [ "$old" != "$HOST" ]; then |
|||
old="$(ssh:run "$host" -- $sudo_if_necessary cat /etc/hostname)" |
|||
Elt "Hostname is '$old'" |
|||
if is_ovh_hostname "$old"; then |
|||
Elt "Hostname '$old' --> '$HOST'" |
|||
print_info "creating" |
|||
echo "$HOST" | ssh:run "$host" -- $sudo_if_necessary tee /etc/hostname >/dev/null && |
|||
ssh:run "$host" -- $sudo_if_necessary hostname "$HOST" |
|||
Feedback || return 1 |
|||
else |
|||
print_info "not changing" |
|||
print_status noop |
|||
Feed |
|||
fi |
|||
|
|||
else |
|||
print_info "already set" |
|||
print_status noop |
|||
Feed |
|||
fi |
|||
else |
|||
info "Not changing domain as '$HOST' doesn't seem to be final domain." |
|||
fi |
|||
|
|||
} |
|||
|
|||
|
|||
cmdline::parse "$@" |
Write
Preview
Loading…
Cancel
Save
Reference in new issue