Extract SSH key import out of base.sh into a standalone, re-runnable service

Was only ever runnable once, buried inside base.sh's required-setup flow —
no way to re-run just this step for a box that already went through base
setup but needs another admin's key added later, or (the immediate case)
a home box for services/vpn-data-mount.sh that only needs this one step.

services/ssh-key-import.sh holds the real logic now (GitHub/Launchpad
import via ssh-import-id, optional password-auth lockdown); base.sh's
_base_setup_ssh chains into it the same way services/asterisk.sh chains
into security-dashboard/pstn-trunk, with a degraded (no import, just
ensures the SSH server itself is running) fallback for a pure standalone
`sudo bash base.sh` run with no sibling files sourced. Independently
runnable via `sudo ./setup.sh ssh-key-import` or `sudo bash
services/ssh-key-import.sh`, and shows up in the whiptail menu under
extras alongside ssh-config. Marked as never showing [installed] in
is_installed()/install_count(), same as ssh-config — it's a repeatable
management action, not a thing with an install state.
This commit is contained in:
Claude
2026-08-10 18:10:27 +00:00
parent 0e42de1cda
commit 8c5be53950
4 changed files with 159 additions and 45 deletions
+2 -1
View File
@@ -172,7 +172,7 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`.
| `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` |
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
| `gaming` | `drum-rhythm-game`, `js99er`, `kyber-launcher`, `kyber-server`, `minecraft`, `wolf`, `wolf-pair` |
| `extras` | `kdeconnect`, `silent-send`, `ssh-config`, `sync-cc` |
| `extras` | `kdeconnect`, `silent-send`, `ssh-config`, `ssh-key-import` (import SSH public keys from GitHub/Launchpad, optionally lock down password auth — same step base.sh's required setup runs, re-runnable on its own), `sync-cc` |
| `backup` | `backup` — complete recovery: entire `~/docker/<service>/` for every service via Kopia (Minecraft: flush+snap, no downtime; others: stop/snap/start for DB consistency), optional offsite mirror (`kopia repository sync-to`), plus `dr_bringup.sh` — unattended restore-everything-and-start for standing up a cold spare box; `borg-backup` — same coverage via Borg (chunk dedup, SSH remote repos, Borgmatic/Vorta compatible); `gaming-backup` — frequent game-save snapshots (Minecraft world data, emulator saves, Steam — no downtime, run hourly) |
Run `./setup.sh --list` to see descriptions.
@@ -263,6 +263,7 @@ extras
kdeconnect
silent-send
ssh-config
ssh-key-import
sync-cc
backup
+17 -43
View File
@@ -144,54 +144,28 @@ _base_setup_nvidia_gpu() {
}
_base_setup_ssh() {
log_info "Configuring SSH server..."
# The real logic lives in services/ssh-key-import.sh now — pulled out so
# it can be re-run on its own later (another admin's key, a home box
# that only needs this one step, ...) instead of only ever running once
# as part of this whole required-setup flow. That file keeps its own
# register_service call and stays independently selectable; this just
# chains into it, same pattern services/asterisk.sh uses for
# security-dashboard/pstn-trunk.
if declare -F install_ssh-key-import >/dev/null 2>&1; then
install_ssh-key-import
return
fi
# Standalone `sudo bash base.sh` with no sibling services/*.sh sourced —
# degrade to just getting the SSH server itself running, skip the
# GitHub/Launchpad import convenience (needs the sibling file's fuller
# standalone stubs, not worth duplicating here for this rare a path).
log_info "Configuring SSH server..."
if ! dpkg -l openssh-server &>/dev/null; then
run_cmd apt-get install -y openssh-server
fi
run_cmd systemctl enable --now ssh
# Import SSH public keys from GitHub and/or Launchpad.
local GH_USER="" LP_USER="" _keys_imported=false
prompt_text "GitHub username to import SSH keys from (blank to skip):" "" GH_USER
if [ -n "$GH_USER" ]; then
if ssh-import-id "gh:$GH_USER"; then
log_success "Imported SSH keys from GitHub: $GH_USER"
_keys_imported=true
else
log_warning "Could not import keys from GitHub: $GH_USER"
fi
fi
prompt_text "Launchpad username to import SSH keys from (blank to skip):" "" LP_USER
if [ -n "$LP_USER" ]; then
if ssh-import-id "lp:$LP_USER"; then
log_success "Imported SSH keys from Launchpad: $LP_USER"
_keys_imported=true
else
log_warning "Could not import keys from Launchpad: $LP_USER"
fi
fi
# Only offer to disable password auth if at least one key was imported.
if [ "$_keys_imported" = true ]; then
local DISABLE_PW=""
prompt_yn "Disable SSH password authentication (key login only)? (y/n):" "y" DISABLE_PW
if [[ "$DISABLE_PW" =~ ^[Yy]$ ]]; then
sed -i \
-e 's/^#*\s*PasswordAuthentication\s.*/PasswordAuthentication no/' \
-e 's/^#*\s*KbdInteractiveAuthentication\s.*/KbdInteractiveAuthentication no/' \
/etc/ssh/sshd_config
# Ubuntu 22.04+ may also have a drop-in that re-enables password auth.
local _dropin="/etc/ssh/sshd_config.d/50-cloud-init.conf"
if [ -f "$_dropin" ]; then
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' "$_dropin"
fi
systemctl restart ssh
log_success "SSH password authentication disabled — key login only"
fi
fi
log_info "Run services/ssh-key-import.sh (or the full repo's wizard) to import keys from GitHub/Launchpad."
}
_base_setup_netbird() {
+138
View File
@@ -0,0 +1,138 @@
#!/bin/bash
# services/ssh-key-import.sh — import SSH public keys from GitHub/Launchpad
# for passwordless login, and optionally lock down password auth.
# Part of the modular post-install system (sourced by setup.sh).
#
# Can also be run standalone on any machine:
# sudo bash ssh-key-import.sh
#
# Extracted out of services/base.sh's required setup (which still chains
# into this) so it can be re-run on its own — e.g. a box that already went
# through base setup but needs another admin's key added later, or a home
# box (see services/vpn-data-mount.sh) that just needs this one step and
# nothing else base.sh does.
#
# What ssh-import-id actually does: fetches the PUBLIC keys listed at
# https://github.com/<user>.keys (or https://launchpad.net/~<user>/+sshkeys
# for Launchpad — Canonical/Ubuntu's own code-hosting + bug-tracker
# platform, the "other option") over HTTPS and appends them to this box's
# ~/.ssh/authorized_keys. That's the same information already publicly
# visible on that profile page — nothing secret is transmitted, and no
# PRIVATE key ever leaves the machine that generated it. This box only
# gains the ability to authenticate INBOUND connections from whoever holds
# the matching private key; it does NOT gain that person's identity for
# OUTBOUND connections (e.g. this box still can't clone a private GitHub
# repo just because it imported someone's public key — that would need a
# separate keypair generated on this box, with ITS public half added to
# GitHub, which is a different, deliberate step).
# ── Standalone bootstrap ──────────────────────────────────────────────────────
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
[[ "$(id -u)" == "0" ]] || { echo "Run with sudo: sudo bash $0"; exit 1; }
_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_COMMON="$_SELF_DIR/../lib/common.sh"
if [[ -f "$_COMMON" ]]; then
# shellcheck source=../lib/common.sh
source "$_COMMON"
else
log_info() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
log_success() { echo -e "\033[0;32m[OK]\033[0m $*"; }
log_warning() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
prompt_text() {
local _q="$1" _def="$2" _var="$3" _r
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
read -r -p " $_q " _r
eval "$_var='${_r:-$_def}'"
}
prompt_yn() {
local _q="$1" _def="$2" _var="$3" _r
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
read -r -p " $_q " _r
eval "$_var='${_r:-$_def}'"
}
run_cmd() {
[[ "${DRY_RUN:-false}" == "true" ]] && { echo "[DRY-RUN] Would execute: $*"; return 0; }
"$@"
}
register_service() { :; }
fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "${HOME:-/root}")"
DRY_RUN="${DRY_RUN:-false}"
UNATTENDED="${UNATTENDED:-false}"
_RUN_STANDALONE=1
fi
# ─────────────────────────────────────────────────────────────────────────────
register_service ssh-key-import extras "Import SSH public keys from GitHub/Launchpad for passwordless login; optionally disable password auth"
install_ssh-key-import() {
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would ensure openssh-server is installed and running"
echo "[DRY-RUN] Would offer to import SSH public keys from GitHub and/or Launchpad"
echo "[DRY-RUN] Would offer to disable SSH password authentication if any key was imported"
return 0
fi
log_info "Configuring SSH server..."
if ! dpkg -l openssh-server &>/dev/null; then
run_cmd apt-get install -y openssh-server
fi
run_cmd systemctl enable --now ssh
local GH_USER="" LP_USER="" _keys_imported=false
prompt_text "GitHub username to import SSH keys from (blank to skip):" "" GH_USER
if [ -n "$GH_USER" ]; then
if ssh-import-id "gh:$GH_USER"; then
log_success "Imported SSH keys from GitHub: $GH_USER"
_keys_imported=true
else
log_warning "Could not import keys from GitHub: $GH_USER"
fi
fi
prompt_text "Launchpad username to import SSH keys from (blank to skip):" "" LP_USER
if [ -n "$LP_USER" ]; then
if ssh-import-id "lp:$LP_USER"; then
log_success "Imported SSH keys from Launchpad: $LP_USER"
_keys_imported=true
else
log_warning "Could not import keys from Launchpad: $LP_USER"
fi
fi
if [ "$_keys_imported" = false ]; then
log_info "No keys imported — nothing else to do."
return 0
fi
local DISABLE_PW=""
prompt_yn "Disable SSH password authentication (key login only)? (y/n):" "y" DISABLE_PW
if [[ "$DISABLE_PW" =~ ^[Yy]$ ]]; then
sed -i \
-e 's/^#*\s*PasswordAuthentication\s.*/PasswordAuthentication no/' \
-e 's/^#*\s*KbdInteractiveAuthentication\s.*/KbdInteractiveAuthentication no/' \
/etc/ssh/sshd_config
# Ubuntu 22.04+ may also have a drop-in that re-enables password auth.
local _dropin="/etc/ssh/sshd_config.d/50-cloud-init.conf"
if [ -f "$_dropin" ]; then
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' "$_dropin"
fi
systemctl restart ssh
log_success "SSH password authentication disabled — key login only"
fi
}
# Run immediately when executed directly (deferred until after function definition)
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_ssh-key-import
+2 -1
View File
@@ -101,6 +101,7 @@ is_installed() {
pstn-trunk) [ -f "$DOCKER_DIR/asterisk-digital-ocean/config/asterisk/pstn-trunk-pjsip.conf" ] || [ -f "$DOCKER_DIR/asterisk/config/asterisk/pstn-trunk-pjsip.conf" ] ;;
sms-inbound) [ -f /opt/sms-inbound/settings.env ] ;;
ssh-config) false ;; # repeatable management tool, never shows [installed]
ssh-key-import) false ;; # repeatable management tool, never shows [installed]
# Every WordPress site is named from the first one on (no plain
# $DOCKER_DIR/wordpress dir the default case below could match) —
# [installed] means "at least one site exists", not any specific one.
@@ -122,7 +123,7 @@ is_installed() {
# is_installed() as 0 or 1.
install_count() {
case "$1" in
base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config)
base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config|ssh-key-import)
is_installed "$1" && echo 1 || echo 0 ;;
wordpress)
find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name 'wordpress-*' -type d 2>/dev/null | wc -l ;;