#!/usr/bin/env bash # Joins a freshly installed Ubuntu Desktop (24.04+) workstation to the tailnet # as tag:workstation, with Tailscale SSH enabled. Served publicly (it holds no # secrets) at https://workstation.klungs.in/install.sh. On the workstation, run: # # wget -qO- https://workstation.klungs.in/install.sh | sudo bash # wget -qO- https://workstation.klungs.in/install.sh | sudo bash -s -- --hostname ws01 # # It prints a login URL and its QR code. Scan it with your phone and sign in as # a member of group:engineer, the only users allowed to assign tag:workstation. # # Safe to re-run: an already joined workstation only gets Tailscale SSH and the # hostname re-applied. set -euo pipefail login_server=https://headscale.klungs.in tag=tag:workstation die() { echo "error: $*" >&2 exit 1 } # Everything runs from main, so bash has read the whole script before the first # command: a truncated download does nothing, and no command can swallow the # rest of the piped script from stdin. main() { local name state codename name=$(hostname -s) while [ $# -gt 0 ]; do case $1 in --hostname) name=${2:?--hostname needs a value}; shift 2 ;; *) die "unknown argument: $1 (usage: ... | sudo bash -s -- [--hostname NAME])" ;; esac done [ "$(id -u)" -eq 0 ] || die "run as root: ... | sudo bash" # shellcheck source=/dev/null . /etc/os-release [ "${ID:-}" = ubuntu ] || die "expected Ubuntu, found ${ID:-unknown}" codename=${VERSION_CODENAME:?VERSION_CODENAME missing from /etc/os-release} # --- install tailscale from the official repository ----------------------- if ! command -v tailscale >/dev/null; then echo "installing tailscale from pkgs.tailscale.com ($codename)" export DEBIAN_FRONTEND=noninteractive apt-get update -q /dev/null \ | sed -n 's/^\s*"BackendState": *"\([^"]*\)".*/\1/p' | head -n 1 || true) if [ "$state" = Running ]; then echo "already on the tailnet; re-applying settings" tailscale set --ssh --hostname "$name" else echo echo "Scan the QR code (or open the URL) on your phone and sign in as an engineer." echo "Do not sign in from this workstation's browser." echo # --ssh: tailscaled answers port 22 on the tailnet address itself, so no # OpenSSH server or authorized_keys is needed. # --operator is deliberately not set: only root may change tailscale. tailscale up \ --reset \ --login-server "$login_server" \ --advertise-tags "$tag" \ --hostname "$name" \ --ssh \ --qr /dev/null; then echo echo "note: an OpenSSH server is also running. It still serves the LAN, but" echo " port 22 on the tailnet address is answered by Tailscale SSH." echo " Remove it unless you need LAN access: apt purge openssh-server" fi } main "$@"