Add arch_install.sh
This commit is contained in:
parent
33926e23df
commit
e935a5fed1
|
|
@ -0,0 +1,324 @@
|
|||
#!/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 OpenSSH
|
||||
# - installs and safely configures 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"
|
||||
SSH_PORT="22"
|
||||
|
||||
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"
|
||||
# -----------------------------
|
||||
|
||||
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 for building AUR packages."
|
||||
err "Run this 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 \
|
||||
openssh \
|
||||
nginx \
|
||||
python \
|
||||
python-pip \
|
||||
python-virtualenv \
|
||||
nodejs \
|
||||
npm \
|
||||
sqlite \
|
||||
pam \
|
||||
shadow \
|
||||
which \
|
||||
iproute2 \
|
||||
r \
|
||||
gcc-fortran
|
||||
}
|
||||
|
||||
enable_ssh() {
|
||||
log "Enabling SSH"
|
||||
|
||||
systemctl enable --now sshd
|
||||
}
|
||||
|
||||
configure_firewall() {
|
||||
if [[ "${USE_UFW}" != "yes" ]]; then
|
||||
warn "Skipping UFW configuration"
|
||||
return
|
||||
fi
|
||||
|
||||
log "Configuring UFW firewall safely"
|
||||
|
||||
# Always allow SSH first so remote access is not lost
|
||||
ufw allow "${SSH_PORT}"/tcp
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw allow "${JUPYTERHUB_PORT}"/tcp
|
||||
ufw allow "${RSTUDIO_PORT}"/tcp
|
||||
|
||||
ufw --force enable
|
||||
systemctl enable --now ufw
|
||||
}
|
||||
|
||||
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_rstudio_server() {
|
||||
log "Installing RStudio Server from AUR (without paru)"
|
||||
|
||||
local build_dir="${REAL_HOME}/rstudio-server-bin-build"
|
||||
|
||||
rm -rf "${build_dir}"
|
||||
|
||||
sudo -u "${REAL_USER}" bash -lc "
|
||||
set -Eeuo pipefail
|
||||
cd '${REAL_HOME}'
|
||||
git clone https://aur.archlinux.org/rstudio-server-bin.git '${build_dir}'
|
||||
cd '${build_dir}'
|
||||
makepkg -si --noconfirm
|
||||
"
|
||||
|
||||
rm -rf "${build_dir}"
|
||||
|
||||
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
|
||||
|
||||
# Remove broken/undesired server-user entries if present
|
||||
if grep -q '^server-user=' /etc/rstudio/rserver.conf 2>/dev/null; then
|
||||
sed -i '/^server-user=/d' /etc/rstudio/rserver.conf
|
||||
fi
|
||||
|
||||
mkdir -p /var/lib/rstudio-server
|
||||
chown -R root:root /var/lib/rstudio-server
|
||||
|
||||
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}" <<EOF
|
||||
c = get_config()
|
||||
|
||||
c.JupyterHub.bind_url = 'http://0.0.0.0:${JUPYTERHUB_PORT}'
|
||||
c.JupyterHub.hub_ip = '127.0.0.1'
|
||||
c.JupyterHub.spawner_class = 'jupyterhub.spawner.LocalProcessSpawner'
|
||||
|
||||
c.Authenticator.admin_users = {'${ADMIN_USER}'}
|
||||
c.JupyterHub.admin_access = True
|
||||
|
||||
c.Spawner.default_url = '/lab'
|
||||
|
||||
# Use system users + PAM authentication
|
||||
c.JupyterHub.authenticator_class = 'jupyterhub.auth.PAMAuthenticator'
|
||||
|
||||
# Keep runtime state in standard locations
|
||||
c.JupyterHub.cookie_secret_file = '${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret'
|
||||
c.JupyterHub.db_url = 'sqlite:///${JUPYTERHUB_CONFIG_DIR}/jupyterhub.sqlite'
|
||||
c.ConfigurableHTTPProxy.command = 'configurable-http-proxy'
|
||||
EOF
|
||||
|
||||
if [[ ! -f "${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret" ]]; then
|
||||
python - <<'PY'
|
||||
import secrets
|
||||
with open('/etc/jupyterhub/jupyterhub_cookie_secret', 'w', encoding='utf-8') as f:
|
||||
f.write(secrets.token_hex(32) + '\n')
|
||||
PY
|
||||
fi
|
||||
|
||||
chmod 600 "${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret"
|
||||
chown root:root "${JUPYTERHUB_CONFIG_DIR}/jupyterhub_cookie_secret"
|
||||
|
||||
cat >"${JUPYTERHUB_SERVICE_FILE}" <<EOF
|
||||
[Unit]
|
||||
Description=JupyterHub
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment="PATH=${JUPYTERHUB_VENV}/bin:/usr/local/bin:/usr/bin:/bin"
|
||||
ExecStart=${JUPYTERHUB_VENV}/bin/jupyterhub -f ${JUPYTERHUB_CONFIG_FILE}
|
||||
WorkingDirectory=${JUPYTERHUB_CONFIG_DIR}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now jupyterhub
|
||||
}
|
||||
|
||||
verify_services() {
|
||||
log "Verifying services"
|
||||
|
||||
systemctl --no-pager --full status sshd || true
|
||||
systemctl --no-pager --full status rstudio-server || true
|
||||
systemctl --no-pager --full status jupyterhub || true
|
||||
systemctl --no-pager --full status ufw || true
|
||||
|
||||
ss -tulpn | grep -E ":(${SSH_PORT}|${JUPYTERHUB_PORT}|${RSTUDIO_PORT}|80|443)\b" || true
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local server_ip
|
||||
server_ip="$(ip -4 addr show scope global | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)"
|
||||
|
||||
echo
|
||||
echo "=========================================================="
|
||||
echo "✅ Installation Summary"
|
||||
echo "=========================================================="
|
||||
echo "SSH:"
|
||||
echo " ssh ${ADMIN_USER}@${server_ip}"
|
||||
echo
|
||||
echo "JupyterHub:"
|
||||
echo " http://${server_ip}:${JUPYTERHUB_PORT}/"
|
||||
echo
|
||||
echo "RStudio Server:"
|
||||
echo " http://${server_ip}:${RSTUDIO_PORT}/"
|
||||
echo
|
||||
echo "Admin login:"
|
||||
echo " ${ADMIN_USER}"
|
||||
echo
|
||||
echo "Multi-user notes:"
|
||||
echo " - JupyterHub uses PAM/local Linux users."
|
||||
echo " - RStudio Server uses the same local Linux users."
|
||||
echo " - To add more users later:"
|
||||
echo " sudo useradd -m -s /bin/bash username"
|
||||
echo " sudo passwd username"
|
||||
echo
|
||||
echo "Important notes:"
|
||||
echo " - JupyterHub on this script is a native Arch install, not TLJH."
|
||||
echo " - RStudio Server is installed from the AUR package rstudio-server-bin."
|
||||
echo "=========================================================="
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
detect_real_user
|
||||
install_base_packages
|
||||
enable_ssh
|
||||
configure_firewall
|
||||
create_admin_user
|
||||
install_rstudio_server
|
||||
install_jupyterhub
|
||||
configure_jupyterhub
|
||||
verify_services
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue