#!/bin/sh # Make a bare IsardVDI Linux desktop reachable: install the SSH server, create # the account the bastion logs in as, and install the SPICE guest agent. # # The Linux counterpart of isard.ps1, and it exists for the same reason: a # freshly-installed guest often has no SSH server and no account matching # `guest_properties.credentials`, and both live *inside* the guest -- the host # and this CLI cannot reach them. The symptoms: # # * `isard ssh ` -> "connection refused": the bastion reached the guest # but nothing is listening on port 22. # * `isard ssh ` -> "unable to authenticate, attempted methods # [none password]": sshd is up, but the guest has no such account. # # Run it once, in the desktop's own viewer, as a single line short enough to # type into a console with no working clipboard: # # curl -fsSL https://get.optersoft.com/isard.sh | sudo sh -s isard mypassword # # The account can also come from the environment (same names isard.ps1 uses) # or from a prompt: # # ISARD_USER=isard ISARD_PASS=... ; export ISARD_USER ISARD_PASS # curl -fsSL https://get.optersoft.com/isard.sh | sudo -E sh # # Idempotent: safe to run again, skips whatever is already in place. # # POSIX sh on purpose -- Ubuntu's /bin/sh is dash, and this must run before # anything has been installed. Supports apt (Debian/Ubuntu) and dnf (Fedora # and the RHEL family). set -eu USER_NAME="${1:-${ISARD_USER:-}}" USER_PASS="${2:-${ISARD_PASS:-}}" SSH_KEY="${ISARD_SSH_KEY:-}" say() { printf '==> %s\n' "$*"; } warn() { printf 'warning: %s\n' "$*" >&2; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } [ "$(id -u)" -eq 0 ] || die "run me as root: pipe into 'sudo sh' rather than 'sh'" # Prompting reads /dev/tty, not stdin: stdin is this script, arriving down the # pipe from curl. if [ -z "$USER_NAME" ]; then if [ -r /dev/tty ]; then printf 'Username for the IsardVDI bastion to log in as: ' > /dev/tty read -r USER_NAME < /dev/tty fi [ -n "$USER_NAME" ] || die "no username given (pass it as the first argument)" fi if [ -z "$USER_PASS" ]; then if [ -r /dev/tty ]; then printf 'Password for %s: ' "$USER_NAME" > /dev/tty stty -echo 2>/dev/null || true read -r USER_PASS < /dev/tty stty echo 2>/dev/null || true printf '\n' > /dev/tty fi [ -n "$USER_PASS" ] || die "no password given (pass it as the second argument)" fi if command -v apt-get > /dev/null 2>&1; then FAMILY=debian elif command -v dnf > /dev/null 2>&1; then FAMILY=fedora else die "no apt-get or dnf found -- unsupported distribution" fi say "detected the $FAMILY family" # --- packages --------------------------------------------------------------- # # spice-vdagent is what makes the viewer's clipboard work; qemu-guest-agent is # how IsardVDI reads the guest's IP and applies guest properties. Neither is # required for SSH, so a failure to install them is not fatal. install_packages() { if [ "$FAMILY" = debian ]; then DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@" else dnf install -y --setopt=install_weak_deps=False "$@" fi } if [ "$FAMILY" = debian ]; then SSHD_PKG=openssh-server; SSHD_SVC=ssh; ADMIN_GROUP=sudo else SSHD_PKG=openssh-server; SSHD_SVC=sshd; ADMIN_GROUP=wheel fi if [ -x /usr/sbin/sshd ] || [ -x /usr/bin/sshd ]; then say "SSH server already installed" else say "installing $SSHD_PKG" install_packages "$SSHD_PKG" || die "could not install $SSHD_PKG -- is there internet egress?" fi say "enabling $SSHD_SVC" systemctl enable --now "$SSHD_SVC" 2>/dev/null || service "$SSHD_SVC" start || \ warn "could not start $SSHD_SVC automatically -- start it by hand" say "installing the guest agents (clipboard + guest agent)" install_packages spice-vdagent qemu-guest-agent > /dev/null 2>&1 || \ warn "guest agents not installed -- SSH still works, the clipboard may not" systemctl enable --now qemu-guest-agent 2>/dev/null || true systemctl enable --now spice-vdagentd 2>/dev/null || true # --- the bastion account ---------------------------------------------------- if id -u "$USER_NAME" > /dev/null 2>&1; then say "user $USER_NAME already exists" else say "creating user $USER_NAME" useradd --create-home --shell /bin/bash "$USER_NAME" fi # Always (re)set the password: the whole point is that it matches what # `guest_properties.credentials` will send. printf '%s:%s\n' "$USER_NAME" "$USER_PASS" | chpasswd say "password set for $USER_NAME" if getent group "$ADMIN_GROUP" > /dev/null 2>&1; then usermod -aG "$ADMIN_GROUP" "$USER_NAME" say "added $USER_NAME to $ADMIN_GROUP" fi # Passwordless sudo, so `isard build` can provision without a terminal. SUDOERS="/etc/sudoers.d/90-${USER_NAME}-isard" printf '%s ALL=(ALL) NOPASSWD:ALL\n' "$USER_NAME" > "$SUDOERS" chmod 0440 "$SUDOERS" say "passwordless sudo enabled for $USER_NAME" if [ -n "$SSH_KEY" ]; then HOME_DIR="$(getent passwd "$USER_NAME" | cut -d: -f6)" install -d -m 0700 "$HOME_DIR/.ssh" if [ -f "$HOME_DIR/.ssh/authorized_keys" ] && \ grep -qF "$SSH_KEY" "$HOME_DIR/.ssh/authorized_keys"; then say "SSH key already authorised" else printf '%s\n' "$SSH_KEY" >> "$HOME_DIR/.ssh/authorized_keys" say "authorised the SSH key" fi chmod 0600 "$HOME_DIR/.ssh/authorized_keys" chown -R "$USER_NAME": "$HOME_DIR/.ssh" fi # The bastion authenticates to the guest with a password, so sshd must accept # one. Distributions increasingly ship PasswordAuthentication no. if [ -d /etc/ssh/sshd_config.d ]; then printf 'PasswordAuthentication yes\n' > /etc/ssh/sshd_config.d/90-isard.conf systemctl reload "$SSHD_SVC" 2>/dev/null || true say "password authentication enabled for sshd" fi printf '\n' say "done -- this desktop is now reachable with: isard ssh " say "you can shut it down; the build continues on its own"