Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cb4a661f9 | ||
|
|
eb7bc14a9c |
+1
-1
@@ -350,7 +350,7 @@ provision_configure_kiosk_settings() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${KIOSK_UNATTENDED:-0}" != "1" ]]; then
|
if [[ "${KIOSK_UNATTENDED:-0}" != "1" ]]; then
|
||||||
remote_ssh_first_time_setup
|
ssh_first_time_setup
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
+142
-104
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
# menus/addon_remote_access.sh - "Remote Access" addon (VNC, WireGuard,
|
# menus/addon_remote_access.sh - "Remote Access" addon (VNC, WireGuard,
|
||||||
# Tailscale, Netbird).
|
# Tailscale, Netbird) plus SSH Access (GitHub/Launchpad key import).
|
||||||
#
|
#
|
||||||
# Third Addon migrated, and the biggest so far in scope (4 sub-areas).
|
# Third Addon migrated, and the biggest so far in scope (4 sub-areas).
|
||||||
# All four genuinely mutate real system state at fixed paths this project
|
# All four genuinely mutate real system state at fixed paths this project
|
||||||
@@ -31,6 +31,7 @@ VNC_APT_PACKAGES=(x11vnc)
|
|||||||
WIREGUARD_APT_PACKAGES=(wireguard wireguard-tools)
|
WIREGUARD_APT_PACKAGES=(wireguard wireguard-tools)
|
||||||
|
|
||||||
remote_access_status() {
|
remote_access_status() {
|
||||||
|
echo "SSH: $(ssh_status_line)"
|
||||||
echo "VNC: $(is_service_active x11vnc && echo "running" || echo "not installed")"
|
echo "VNC: $(is_service_active x11vnc && echo "running" || echo "not installed")"
|
||||||
echo "WireGuard: $(wireguard_connected && echo "connected" || (command -v wg &>/dev/null && echo "installed, not connected" || echo "not installed"))"
|
echo "WireGuard: $(wireguard_connected && echo "connected" || (command -v wg &>/dev/null && echo "installed, not connected" || echo "not installed"))"
|
||||||
echo "Tailscale: $(command -v tailscale &>/dev/null && echo "installed" || echo "not installed")"
|
echo "Tailscale: $(command -v tailscale &>/dev/null && echo "installed" || echo "not installed")"
|
||||||
@@ -38,8 +39,8 @@ remote_access_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remote_access_menu_builder() {
|
remote_access_menu_builder() {
|
||||||
MENU_LABELS=("VNC Remote Desktop" "WireGuard VPN" "Tailscale VPN" "Netbird VPN")
|
MENU_LABELS=("SSH Access (GitHub/Launchpad keys)" "VNC Remote Desktop" "WireGuard VPN" "Tailscale VPN" "Netbird VPN")
|
||||||
MENU_HANDLERS=(vnc_menu wireguard_menu tailscale_menu netbird_menu)
|
MENU_HANDLERS=(ssh_menu vnc_menu wireguard_menu tailscale_menu netbird_menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
remote_access_menu() {
|
remote_access_menu() {
|
||||||
@@ -264,7 +265,6 @@ tailscale_status() {
|
|||||||
|
|
||||||
if [[ "$(tailscale_backend_state)" == "Running" ]]; then
|
if [[ "$(tailscale_backend_state)" == "Running" ]]; then
|
||||||
echo "Tailscale: connected"
|
echo "Tailscale: connected"
|
||||||
echo " SSH: $(tailscale_ssh_enabled && echo "enabled" || echo "disabled")"
|
|
||||||
echo " Hostname: $(tailscale status --json 2>/dev/null | jq -r '.Self.HostName // "unknown"')"
|
echo " Hostname: $(tailscale status --json 2>/dev/null | jq -r '.Self.HostName // "unknown"')"
|
||||||
echo " IP: $(tailscale ip -4 2>/dev/null)"
|
echo " IP: $(tailscale ip -4 2>/dev/null)"
|
||||||
else
|
else
|
||||||
@@ -272,40 +272,10 @@ tailscale_status() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Tailscale SSH: tailscaled itself answers SSH on this machine's tailnet
|
|
||||||
# address, authenticated by tailnet identity - no keys to manage. Which
|
|
||||||
# users may log in as whom is set by the tailnet's ACL "ssh" rules in the
|
|
||||||
# Tailscale admin console (https://login.tailscale.com/admin/acls).
|
|
||||||
tailscale_ssh_enabled() {
|
|
||||||
[[ "$(tailscale debug prefs 2>/dev/null | jq -r '.RunSSH // false' 2>/dev/null)" == "true" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Echoes "--ssh" when the user wants Tailscale SSH, nothing otherwise.
|
|
||||||
tailscale_ask_ssh_flag() {
|
|
||||||
if ask_yes_no "Enable Tailscale SSH (log in over the tailnet, no SSH keys)?" "y" >&2; then
|
|
||||||
echo "--ssh"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
action_tailscale_toggle_ssh() {
|
|
||||||
echo
|
|
||||||
if [[ "$(tailscale_backend_state)" != "Running" ]]; then
|
|
||||||
log_error "Connect Tailscale first (Connect, above)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if tailscale_ssh_enabled; then
|
|
||||||
ask_yes_no "Tailscale SSH is ENABLED. Disable it?" "n" || { echo "Cancelled"; return; }
|
|
||||||
sudo tailscale set --ssh=false && log_success "Tailscale SSH disabled"
|
|
||||||
else
|
|
||||||
sudo tailscale set --ssh=true && log_success "Tailscale SSH enabled"
|
|
||||||
echo "Allow it in your tailnet ACL (\"ssh\" rules): https://login.tailscale.com/admin/acls"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
tailscale_menu_builder() {
|
tailscale_menu_builder() {
|
||||||
if command -v tailscale &>/dev/null; then
|
if command -v tailscale &>/dev/null; then
|
||||||
MENU_LABELS=("Connect (interactive)" "Connect with auth key" "Enable/disable Tailscale SSH" "Show status" "Uninstall")
|
MENU_LABELS=("Connect (interactive)" "Connect with auth key" "Show status" "Uninstall")
|
||||||
MENU_HANDLERS=(action_tailscale_connect_interactive action_tailscale_connect_authkey action_tailscale_toggle_ssh action_tailscale_show_status action_tailscale_uninstall)
|
MENU_HANDLERS=(action_tailscale_connect_interactive action_tailscale_connect_authkey action_tailscale_show_status action_tailscale_uninstall)
|
||||||
else
|
else
|
||||||
MENU_LABELS=("Install Tailscale")
|
MENU_LABELS=("Install Tailscale")
|
||||||
MENU_HANDLERS=(action_tailscale_install)
|
MENU_HANDLERS=(action_tailscale_install)
|
||||||
@@ -316,13 +286,9 @@ tailscale_menu() {
|
|||||||
run_menu "TAILSCALE VPN" tailscale_menu_builder tailscale_status
|
run_menu "TAILSCALE VPN" tailscale_menu_builder tailscale_status
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1 = "noconfirm" skips the "Install?" question (first-time install
|
|
||||||
# has already asked).
|
|
||||||
action_tailscale_install() {
|
action_tailscale_install() {
|
||||||
echo
|
echo
|
||||||
if [[ "${1:-}" != "noconfirm" ]]; then
|
|
||||||
ask_yes_no "Install Tailscale?" "n" || { echo "Cancelled"; return; }
|
ask_yes_no "Install Tailscale?" "n" || { echo "Cancelled"; return; }
|
||||||
fi
|
|
||||||
|
|
||||||
if ! curl -fsSL https://tailscale.com/install.sh | sh; then
|
if ! curl -fsSL https://tailscale.com/install.sh | sh; then
|
||||||
log_error "Tailscale installation failed"
|
log_error "Tailscale installation failed"
|
||||||
@@ -345,10 +311,7 @@ action_tailscale_install() {
|
|||||||
|
|
||||||
action_tailscale_connect_interactive() {
|
action_tailscale_connect_interactive() {
|
||||||
echo
|
echo
|
||||||
local ssh_flag
|
if sudo tailscale up; then
|
||||||
ssh_flag=$(tailscale_ask_ssh_flag)
|
|
||||||
# shellcheck disable=SC2086 # ssh_flag is empty or exactly "--ssh"
|
|
||||||
if sudo tailscale up $ssh_flag; then
|
|
||||||
log_success "Tailscale connected"
|
log_success "Tailscale connected"
|
||||||
else
|
else
|
||||||
log_error "Tailscale connection failed"
|
log_error "Tailscale connection failed"
|
||||||
@@ -365,10 +328,7 @@ action_tailscale_connect_authkey() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local ssh_flag
|
if sudo tailscale up --authkey="$authkey"; then
|
||||||
ssh_flag=$(tailscale_ask_ssh_flag)
|
|
||||||
# shellcheck disable=SC2086 # ssh_flag is empty or exactly "--ssh"
|
|
||||||
if sudo tailscale up --authkey="$authkey" $ssh_flag; then
|
|
||||||
log_success "Tailscale connected"
|
log_success "Tailscale connected"
|
||||||
else
|
else
|
||||||
log_error "Tailscale connection failed"
|
log_error "Tailscale connection failed"
|
||||||
@@ -409,48 +369,16 @@ netbird_status() {
|
|||||||
|
|
||||||
if netbird_connected; then
|
if netbird_connected; then
|
||||||
echo "Netbird: connected"
|
echo "Netbird: connected"
|
||||||
echo " SSH: $(netbird_ssh_enabled && echo "enabled" || echo "disabled")"
|
|
||||||
netbird status 2>/dev/null | grep -E "NetBird IP:|Public key:" | sed 's/^/ /' || true
|
netbird status 2>/dev/null | grep -E "NetBird IP:|Public key:" | sed 's/^/ /' || true
|
||||||
else
|
else
|
||||||
echo "Netbird: installed, not connected"
|
echo "Netbird: installed, not connected"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Netbird's built-in SSH server listens on port 22022 on the Netbird
|
|
||||||
# interface (wt0). ufw's default-deny-incoming (lib/provision.sh) would
|
|
||||||
# block it, so open that port on wt0 only.
|
|
||||||
netbird_ssh_enabled() {
|
|
||||||
netbird status 2>/dev/null | grep -qiE '^SSH Server:.*(enabled|true)' ||
|
|
||||||
sudo -n grep -qE '"ServerSSHAllowed": *true' /var/lib/netbird/default.json 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
netbird_open_ssh_firewall() {
|
|
||||||
if command -v ufw &>/dev/null && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
|
||||||
sudo ufw allow in on wt0 to any port 22022 proto tcp >/dev/null &&
|
|
||||||
log_info "Firewall: allowed Netbird SSH (22022) on wt0 only"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
action_netbird_toggle_ssh() {
|
|
||||||
echo
|
|
||||||
if ! netbird_connected; then
|
|
||||||
log_error "Connect Netbird first (Connect, above)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if netbird_ssh_enabled; then
|
|
||||||
ask_yes_no "Netbird SSH is ENABLED. Disable it?" "n" || { echo "Cancelled"; return; }
|
|
||||||
sudo netbird up --allow-server-ssh=false && log_success "Netbird SSH disabled"
|
|
||||||
else
|
|
||||||
sudo netbird up --allow-server-ssh && log_success "Netbird SSH enabled" &&
|
|
||||||
netbird_open_ssh_firewall
|
|
||||||
echo "Connect from another Netbird peer: ssh <user>@<netbird-ip> (port 22022 if your client needs it)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
netbird_menu_builder() {
|
netbird_menu_builder() {
|
||||||
if command -v netbird &>/dev/null; then
|
if command -v netbird &>/dev/null; then
|
||||||
MENU_LABELS=("Connect with setup key" "Enable/disable Netbird SSH" "Show status" "Uninstall")
|
MENU_LABELS=("Connect with setup key" "Show status" "Uninstall")
|
||||||
MENU_HANDLERS=(action_netbird_connect action_netbird_toggle_ssh action_netbird_show_status action_netbird_uninstall)
|
MENU_HANDLERS=(action_netbird_connect action_netbird_show_status action_netbird_uninstall)
|
||||||
else
|
else
|
||||||
MENU_LABELS=("Install Netbird")
|
MENU_LABELS=("Install Netbird")
|
||||||
MENU_HANDLERS=(action_netbird_install)
|
MENU_HANDLERS=(action_netbird_install)
|
||||||
@@ -461,13 +389,9 @@ netbird_menu() {
|
|||||||
run_menu "NETBIRD VPN" netbird_menu_builder netbird_status
|
run_menu "NETBIRD VPN" netbird_menu_builder netbird_status
|
||||||
}
|
}
|
||||||
|
|
||||||
# $1 = "noconfirm" skips the "Install?" question (first-time install
|
|
||||||
# has already asked).
|
|
||||||
action_netbird_install() {
|
action_netbird_install() {
|
||||||
echo
|
echo
|
||||||
if [[ "${1:-}" != "noconfirm" ]]; then
|
|
||||||
ask_yes_no "Install Netbird?" "n" || { echo "Cancelled"; return; }
|
ask_yes_no "Install Netbird?" "n" || { echo "Cancelled"; return; }
|
||||||
fi
|
|
||||||
|
|
||||||
if ! curl -fsSL https://pkgs.netbird.io/install.sh | sh; then
|
if ! curl -fsSL https://pkgs.netbird.io/install.sh | sh; then
|
||||||
log_error "Netbird installation failed"
|
log_error "Netbird installation failed"
|
||||||
@@ -491,13 +415,8 @@ action_netbird_connect() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local ssh_flag=()
|
if sudo netbird up --setup-key "$setup_key" --allow-server-ssh; then
|
||||||
if ask_yes_no "Enable Netbird SSH (log in over the Netbird network)?" "y"; then
|
|
||||||
ssh_flag=(--allow-server-ssh)
|
|
||||||
fi
|
|
||||||
if sudo netbird up --setup-key "$setup_key" "${ssh_flag[@]}"; then
|
|
||||||
log_success "Netbird connected"
|
log_success "Netbird connected"
|
||||||
if [[ ${#ssh_flag[@]} -gt 0 ]]; then netbird_open_ssh_firewall; fi
|
|
||||||
else
|
else
|
||||||
log_error "Netbird connection failed"
|
log_error "Netbird connection failed"
|
||||||
fi
|
fi
|
||||||
@@ -522,22 +441,141 @@ netbird_do_uninstall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# First-time install hook (called from lib/provision.sh): offer to set up
|
# SSH Access - openssh-server plus public keys imported from GitHub or
|
||||||
# one of the two mesh-VPN providers with SSH enabled, so the kiosk is
|
# Launchpad with ssh-import-id (the same "Import SSH identity" choice
|
||||||
# reachable remotely right after install.
|
# Ubuntu's own installer offers). Keys go to the admin user running this
|
||||||
|
# menu (~/.ssh/authorized_keys), never to the restricted `kiosk` user.
|
||||||
|
# The key sets published by those services are fetched over HTTPS and
|
||||||
|
# are public data, so no secret is handled here.
|
||||||
################################################################################
|
################################################################################
|
||||||
remote_ssh_first_time_setup() {
|
|
||||||
|
SSH_KEYS_ONLY_CONF="${SSHD_CONFIG_DIR:-/etc/ssh/sshd_config.d}/10-kiosk-keys-only.conf"
|
||||||
|
SSH_AUTH_KEYS="${SSH_AUTH_KEYS:-$HOME/.ssh/authorized_keys}"
|
||||||
|
|
||||||
|
ssh_key_count() {
|
||||||
|
[[ -f "$SSH_AUTH_KEYS" ]] && grep -cE '^(ssh-|ecdsa-|sk-)' "$SSH_AUTH_KEYS" || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_status_line() {
|
||||||
|
local state="not installed"
|
||||||
|
if is_service_active ssh || is_service_active sshd; then
|
||||||
|
state="running"
|
||||||
|
elif dpkg -s openssh-server &>/dev/null; then
|
||||||
|
state="installed, not running"
|
||||||
|
fi
|
||||||
|
local pw="password login on"
|
||||||
|
[[ -f "$SSH_KEYS_ONLY_CONF" ]] && pw="keys only"
|
||||||
|
echo "$state, $(ssh_key_count) key(s), $pw"
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_status() {
|
||||||
|
echo "SSH: $(ssh_status_line)"
|
||||||
|
echo " Keys go to: $SSH_AUTH_KEYS"
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_menu_builder() {
|
||||||
|
MENU_LABELS=("Enable SSH server" "Import keys from GitHub" "Import keys from Launchpad" "List authorized keys" "Require keys (disable password login)" "Allow password login again")
|
||||||
|
MENU_HANDLERS=(action_ssh_enable action_ssh_import_github action_ssh_import_launchpad action_ssh_list_keys action_ssh_keys_only action_ssh_allow_passwords)
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_menu() {
|
||||||
|
run_menu "SSH ACCESS" ssh_menu_builder ssh_status
|
||||||
|
}
|
||||||
|
|
||||||
|
action_ssh_enable() {
|
||||||
echo
|
echo
|
||||||
echo "Remote SSH access over a mesh VPN lets you manage the kiosk from"
|
sudo apt install -y openssh-server ssh-import-id || { log_error "Could not install openssh-server"; return 1; }
|
||||||
echo "anywhere with no port forwarding:"
|
sudo systemctl enable --now ssh || { log_error "Could not start the SSH server"; return 1; }
|
||||||
echo " 1. Tailscale (with Tailscale SSH)"
|
# provision_configure_firewall already allows ssh; harmless if repeated.
|
||||||
echo " 2. Netbird (with Netbird SSH)"
|
if command -v ufw &>/dev/null && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||||
echo " 3. Skip (configure later: Addons -> Remote Access)"
|
sudo ufw allow ssh >/dev/null
|
||||||
|
fi
|
||||||
|
log_success "SSH server enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 = "gh" or "lp", $2 = label for messages.
|
||||||
|
ssh_import_from() {
|
||||||
|
local prefix="$1" label="$2" user
|
||||||
|
echo
|
||||||
|
if ! command -v ssh-import-id &>/dev/null; then
|
||||||
|
sudo apt install -y ssh-import-id || { log_error "Could not install ssh-import-id"; return 1; }
|
||||||
|
fi
|
||||||
|
read -r -p "$label username (blank to cancel): " user
|
||||||
|
[[ -z "$user" ]] && { echo "Cancelled"; return; }
|
||||||
|
if [[ ! "$user" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
||||||
|
log_error "That doesn't look like a valid $label username"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ssh-import-id "${prefix}:${user}"; then
|
||||||
|
log_success "Imported $label keys for $user ($(ssh_key_count) key(s) now authorized)"
|
||||||
|
else
|
||||||
|
log_error "Import failed - check the username and that this machine can reach $label"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_ssh_import_github() { ssh_import_from gh "GitHub"; }
|
||||||
|
action_ssh_import_launchpad() { ssh_import_from lp "Launchpad"; }
|
||||||
|
|
||||||
|
action_ssh_list_keys() {
|
||||||
|
echo
|
||||||
|
if [[ "$(ssh_key_count)" == "0" ]]; then
|
||||||
|
echo "No authorized keys for $USER yet."
|
||||||
|
else
|
||||||
|
ssh-keygen -lf "$SSH_AUTH_KEYS"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_ssh_keys_only() {
|
||||||
|
echo
|
||||||
|
if [[ "$(ssh_key_count)" == "0" ]]; then
|
||||||
|
log_error "No authorized keys yet - import some first, or you'd lock yourself out"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "Log in from another machine with a key BEFORE doing this; password"
|
||||||
|
echo "logins over SSH will stop working."
|
||||||
|
ask_yes_no "Disable SSH password login?" "n" || { echo "Cancelled"; return; }
|
||||||
|
printf 'PasswordAuthentication no\nKbdInteractiveAuthentication no\n' |
|
||||||
|
sudo install -D -m 644 /dev/stdin "$SSH_KEYS_ONLY_CONF"
|
||||||
|
if sudo sshd -t && sudo systemctl reload ssh; then
|
||||||
|
log_success "SSH now requires a key"
|
||||||
|
else
|
||||||
|
sudo rm -f "$SSH_KEYS_ONLY_CONF"
|
||||||
|
log_error "sshd rejected the config - reverted"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_ssh_allow_passwords() {
|
||||||
|
echo
|
||||||
|
sudo rm -f "$SSH_KEYS_ONLY_CONF"
|
||||||
|
sudo systemctl reload ssh 2>/dev/null || true
|
||||||
|
log_success "SSH password login allowed again"
|
||||||
|
}
|
||||||
|
|
||||||
|
# First-time install hook (called from lib/provision.sh).
|
||||||
|
ssh_first_time_setup() {
|
||||||
|
echo
|
||||||
|
echo "SSH lets you manage the kiosk remotely. Ubuntu can import your"
|
||||||
|
echo "public keys from GitHub or Launchpad so you can log in with no password."
|
||||||
|
ask_yes_no "Enable SSH and import keys now?" "y" || { log_info "Configure later: Addons -> Remote Access -> SSH Access"; return; }
|
||||||
|
action_ssh_enable || return 0
|
||||||
local choice
|
local choice
|
||||||
|
while true; do
|
||||||
|
echo
|
||||||
|
echo "Import keys from:"
|
||||||
|
echo " 1. GitHub"
|
||||||
|
echo " 2. Launchpad"
|
||||||
|
echo " 3. Done"
|
||||||
choice=$(ask_integer "Choose" "3" 1 3)
|
choice=$(ask_integer "Choose" "3" 1 3)
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) action_tailscale_install noconfirm ;;
|
1) action_ssh_import_github || true ;;
|
||||||
2) action_netbird_install noconfirm ;;
|
2) action_ssh_import_launchpad || true ;;
|
||||||
*) log_info "Configure later: Addons -> Remote Access" ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
|
done
|
||||||
|
if [[ "$(ssh_key_count)" != "0" ]]; then
|
||||||
|
echo "Tip: Remote Access -> SSH Access can switch SSH to key-only login"
|
||||||
|
echo "once you've confirmed a key works."
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user