diff --git a/arch_install.sh b/arch_install.sh new file mode 100644 index 0000000..524b1ce --- /dev/null +++ b/arch_install.sh @@ -0,0 +1,309 @@ +#!/usr/bin/env bash +# +# Arch Linux installer for: +# - R + RStudio Server (via AUR rstudio-server-bin) +# - JupyterHub + JupyterLab (native install, not TLJH) +# +# What it does: +# - installs system dependencies +# - installs and enables UFW +# - creates an admin UNIX account +# - installs R and RStudio Server +# - installs JupyterHub into /opt/jupyterhub +# - configures JupyterHub as a systemd service +# +# Run as root: +# sudo bash install_jhub_rstudio_arch.sh +# + +set -Eeuo pipefail + +# ----------------------------- +# Configuration: EDIT THESE +# ----------------------------- +ADMIN_USER="your_admin_user" +ADMIN_PASSWORD="your_admin_password" + +JUPYTERHUB_PORT="8000" +RSTUDIO_PORT="8787" + +JUPYTERHUB_VENV="/opt/jupyterhub" +JUPYTERHUB_CONFIG_DIR="/etc/jupyterhub" +JUPYTERHUB_CONFIG_FILE="${JUPYTERHUB_CONFIG_DIR}/jupyterhub_config.py" +JUPYTERHUB_SERVICE_FILE="/etc/systemd/system/jupyterhub.service" + +USE_UFW="yes" +INSTALL_PARU="yes" +# ----------------------------- + +log() { + printf "\n\033[1;36m==> %s\033[0m\n" "$*" +} + +warn() { + printf "\n\033[1;33m[WARN]\033[0m %s\n" "$*" +} + +err() { + printf "\n\033[1;31m[ERROR]\033[0m %s\n" "$*" >&2 +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + err "Run this script as root (sudo)." + exit 1 + fi +} + +detect_real_user() { + if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then + REAL_USER="${SUDO_USER}" + else + REAL_USER="$(logname 2>/dev/null || true)" + if [[ -z "${REAL_USER}" || "${REAL_USER}" == "root" ]]; then + err "Could not determine the non-root user that should build AUR packages." + err "Run with: sudo bash $0 from your normal user session." + exit 1 + fi + fi + + REAL_HOME="$(getent passwd "${REAL_USER}" | cut -d: -f6)" + if [[ -z "${REAL_HOME}" || ! -d "${REAL_HOME}" ]]; then + err "Could not determine home directory for ${REAL_USER}." + exit 1 + fi +} + +install_base_packages() { + log "Updating pacman and installing base packages" + + pacman -Syu --noconfirm + + pacman -S --needed --noconfirm \ + base-devel \ + git \ + curl \ + wget \ + sudo \ + ufw \ + nginx \ + python \ + python-pip \ + python-virtualenv \ + nodejs \ + npm \ + sqlite \ + pam \ + shadow \ + which \ + r \ + gcc-fortran +} + +configure_firewall() { + if [[ "${USE_UFW}" != "yes" ]]; then + warn "Skipping UFW configuration" + return + fi + + log "Configuring UFW firewall" + systemctl enable --now ufw + ufw allow 80/tcp + ufw allow 443/tcp + ufw allow "${JUPYTERHUB_PORT}"/tcp + ufw allow "${RSTUDIO_PORT}"/tcp + ufw --force enable +} + +create_admin_user() { + log "Creating admin system user if needed" + + if ! id "${ADMIN_USER}" >/dev/null 2>&1; then + useradd -m -s /bin/bash "${ADMIN_USER}" + echo "${ADMIN_USER}:${ADMIN_PASSWORD}" | chpasswd + usermod -aG wheel "${ADMIN_USER}" + log "Created user ${ADMIN_USER}" + else + warn "User ${ADMIN_USER} already exists; updating password" + echo "${ADMIN_USER}:${ADMIN_PASSWORD}" | chpasswd + usermod -aG wheel "${ADMIN_USER}" || true + fi +} + +install_paru() { + if command -v paru >/dev/null 2>&1; then + log "paru already installed" + return + fi + + if [[ "${INSTALL_PARU}" != "yes" ]]; then + err "paru is not installed and INSTALL_PARU=no" + exit 1 + fi + + log "Installing paru AUR helper as ${REAL_USER}" + + local build_dir="${REAL_HOME}/paru-build" + rm -rf "${build_dir}" + + sudo -u "${REAL_USER}" bash -lc " + set -Eeuo pipefail + cd '${REAL_HOME}' + git clone https://aur.archlinux.org/paru.git '${build_dir}' + cd '${build_dir}' + makepkg -si --noconfirm + " + + rm -rf "${build_dir}" +} + +install_rstudio_server() { + log "Installing RStudio Server from AUR (rstudio-server-bin)" + + sudo -u "${REAL_USER}" bash -lc " + set -Eeuo pipefail + paru -S --needed --noconfirm rstudio-server-bin + " + + mkdir -p /etc/rstudio + if ! grep -q '^rsession-which-r=' /etc/rstudio/rserver.conf 2>/dev/null; then + echo 'rsession-which-r=/usr/bin/R' >> /etc/rstudio/rserver.conf + else + sed -i 's|^rsession-which-r=.*|rsession-which-r=/usr/bin/R|' /etc/rstudio/rserver.conf + fi + + mkdir -p /var/lib/rstudio-server + chown -R root:root /var/lib/rstudio-server + + # Some installs need an explicit PAM service file present + if [[ ! -e /etc/pam.d/rstudio ]]; then + cat >/etc/pam.d/rstudio <<'EOF' +auth include system-login +account include system-login +password include system-login +session include system-login +EOF + fi + + systemctl enable --now rstudio-server +} + +install_jupyterhub() { + log "Installing JupyterHub in virtual environment" + + mkdir -p "${JUPYTERHUB_VENV}" + python -m venv "${JUPYTERHUB_VENV}" + + "${JUPYTERHUB_VENV}/bin/pip" install --upgrade pip wheel setuptools + + "${JUPYTERHUB_VENV}/bin/pip" install \ + jupyterhub \ + jupyterlab \ + notebook + + npm install -g configurable-http-proxy +} + +configure_jupyterhub() { + log "Configuring JupyterHub" + + mkdir -p "${JUPYTERHUB_CONFIG_DIR}" + + cat >"${JUPYTERHUB_CONFIG_FILE}" < "${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret" + fi + + chmod 600 "${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret" + + cat >"${JUPYTERHUB_SERVICE_FILE}" <