Renames bootstrap.sh to setup-local.sh to match its actual scope (the travel laptop only) and adds setup-remote.sh for the machine you Remote-SSH/RDP into, since that's a different machine's one-time setup and was already living outside setup-local.sh's reach (same reasoning as the existing Tailscale-SSH prerequisite). setup-remote.sh configures GNOME's system-level RDP (works from a cold GDM login screen, not just an existing session) restricted to the tailscale interface via a ufw rule, a self-signed TLS cert, and RDP credentials that are deliberately separate from the account password and never cached - only prompted if unset. Shares step-tracking helpers with setup-local.sh via a new lib.sh rather than duplicating them. Tested the branching logic (credentials-already-set, RDP-already- enabled, ufw-already-active-with-rule, ufw-inactive-confirm/decline) against realistic stubbed command output. Caught and fixed a real bug in the process: the inactive/active ufw check used a bare `grep -qi active`, which also matches the substring inside "inactive" - it was silently skipping the enable-confirmation gate and going straight to adding a firewall rule on a firewall that was never turned on. Fixed by anchoring the match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
166 lines
5.3 KiB
Bash
166 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=lib.sh
|
|
source "$SCRIPT_DIR/lib.sh"
|
|
|
|
RDP_PORT=3389
|
|
TLS_DIR="/etc/gnome-remote-desktop/tls"
|
|
|
|
print_summary() {
|
|
local any_failed=0
|
|
print_summary_table || any_failed=1
|
|
if [ "$any_failed" = 1 ]; then
|
|
echo "One or more steps failed. Fix the issue above and re-run this script -"
|
|
echo "already-completed steps are safe to skip and will not be repeated."
|
|
exit 1
|
|
fi
|
|
echo "All steps OK or already satisfied."
|
|
echo
|
|
echo "================== next steps ======================"
|
|
echo "Connect from the travel laptop with GNOME Connections (or xfreerdp)"
|
|
echo "to this machine's tailscale address, port $RDP_PORT. You'll be asked"
|
|
echo "for the RDP username/password you just set - that gets you to the"
|
|
echo "actual login screen, where you still log in with your real account"
|
|
echo "password same as sitting at the machine."
|
|
echo "====================================================="
|
|
}
|
|
|
|
preflight_env() {
|
|
section "preflight: environment"
|
|
if [ "$(id -u)" = "0" ]; then
|
|
step_fail "environment check" "do not run as root - script uses sudo for the specific commands that need it"
|
|
print_summary
|
|
exit 1
|
|
fi
|
|
if ! command -v grdctl >/dev/null 2>&1; then
|
|
step_fail "environment check" "grdctl not found - this script targets a machine with gnome-remote-desktop installed"
|
|
print_summary
|
|
exit 1
|
|
fi
|
|
step_ok "environment check"
|
|
}
|
|
|
|
harden_firewall() {
|
|
section "firewall (restrict RDP to tailscale)"
|
|
|
|
if ! command -v ufw >/dev/null 2>&1; then
|
|
step_fail "firewall" "ufw not installed"
|
|
return
|
|
fi
|
|
|
|
if ! sudo ufw status | head -1 | grep -qi "^Status: active"; then
|
|
echo "ufw is currently inactive on this machine. Enabling it switches to a"
|
|
echo "deny-incoming-by-default posture, which could affect any other LAN"
|
|
echo "service here that doesn't already have an explicit allow rule."
|
|
echo "Rules that will be in place once enabled:"
|
|
sudo ufw show added
|
|
read -rp "Type 'yes' to enable ufw now: " confirm
|
|
if [ "$confirm" != "yes" ]; then
|
|
step_fail "firewall" "not confirmed - ufw left inactive, RDP rule not added"
|
|
return
|
|
fi
|
|
sudo ufw --force enable
|
|
fi
|
|
|
|
if sudo ufw status | grep -qF "${RDP_PORT}/tcp on tailscale0"; then
|
|
step_skip "firewall" "RDP already restricted to tailscale0"
|
|
return
|
|
fi
|
|
|
|
if sudo ufw allow in on tailscale0 to any port "$RDP_PORT" proto tcp; then
|
|
step_ok "firewall" "RDP (${RDP_PORT}/tcp) allowed on tailscale0 only"
|
|
else
|
|
step_fail "firewall" "ufw rule failed to apply"
|
|
fi
|
|
}
|
|
|
|
configure_tls() {
|
|
section "tls certificate"
|
|
sudo mkdir -p "$TLS_DIR"
|
|
|
|
if sudo test -f "$TLS_DIR/cert.pem" && sudo test -f "$TLS_DIR/key.pem"; then
|
|
step_skip "tls certificate" "already present at $TLS_DIR - not regenerating (would invalidate the fingerprint clients already trust)"
|
|
else
|
|
if sudo openssl req -x509 -newkey rsa:4096 -nodes \
|
|
-keyout "$TLS_DIR/key.pem" -out "$TLS_DIR/cert.pem" \
|
|
-days 3650 -subj "/CN=$(hostname)" >/tmp/grd-tls.log 2>&1; then
|
|
sudo chown gnome-remote-desktop:gnome-remote-desktop "$TLS_DIR/key.pem" "$TLS_DIR/cert.pem"
|
|
sudo chmod 600 "$TLS_DIR/key.pem"
|
|
sudo chmod 644 "$TLS_DIR/cert.pem"
|
|
step_ok "tls certificate" "self-signed cert generated at $TLS_DIR"
|
|
else
|
|
step_fail "tls certificate" "openssl req failed - see /tmp/grd-tls.log"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
sudo grdctl --system rdp set-tls-cert "$TLS_DIR/cert.pem"
|
|
sudo grdctl --system rdp set-tls-key "$TLS_DIR/key.pem"
|
|
}
|
|
|
|
configure_credentials() {
|
|
section "rdp credentials"
|
|
local rdp_status
|
|
rdp_status=$(sudo grdctl --system rdp status --show-credentials 2>&1)
|
|
|
|
if ! echo "$rdp_status" | grep -q "Username: (null)"; then
|
|
step_skip "rdp credentials" "already configured - not touching an existing password"
|
|
return
|
|
fi
|
|
|
|
local rdp_user
|
|
read -rp "RDP username [$(whoami)]: " rdp_user
|
|
rdp_user="${rdp_user:-$(whoami)}"
|
|
|
|
echo "RDP password - a separate secret from your account login password,"
|
|
echo "gates only the initial RDP connection (you still log into the actual"
|
|
echo "session with your real account password afterwards). Not stored by"
|
|
echo "this script anywhere. Paste/type it, then press Enter and Ctrl-D:"
|
|
local rdp_pass
|
|
rdp_pass=$(cat)
|
|
rdp_pass="${rdp_pass//[[:space:]]/}"
|
|
|
|
if [ -z "$rdp_pass" ]; then
|
|
step_fail "rdp credentials" "empty password entered - not setting"
|
|
return
|
|
fi
|
|
|
|
if sudo grdctl --system rdp set-credentials "$rdp_user" "$rdp_pass"; then
|
|
step_ok "rdp credentials" "set for user $rdp_user"
|
|
else
|
|
step_fail "rdp credentials" "grdctl set-credentials failed"
|
|
fi
|
|
unset rdp_pass
|
|
}
|
|
|
|
enable_rdp() {
|
|
section "enable rdp backend"
|
|
sudo grdctl --system rdp set-port "$RDP_PORT"
|
|
sudo grdctl --system rdp disable-port-negotiation
|
|
|
|
if sudo grdctl --system rdp status 2>&1 | grep -qi "Status: *enabled"; then
|
|
step_skip "enable rdp backend" "already enabled"
|
|
return
|
|
fi
|
|
|
|
if sudo grdctl --system rdp enable; then
|
|
step_ok "enable rdp backend"
|
|
else
|
|
step_fail "enable rdp backend" "grdctl --system rdp enable failed"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
preflight_env
|
|
harden_firewall
|
|
configure_tls
|
|
configure_credentials
|
|
enable_rdp
|
|
print_summary
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi
|