#!/usr/bin/env bash # Makes a Linux machine drivable from the desktop over SSH. Typed once on the # machine, from the account that will be used, after it joins the home wifi: # # wget -qO- https://ssh.vineyardtechnologies.org | sudo bash # # Installs OpenSSH Server with the desktop's key as the only way in, gives the # account passwordless sudo, and advertises the machine on the LAN over mDNS # so the desktop finds it as .local. Safe to re-run. # connectOverSSH.md explains the setup. set -euo pipefail DESKTOP_KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCBl86RRQZ1012pBsJp/CtDn9YDuaE3agUmViPVgAV9 andrew@andrewDesktop' # Everything runs from main, so bash has read the whole script before any of # it runs; a pipe that closed early can then never run half of it. main() { [ "$(id -u)" = 0 ] || { echo "run it through sudo: wget -qO- https://ssh.vineyardtechnologies.org | sudo bash" >&2; exit 1; } local user=${SUDO_USER:-} [ -n "$user" ] && [ "$user" != root ] || { echo "run it with sudo from your own account, not as root" >&2; exit 1; } command -v apt-get >/dev/null || { echo "this script knows apt only (Ubuntu, Debian and their flavours)" >&2; exit 1; } local home home=$(getent passwd "$user" | cut -d: -f6) echo "== packages" export DEBIAN_FRONTEND=noninteractive # A broken third-party source fails the update without affecting these # packages, so it is reported rather than fatal. apt-get update -q > "$home/.ssh/authorized_keys" chown "$user:$user" "$home/.ssh/authorized_keys" chmod 600 "$home/.ssh/authorized_keys" echo "== passwordless sudo for $user" # The desktop's key is the only way into this account, and a command sent # over SSH has no terminal to type a sudo password into. printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$user" > /etc/sudoers.d/desktop chmod 440 /etc/sudoers.d/desktop visudo -cf /etc/sudoers.d/desktop >/dev/null echo "== sshd: key only" # sshd takes the first value it reads for a setting, and 10- sorts ahead of # anything a cloud image or installer drops in (50-cloud-init.conf turns # passwords on). cat > /etc/ssh/sshd_config.d/10-desktop.conf <<'CONF' # Written by MyUbuntuConfigs/ssh/enable-ssh.sh: the desktop's key is the only way in. PubkeyAuthentication yes PasswordAuthentication no KbdInteractiveAuthentication no CONF # An earlier setup of this kind bound sshd to loopback only; sshd listens on # every interface now. rm -f /etc/ssh/sshd_config.d/10-desktop-only.conf /etc/systemd/system/ssh.socket.d/loopback.conf # sshd -t wants its privilege separation directory, which only exists once # sshd has run, and a socket-started sshd may not have yet. install -d -m 755 /run/sshd sshd -t systemctl daemon-reload if systemctl is-enabled --quiet ssh.socket 2>/dev/null; then # Ubuntu starts sshd from ssh.socket: stopping the service makes the # next connection start one that reads the new config. systemctl stop ssh.service systemctl restart ssh.socket else systemctl enable ssh.service systemctl restart ssh.service fi echo "== mDNS" # Advertising _ssh._tcp lets the desktop list the machines it can adopt # (avahi-browse _ssh._tcp) without anyone reading a hostname off a screen. cat > /etc/avahi/services/ssh.service < %h _ssh._tcp 22 user=$user SERVICE systemctl enable --now avahi-daemon systemctl reload avahi-daemon if command -v ufw >/dev/null && ufw status | grep -q '^Status: active'; then echo "== firewall" ufw allow OpenSSH fi local listening listening=$(ss -ltnH 'sport = :22' | awk '{print $4}') if [ -z "$(grep -vE '^(127\.|\[::1\])' <<<"$listening" || true)" ]; then echo "sshd is not listening on the network (only: ${listening:-nothing}), so the desktop cannot reach it" >&2 exit 1 fi local target="$user@$(hostname).local" echo echo "Done: the desktop reaches this machine as $target" echo "On the desktop: node ~/git/MyUbuntuConfigs/ssh/adopt.ts $target" } main "$@"