Offered during first-time install and under Addons -> Remote Access. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
582 lines
19 KiB
Bash
582 lines
19 KiB
Bash
#!/bin/bash
|
|
################################################################################
|
|
# menus/addon_remote_access.sh - "Remote Access" addon (VNC, WireGuard,
|
|
# Tailscale, Netbird) plus SSH Access (GitHub/Launchpad key import).
|
|
#
|
|
# 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
|
|
# doesn't own the layout of (apt packages, /etc/wireguard, real VPN
|
|
# client CLIs) - same risk class as CUPS. Only $WIREGUARD_DIR and
|
|
# $SYSTEMD_DIR (lib/config.sh) are parameterized, since those are the
|
|
# only paths this file itself writes to; every command (apt, systemctl,
|
|
# wg, tailscale, netbird, x11vnc) gets full stubbing in every test.
|
|
#
|
|
# Tailscale and Netbird install themselves via `curl -fsSL <vendor
|
|
# url> | sh` - the vendors' own documented install method, preserved as-
|
|
# is rather than redesigned. This is NEVER allowed to run for real in
|
|
# any test: curl itself is stubbed, not just sudo, so there is no path
|
|
# by which a test could reach the network.
|
|
#
|
|
# None of x11vnc/wg/tailscale/netbird are installed in a fresh
|
|
# environment, so their "not installed" detection is real/unstubbed and
|
|
# safe to exercise end-to-end - only the "install" actions need stubs.
|
|
#
|
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
|
################################################################################
|
|
|
|
# Also read by iso/offline/build-bundle.sh to bundle them for offline
|
|
# installs. (Tailscale/Netbird aren't bundled: they install from their
|
|
# vendors' scripts and need their vendors' servers to work anyway.)
|
|
VNC_APT_PACKAGES=(x11vnc)
|
|
WIREGUARD_APT_PACKAGES=(wireguard wireguard-tools)
|
|
|
|
remote_access_status() {
|
|
echo "SSH: $(ssh_status_line)"
|
|
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 "Tailscale: $(command -v tailscale &>/dev/null && echo "installed" || echo "not installed")"
|
|
echo "Netbird: $(command -v netbird &>/dev/null && echo "installed" || echo "not installed")"
|
|
}
|
|
|
|
remote_access_menu_builder() {
|
|
MENU_LABELS=("SSH Access (GitHub/Launchpad keys)" "VNC Remote Desktop" "WireGuard VPN" "Tailscale VPN" "Netbird VPN")
|
|
MENU_HANDLERS=(ssh_menu vnc_menu wireguard_menu tailscale_menu netbird_menu)
|
|
}
|
|
|
|
remote_access_menu() {
|
|
run_menu "REMOTE ACCESS" remote_access_menu_builder remote_access_status
|
|
}
|
|
|
|
################################################################################
|
|
# VNC (x11vnc)
|
|
################################################################################
|
|
|
|
vnc_status() {
|
|
if is_service_active x11vnc; then
|
|
echo "VNC: running - connect to $(get_ip_address):5900"
|
|
else
|
|
echo "VNC: not installed"
|
|
fi
|
|
}
|
|
|
|
vnc_menu_builder() {
|
|
if is_service_active x11vnc; then
|
|
MENU_LABELS=("Reconfigure password" "Uninstall")
|
|
MENU_HANDLERS=(action_vnc_change_password action_vnc_uninstall)
|
|
else
|
|
MENU_LABELS=("Install x11vnc")
|
|
MENU_HANDLERS=(action_vnc_install)
|
|
fi
|
|
}
|
|
|
|
vnc_menu() {
|
|
run_menu "VNC REMOTE DESKTOP" vnc_menu_builder vnc_status
|
|
}
|
|
|
|
action_vnc_install() {
|
|
echo
|
|
ask_yes_no "Install x11vnc?" "n" || { echo "Cancelled"; return; }
|
|
|
|
if ! run_with_offline_fallback sudo apt install -y "${VNC_APT_PACKAGES[@]}"; then
|
|
log_error "x11vnc installation failed"
|
|
return 1
|
|
fi
|
|
|
|
local vnc_pass
|
|
read -r -s -p "VNC password: " vnc_pass
|
|
echo
|
|
if [[ -z "$vnc_pass" ]]; then
|
|
log_error "No password provided - cancelled"
|
|
return 1
|
|
fi
|
|
|
|
sudo -u "$KIOSK_USER" mkdir -p "$KIOSK_HOME/.vnc"
|
|
sudo -u "$KIOSK_USER" x11vnc -storepasswd "$vnc_pass" "$KIOSK_HOME/.vnc/passwd"
|
|
|
|
sudo tee "$SYSTEMD_DIR/x11vnc.service" > /dev/null <<EOF
|
|
[Unit]
|
|
Description=x11vnc Remote Desktop
|
|
After=lightdm.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${KIOSK_USER}
|
|
ExecStart=/usr/bin/x11vnc -display :0 -auth guess -rfbauth ${KIOSK_HOME}/.vnc/passwd -forever -loop -noxdamage -repeat -shared
|
|
Restart=always
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
if enable_and_start_units x11vnc; then
|
|
sudo ufw allow 5900/tcp comment 'VNC' 2>/dev/null || true
|
|
log_success "VNC installed - connect to $(get_ip_address):5900"
|
|
else
|
|
log_warning "x11vnc installed but systemctl enable/start failed - check 'systemctl status x11vnc'"
|
|
fi
|
|
}
|
|
|
|
action_vnc_change_password() {
|
|
echo
|
|
local vnc_pass
|
|
read -r -s -p "New VNC password: " vnc_pass
|
|
echo
|
|
if [[ -z "$vnc_pass" ]]; then
|
|
log_error "No password provided - cancelled"
|
|
return 1
|
|
fi
|
|
|
|
sudo -u "$KIOSK_USER" x11vnc -storepasswd "$vnc_pass" "$KIOSK_HOME/.vnc/passwd"
|
|
if sudo systemctl restart x11vnc 2>/dev/null; then
|
|
log_success "VNC password updated"
|
|
else
|
|
log_warning "Password file updated, but restarting x11vnc failed - check 'systemctl status x11vnc'"
|
|
fi
|
|
}
|
|
|
|
action_vnc_uninstall() {
|
|
echo
|
|
ask_yes_no "Remove VNC?" "n" || { echo "Cancelled"; return; }
|
|
vnc_do_uninstall
|
|
}
|
|
|
|
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
|
|
vnc_do_uninstall() {
|
|
sudo systemctl stop x11vnc 2>/dev/null || true
|
|
sudo systemctl disable x11vnc 2>/dev/null || true
|
|
sudo rm -f "$SYSTEMD_DIR/x11vnc.service"
|
|
sudo apt remove -y x11vnc 2>/dev/null || true
|
|
log_success "VNC removed"
|
|
}
|
|
|
|
################################################################################
|
|
# WireGuard
|
|
################################################################################
|
|
|
|
wireguard_connected() {
|
|
command -v wg &>/dev/null && sudo wg show 2>/dev/null | grep -q interface
|
|
}
|
|
|
|
wireguard_status() {
|
|
if wireguard_connected; then
|
|
echo "WireGuard: connected"
|
|
sudo wg show 2>/dev/null | grep -E "interface:|endpoint:|allowed ips:" | sed 's/^/ /' || true
|
|
elif command -v wg &>/dev/null; then
|
|
echo "WireGuard: installed, not connected"
|
|
else
|
|
echo "WireGuard: not installed"
|
|
fi
|
|
}
|
|
|
|
wireguard_menu_builder() {
|
|
if wireguard_connected; then
|
|
MENU_LABELS=("Show full config" "Paste new config" "Uninstall")
|
|
MENU_HANDLERS=(action_wireguard_show_config action_wireguard_paste_config action_wireguard_uninstall)
|
|
elif command -v wg &>/dev/null; then
|
|
MENU_LABELS=("Paste config" "Uninstall")
|
|
MENU_HANDLERS=(action_wireguard_paste_config action_wireguard_uninstall)
|
|
else
|
|
MENU_LABELS=("Install WireGuard")
|
|
MENU_HANDLERS=(action_wireguard_install)
|
|
fi
|
|
}
|
|
|
|
wireguard_menu() {
|
|
run_menu "WIREGUARD VPN" wireguard_menu_builder wireguard_status
|
|
}
|
|
|
|
action_wireguard_install() {
|
|
echo
|
|
ask_yes_no "Install WireGuard?" "n" || { echo "Cancelled"; return; }
|
|
|
|
if ! run_with_offline_fallback sudo apt install -y "${WIREGUARD_APT_PACKAGES[@]}"; then
|
|
log_error "WireGuard installation failed"
|
|
return 1
|
|
fi
|
|
log_success "WireGuard installed"
|
|
|
|
echo
|
|
if ask_yes_no "Paste a config now?" "n"; then
|
|
action_wireguard_paste_config
|
|
fi
|
|
}
|
|
|
|
action_wireguard_show_config() {
|
|
echo
|
|
sudo wg show all
|
|
}
|
|
|
|
# Reads a WireGuard config from stdin until EOF (Ctrl+D on a real
|
|
# terminal) - same as the legacy addon. Writes to $WIREGUARD_DIR rather
|
|
# than a hardcoded /etc/wireguard, so tests can point it at scratch space
|
|
# and verify the written content without touching the real directory.
|
|
action_wireguard_paste_config() {
|
|
echo
|
|
echo "Paste your WireGuard config (Ctrl+D when done):"
|
|
local config
|
|
config=$(cat)
|
|
|
|
if [[ -z "$config" ]]; then
|
|
log_error "No config provided"
|
|
return 1
|
|
fi
|
|
|
|
local wg_name
|
|
wg_name=$(ask_text "Config name" "wg0")
|
|
|
|
sudo mkdir -p "$WIREGUARD_DIR"
|
|
echo "$config" | sudo tee "$WIREGUARD_DIR/${wg_name}.conf" > /dev/null
|
|
sudo chmod 600 "$WIREGUARD_DIR/${wg_name}.conf"
|
|
|
|
if enable_and_start_units "wg-quick@${wg_name}"; then
|
|
log_success "WireGuard configured: $wg_name"
|
|
else
|
|
log_warning "Config written, but systemctl enable/start failed - check 'systemctl status wg-quick@${wg_name}'"
|
|
fi
|
|
}
|
|
|
|
action_wireguard_uninstall() {
|
|
echo
|
|
ask_yes_no "Remove WireGuard?" "n" || { echo "Cancelled"; return; }
|
|
wireguard_do_uninstall
|
|
}
|
|
|
|
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
|
|
wireguard_do_uninstall() {
|
|
sudo systemctl stop 'wg-quick@*' 2>/dev/null || true
|
|
sudo systemctl disable 'wg-quick@*' 2>/dev/null || true
|
|
sudo apt remove -y wireguard wireguard-tools 2>/dev/null || true
|
|
log_success "WireGuard removed"
|
|
}
|
|
|
|
################################################################################
|
|
# Tailscale
|
|
################################################################################
|
|
|
|
tailscale_backend_state() {
|
|
tailscale status --json 2>/dev/null | jq -r '.BackendState // "unknown"' 2>/dev/null || echo "unknown"
|
|
}
|
|
|
|
tailscale_status() {
|
|
if ! command -v tailscale &>/dev/null; then
|
|
echo "Tailscale: not installed"
|
|
return
|
|
fi
|
|
|
|
if [[ "$(tailscale_backend_state)" == "Running" ]]; then
|
|
echo "Tailscale: connected"
|
|
echo " Hostname: $(tailscale status --json 2>/dev/null | jq -r '.Self.HostName // "unknown"')"
|
|
echo " IP: $(tailscale ip -4 2>/dev/null)"
|
|
else
|
|
echo "Tailscale: installed, not connected"
|
|
fi
|
|
}
|
|
|
|
tailscale_menu_builder() {
|
|
if command -v tailscale &>/dev/null; then
|
|
MENU_LABELS=("Connect (interactive)" "Connect with auth key" "Show status" "Uninstall")
|
|
MENU_HANDLERS=(action_tailscale_connect_interactive action_tailscale_connect_authkey action_tailscale_show_status action_tailscale_uninstall)
|
|
else
|
|
MENU_LABELS=("Install Tailscale")
|
|
MENU_HANDLERS=(action_tailscale_install)
|
|
fi
|
|
}
|
|
|
|
tailscale_menu() {
|
|
run_menu "TAILSCALE VPN" tailscale_menu_builder tailscale_status
|
|
}
|
|
|
|
action_tailscale_install() {
|
|
echo
|
|
ask_yes_no "Install Tailscale?" "n" || { echo "Cancelled"; return; }
|
|
|
|
if ! curl -fsSL https://tailscale.com/install.sh | sh; then
|
|
log_error "Tailscale installation failed"
|
|
return 1
|
|
fi
|
|
log_success "Tailscale installed"
|
|
|
|
echo
|
|
echo "Options:"
|
|
echo " 1. Connect now (interactive)"
|
|
echo " 2. Connect with auth key"
|
|
echo " 3. Connect later"
|
|
local choice
|
|
choice=$(ask_integer "Choose" "3" 1 3)
|
|
case "$choice" in
|
|
1) action_tailscale_connect_interactive ;;
|
|
2) action_tailscale_connect_authkey ;;
|
|
esac
|
|
}
|
|
|
|
action_tailscale_connect_interactive() {
|
|
echo
|
|
if sudo tailscale up; then
|
|
log_success "Tailscale connected"
|
|
else
|
|
log_error "Tailscale connection failed"
|
|
fi
|
|
}
|
|
|
|
action_tailscale_connect_authkey() {
|
|
echo
|
|
echo "Get an auth key from: https://login.tailscale.com/admin/settings/keys"
|
|
local authkey
|
|
read -r -p "Enter auth key: " authkey
|
|
if [[ -z "$authkey" ]]; then
|
|
echo "Cancelled"
|
|
return
|
|
fi
|
|
|
|
if sudo tailscale up --authkey="$authkey"; then
|
|
log_success "Tailscale connected"
|
|
else
|
|
log_error "Tailscale connection failed"
|
|
fi
|
|
}
|
|
|
|
action_tailscale_show_status() {
|
|
echo
|
|
tailscale status
|
|
}
|
|
|
|
action_tailscale_uninstall() {
|
|
echo
|
|
ask_yes_no "Remove Tailscale?" "n" || { echo "Cancelled"; return; }
|
|
tailscale_do_uninstall
|
|
}
|
|
|
|
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
|
|
tailscale_do_uninstall() {
|
|
sudo tailscale down 2>/dev/null || true
|
|
sudo apt remove -y tailscale 2>/dev/null || true
|
|
log_success "Tailscale removed"
|
|
}
|
|
|
|
################################################################################
|
|
# Netbird
|
|
################################################################################
|
|
|
|
netbird_connected() {
|
|
[[ "$(netbird status 2>/dev/null | grep "Status:" | awk '{print $2}')" == "Connected" ]]
|
|
}
|
|
|
|
netbird_status() {
|
|
if ! command -v netbird &>/dev/null; then
|
|
echo "Netbird: not installed"
|
|
return
|
|
fi
|
|
|
|
if netbird_connected; then
|
|
echo "Netbird: connected"
|
|
netbird status 2>/dev/null | grep -E "NetBird IP:|Public key:" | sed 's/^/ /' || true
|
|
else
|
|
echo "Netbird: installed, not connected"
|
|
fi
|
|
}
|
|
|
|
netbird_menu_builder() {
|
|
if command -v netbird &>/dev/null; then
|
|
MENU_LABELS=("Connect with setup key" "Show status" "Uninstall")
|
|
MENU_HANDLERS=(action_netbird_connect action_netbird_show_status action_netbird_uninstall)
|
|
else
|
|
MENU_LABELS=("Install Netbird")
|
|
MENU_HANDLERS=(action_netbird_install)
|
|
fi
|
|
}
|
|
|
|
netbird_menu() {
|
|
run_menu "NETBIRD VPN" netbird_menu_builder netbird_status
|
|
}
|
|
|
|
action_netbird_install() {
|
|
echo
|
|
ask_yes_no "Install Netbird?" "n" || { echo "Cancelled"; return; }
|
|
|
|
if ! curl -fsSL https://pkgs.netbird.io/install.sh | sh; then
|
|
log_error "Netbird installation failed"
|
|
return 1
|
|
fi
|
|
log_success "Netbird installed"
|
|
|
|
echo
|
|
if ask_yes_no "Connect with a setup key now?" "n"; then
|
|
action_netbird_connect
|
|
fi
|
|
}
|
|
|
|
action_netbird_connect() {
|
|
echo
|
|
echo "Get a setup key from the Netbird dashboard"
|
|
local setup_key
|
|
read -r -p "Enter setup key: " setup_key
|
|
if [[ -z "$setup_key" ]]; then
|
|
echo "Cancelled"
|
|
return
|
|
fi
|
|
|
|
if sudo netbird up --setup-key "$setup_key" --allow-server-ssh; then
|
|
log_success "Netbird connected"
|
|
else
|
|
log_error "Netbird connection failed"
|
|
fi
|
|
}
|
|
|
|
action_netbird_show_status() {
|
|
echo
|
|
netbird status
|
|
}
|
|
|
|
action_netbird_uninstall() {
|
|
echo
|
|
ask_yes_no "Remove Netbird?" "n" || { echo "Cancelled"; return; }
|
|
netbird_do_uninstall
|
|
}
|
|
|
|
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
|
|
netbird_do_uninstall() {
|
|
sudo netbird down 2>/dev/null || true
|
|
sudo apt remove -y netbird 2>/dev/null || true
|
|
log_success "Netbird removed"
|
|
}
|
|
|
|
################################################################################
|
|
# SSH Access - openssh-server plus public keys imported from GitHub or
|
|
# Launchpad with ssh-import-id (the same "Import SSH identity" choice
|
|
# 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.
|
|
################################################################################
|
|
|
|
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
|
|
sudo apt install -y openssh-server ssh-import-id || { log_error "Could not install openssh-server"; return 1; }
|
|
sudo systemctl enable --now ssh || { log_error "Could not start the SSH server"; return 1; }
|
|
# provision_configure_firewall already allows ssh; harmless if repeated.
|
|
if command -v ufw &>/dev/null && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
|
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
|
|
while true; do
|
|
echo
|
|
echo "Import keys from:"
|
|
echo " 1. GitHub"
|
|
echo " 2. Launchpad"
|
|
echo " 3. Done"
|
|
choice=$(ask_integer "Choose" "3" 1 3)
|
|
case "$choice" in
|
|
1) action_ssh_import_github || true ;;
|
|
2) action_ssh_import_launchpad || true ;;
|
|
*) break ;;
|
|
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
|
|
}
|