detect the operating system before any step runs

The script assumed Ubuntu on x86_64 in every line of it. detect_os() now runs
first and fills in OS, OS_NAME, OS_VERSION, PM, ARCH and IS_WSL, so the steps
have something to branch on as support for other systems is added.

Read from /etc/os-release rather than probing for a binary: a machine can have
more than one package manager on PATH, and only os-release can say which
distribution this actually is or give a version worth printing. Sourced in a
subshell so its NAME, VERSION and ID do not leak in here. ID_LIKE is the
fallback, so Pop!_OS, Mint and EndeavourOS resolve without being named.

ARCH is normalised to amd64/arm64 in one place because upstream disagrees —
Neovim ships aarch64, Go and Docker ship arm64, lazygit ships x86_64 — and
several steps hardcode one spelling today.

Windows exits with a message pointing at WSL2. WSL itself is detected and
warned about rather than refused: it reports as Linux but has no real systemd
session, so the suspend, logind and boot-hang steps do nothing there.

Everything below pre-flight is still apt and systemd only, so a gate refuses
pacman/dnf/brew by name rather than half-building a machine and stopping
somewhere unhelpful. Relax that case one entry at a time as each grows a path.

Verified on this host (Ubuntu 24.04.4, amd64, apt) and by stubbing uname and
os_release for arch, manjaro/arm64, fedora, pop, macos, mingw and riscv64.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 16:56:21 +00:00
co-authored by Claude Opus 5
parent 483bb15d8a
commit dda214ffb0
+109 -2
View File
@@ -14,6 +14,15 @@ ERRORS=()
CURRENT_STEP=""
SKIP_STEP=false
# What machine this is. Filled in by detect_os() before any step runs; every step
# after that branches on these rather than assuming apt on x86_64.
OS="" # os-release ID: ubuntu | debian | arch | fedora | macos | …
OS_NAME="" # pretty name, for the banner
OS_VERSION="" # version id; empty on rolling releases
PM="" # apt | pacman | dnf | brew
ARCH="" # amd64 | arm64, normalised — upstream tarballs disagree on spelling
IS_WSL=false
# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
@@ -84,15 +93,113 @@ as_user() {
sudo -u "$USERNAME" -i bash -c "$1"
}
# -----------------------------------------------------------------------------
# Operating system detection
# -----------------------------------------------------------------------------
#
# Read one key out of /etc/os-release without leaking the rest of it into this
# script. That file defines NAME, VERSION and ID — all generic enough to collide
# with something here — so it is sourced in a subshell and only the one value
# asked for comes back.
os_release() {
[[ -r /etc/os-release ]] || return 1
# shellcheck disable=SC1091
(
. /etc/os-release 2>/dev/null
printf '%s' "${!1:-}"
)
}
# Identify the machine, or refuse to guess.
#
# /etc/os-release rather than probing for a binary: a box can have more than one
# package manager on PATH (a Homebrew install on Linux, a leftover apt on a
# converted box), and only os-release can say which distribution the machine
# actually IS, or give a version worth reporting.
#
# ID_LIKE is the fallback so derivatives resolve without being listed by name —
# Pop!_OS, Mint and EndeavourOS all answer correctly without appearing below.
detect_os() {
local kernel like
kernel="$(uname -s)"
case "$kernel" in
Darwin)
OS="macos"
OS_VERSION="$(sw_vers -productVersion 2>/dev/null || true)"
OS_NAME="macOS ${OS_VERSION}"
PM="brew"
;;
Linux)
OS="$(os_release ID || true)"
OS_NAME="$(os_release PRETTY_NAME || true)"
OS_VERSION="$(os_release VERSION_ID || true)"
like="$(os_release ID_LIKE || true)"
case "$OS" in
ubuntu | debian | linuxmint | pop | raspbian | elementary) PM="apt" ;;
arch | manjaro | endeavouros | cachyos | garuda) PM="pacman" ;;
fedora | rhel | centos | rocky | almalinux) PM="dnf" ;;
*)
case " $like " in
*" debian "* | *" ubuntu "*) PM="apt" ;;
*" arch "*) PM="pacman" ;;
*" fedora "* | *" rhel "*) PM="dnf" ;;
esac
;;
esac
# WSL reports itself as Linux, but has no real systemd session: masking
# sleep targets, restarting logind and anything touching the boot path
# either fail or silently do nothing. Worth knowing before those steps run.
if grep -qi microsoft /proc/version 2>/dev/null; then IS_WSL=true; fi
;;
MINGW* | MSYS* | CYGWIN*)
fail "Windows is not supported. Run this inside WSL2 with an Ubuntu image instead."
;;
*)
fail "Unrecognised kernel '$kernel' — cannot tell what this machine is."
;;
esac
# Normalised once here because upstream projects spell it differently:
# Neovim ships aarch64, Go and Docker ship arm64, and lazygit ships x86_64.
case "$(uname -m)" in
x86_64 | amd64) ARCH="amd64" ;;
aarch64 | arm64) ARCH="arm64" ;;
*) fail "Unsupported CPU architecture '$(uname -m)' — this script installs amd64/arm64 binaries only." ;;
esac
[[ -n "$OS" ]] || fail "Could not identify this distribution (no readable /etc/os-release)."
[[ -n "$OS_NAME" ]] || OS_NAME="$OS${OS_VERSION:+ $OS_VERSION}"
}
# =============================================================================
# 1. Pre-flight
# =============================================================================
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}Ubuntu Server Setup${NC}"
echo -e "${BOLD}Machine Setup ${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}"
detect_os
echo ""
info "Machine: ${OS_NAME} (${ARCH})"
info "Packages: ${PM:-none detected}"
[[ "$IS_WSL" == true ]] && warn "WSL detected — the suspend, logind and boot-hang steps do not apply here"
# Everything below this line is written against apt and systemd. Detection above
# recognises pacman, dnf and brew so the branches have somewhere to hang, but
# nothing implements them yet — and running the apt path on Arch would half-build
# a machine and stop somewhere unhelpful. Refuse clearly instead, and relax this
# list one entry at a time as each package manager grows a real path.
case "$PM" in
apt) ;;
"") fail "Could not find a package manager for '${OS_NAME}'." ;;
*) fail "${OS_NAME} uses ${PM}, which this script does not implement yet — apt-based systems only, so far." ;;
esac
if [[ -f "$PROGRESS_FILE" ]]; then
DONE_COUNT=$(wc -l < "$PROGRESS_FILE")
echo -e "${YELLOW} Resuming — $DONE_COUNT step(s) already completed${NC}"
@@ -101,7 +208,7 @@ if [[ -f "$PROGRESS_FILE" ]]; then
fi
if [[ "$EUID" -ne 0 ]]; then
fail "Please run as root: sudo ./setup-ubuntu.sh"
fail "Please run as root: sudo ./machine-setup.sh"
fi
prompt_value USERNAME "New admin username (or existing)" ""