跳转至

01 jupyterhub 安装脚本

jupyterhub 安装脚本

背景

  • 支持一键安装jupyterhub
  • 支持用户创建

脚本install_jupyterhub.sh

#!/bin/bash

# JupyterHub 安装部署脚本
# 使用说明:以root用户执行此脚本

set -Eeuo pipefail  # 遇到错误、未定义变量或管道错误时立即退出

# 始终从脚本所在目录读取用户清单,避免因执行目录不同而找不到文件
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查是否以root用户运行
check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "此脚本必须以root用户运行"
        exit 1
    fi
    log_info "root权限检查通过"
}

# 安装系统依赖
install_dependencies() {
    log_info "安装系统依赖包..."

    # 优先根据包管理器判断,避免 /etc/os-release 导致 Debian 被误判为 RedHat
    if command -v dnf &>/dev/null; then
        dnf update -y
        dnf install -y python3 python3-devel python3-pip \
                      nodejs npm \
                      git curl wget \
                      sqlite sqlite-devel \
                      gcc gcc-c++ make
    elif command -v yum &>/dev/null; then
        yum update -y
        yum install -y python3 python3-devel python3-pip \
                      nodejs npm \
                      git curl wget \
                      sqlite sqlite-devel \
                      gcc gcc-c++ make
    elif command -v apt-get &>/dev/null; then
        apt-get update
        apt-get install -y python3 python3-dev python3-pip \
                          nodejs npm \
                          git curl wget \
                          sqlite3 libsqlite3-dev \
                          gcc g++ make

    else
        log_warn "未知的系统类型,请手动安装依赖"
    fi

    # 升级pip
    pip3 install --upgrade pip
    log_info "系统依赖安装完成"
}

# 安装JupyterHub和相关组件
install_jupyterhub() {
    log_info "安装JupyterHub和相关组件..."

    # 安装JupyterHub
    pip3 install jupyterhub

    # 安装Jupyter Notebook(用于单用户服务器)
    pip3 install notebook

    # 安装配置代理
    npm install -g configurable-http-proxy@3

    # 安装sudospawner(用于sudo创建用户)
    pip3 install sudospawner

    # 安装PAM认证(可选,用于系统用户认证)
    #pip3 install jupyterhub-pamauthenticator

    log_info "JupyterHub安装完成"
}

# 创建系统用户
create_users() {
    local users_file="${SCRIPT_DIR}/jupyterhub_users.txt"
    local username password uid extra
    local existing_uid home_dir primary_group
    local line_no=0
    local validation_errors=0
    declare -A seen_users=()
    declare -A seen_uids=()

    log_info "创建JupyterHub用户..."

    # 检查用户文件是否存在,如果不存在则创建示例
    if [ ! -f "$users_file" ]; then
        log_warn "用户文件 $users_file 不存在,创建示例文件"
        cat > "$users_file" << 'EOF'
# JupyterHub用户列表
# 每行格式:用户名:密码:UID(UID为必填项)
# 示例:
zhucongming:请修改此密码:1000
EOF
        chmod 600 "$users_file"
        log_info "示例用户文件已创建: $users_file"
        log_warn "请编辑此文件后重新运行用户创建步骤"
        return
    fi

    # 文件含明文密码,仅允许root读写
    chmod 600 "$users_file"

    # 第一遍只做校验,确保配置全部正确后再创建,避免产生部分用户
    while IFS=':' read -r username password uid extra || [[ -n "${username:-}" ]]; do
        ((line_no += 1))

        # 跳过空行和注释
        [[ "${username:-}" =~ ^[[:space:]]*# ]] && continue
        [[ -z "$username" ]] && continue

        if [[ -n "${extra:-}" ]]; then
            log_error "第 ${line_no} 行格式错误,应为:用户名:密码:UID"
            ((validation_errors += 1))
            continue
        fi

        if [[ ! "$username" =~ ^[a-zA-Z_][a-zA-Z0-9_.-]*[$]?$ ]]; then
            log_error "第 ${line_no} 行用户名不合法: $username"
            ((validation_errors += 1))
            continue
        fi

        if [[ -z "${uid:-}" ]]; then
            log_error "第 ${line_no} 行用户 $username 未指定 UID"
            ((validation_errors += 1))
            continue
        fi

        if [[ ! "$uid" =~ ^(0|[1-9][0-9]*)$ ]]; then
            log_error "第 ${line_no} 行用户 $username 的 UID 格式不合法: $uid"
            ((validation_errors += 1))
            continue
        fi

        if [[ -n "${seen_users[$username]:-}" ]]; then
            log_error "第 ${line_no} 行用户名重复: $username"
            ((validation_errors += 1))
            continue
        fi

        if [[ -n "${seen_uids[$uid]:-}" ]]; then
            log_error "第 ${line_no} 行 UID $uid 与用户 ${seen_uids[$uid]} 重复"
            ((validation_errors += 1))
            continue
        fi
        seen_users["$username"]=1
        seen_uids["$uid"]="$username"

        if id "$username" &>/dev/null; then
            existing_uid="$(id -u "$username")"
            if [[ "$existing_uid" != "$uid" ]]; then
                log_error "用户 $username 已存在,但现有 UID 为 $existing_uid,配置要求为 $uid"
                ((validation_errors += 1))
            fi
        elif getent passwd "$uid" &>/dev/null; then
            log_error "UID $uid 已被用户 $(getent passwd "$uid" | cut -d: -f1) 占用"
            ((validation_errors += 1))
        fi
    done < "$users_file"

    if ((validation_errors > 0)); then
        log_error "用户文件校验失败,共发现 ${validation_errors} 个错误;未创建任何用户"
        return 1
    fi

    # 第二遍创建用户;UID 必须通过 -u 显式传给 useradd
    while IFS=':' read -r username password uid extra || [[ -n "${username:-}" ]]; do
        [[ "${username:-}" =~ ^[[:space:]]*# ]] && continue
        [[ -z "${username:-}" ]] && continue

        if id "$username" &>/dev/null; then
            log_info "用户 $username 已存在,UID 已确认是 $uid"
        else
            useradd -m -u "$uid" -s /bin/bash "$username"

            # 设置密码
            if [ -n "$password" ]; then
                printf '%s:%s\n' "$username" "$password" | chpasswd
                log_info "创建用户: $username(UID: $uid)"
            else
                log_warn "已创建用户 $username(UID: $uid),但没有设置密码"
            fi

            # 创建Jupyter配置目录
            IFS=':' read -r _ _ _ _ _ home_dir _ < <(getent passwd "$username")
            primary_group="$(id -gn "$username")"
            mkdir -p "$home_dir/.jupyter"
            chown -R "$username:$primary_group" "$home_dir"
        fi
    done < "$users_file"

    log_info "用户创建完成"
}

# 配置JupyterHub
configure_jupyterhub() {
    log_info "配置JupyterHub..."

    # 创建配置目录
    mkdir -p /etc/jupyterhub
    cd /etc/jupyterhub

    # 生成默认配置
    if [ ! -f jupyterhub_config.py ]; then
        log_info "生成JupyterHub配置文件..."
        jupyterhub --generate-config

        # 备份原始配置
        cp jupyterhub_config.py jupyterhub_config.py.backup
    fi

    # 创建自定义配置
    cat > /etc/jupyterhub/custom_config.py << 'EOF'
# JupyterHub自定义配置
import os
from jupyterhub.spawner import LocalProcessSpawner

# 使用PAM认证
c.JupyterHub.authenticator_class = 'pam'
c.PAMAuthenticator.open_sessions = False

# 允许root运行hub
c.JupyterHub.allow_root = True

# 设置hub的IP和端口
c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_port = 8080

# 设置公共接口
c.JupyterHub.ip = '0.0.0.0'
c.JupyterHub.port = 8000

# 使用sudospawner
c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner'

# 设置sudospawner路径
c.SudoSpawner.sudo_cmd = ['sudo', '-nHu', '{USERNAME}']

# 设置默认notebook服务器
c.Spawner.default_url = '/tree'
c.Spawner.notebook_dir = '~/'

# 用户白名单(可选)
# c.Authenticator.allowed_users = {'user1', 'user2'}
c.Authenticator.allow_all = True

# 管理员用户
c.Authenticator.admin_users = {'root'}

# SSL配置(如果需要HTTPS)
# c.JupyterHub.ssl_cert = '/path/to/cert.pem'
# c.JupyterHub.ssl_key = '/path/to/key.pem'

# Cookie密钥(生产环境需要设置)
import uuid
c.JupyterHub.cookie_secret = os.urandom(32)

# 数据库设置
c.JupyterHub.db_url = 'sqlite:////etc/jupyterhub/jupyterhub.sqlite'
EOF

    # 合并配置文件
    echo "" >> jupyterhub_config.py
    cat custom_config.py >> jupyterhub_config.py

    log_info "JupyterHub配置完成"
}

# 配置sudo权限
configure_sudo() {
    log_info "配置sudo权限..."

    # 创建sudospawner配置文件
    cat > /etc/sudoers.d/jupyterhub << 'EOF'
# Sudo配置 for JupyterHub
# 允许jupyterhub运行sudospawner

# 设置env_keep以保留环境变量
Defaults env_keep += "PATH"
Defaults env_keep += "PYTHONPATH"
Defaults env_keep += "HOME"
Defaults env_keep += "USER"

# 允许jupyterhub使用sudospawner
Cmnd_Alias JUPYTER_CMD = /usr/local/bin/sudospawner
%jupyterhub ALL=(ALL) NOPASSWD: JUPYTER_CMD
EOF

    # 设置正确的权限
    chmod 440 /etc/sudoers.d/jupyterhub

    log_info "sudo配置完成"
}

# 创建systemd服务
create_systemd_service() {
    log_info "创建systemd服务..."

    cat > /etc/systemd/system/jupyterhub.service << 'EOF'
[Unit]
Description=JupyterHub
After=network.target

[Service]
User=root
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/usr/local/bin/jupyterhub -f /etc/jupyterhub/jupyterhub_config.py
WorkingDirectory=/etc/jupyterhub
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

    # 重新加载systemd
    systemctl daemon-reload
    systemctl enable jupyterhub

    log_info "systemd服务创建完成"
}

# 防火墙配置
configure_firewall() {
    log_info "配置防火墙..."

    # 检查防火墙类型
    if command -v ufw &> /dev/null; then
        # Ubuntu/Debian使用ufw
        ufw allow 8000/tcp
        ufw allow 8080/tcp
        log_info "UFW防火墙已配置"

    elif command -v firewall-cmd &> /dev/null; then
        # CentOS/RHEL使用firewalld
        firewall-cmd --permanent --add-port=8000/tcp
        firewall-cmd --permanent --add-port=8080/tcp
        firewall-cmd --reload
        log_info "Firewalld已配置"

    elif command -v iptables &> /dev/null; then
        # 使用iptables
        iptables -A INPUT -p tcp --dport 8000 -j ACCEPT
        iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
        log_info "iptables已配置"
    else
        log_warn "未找到支持的防火墙工具,请手动开放端口8000和8080"
    fi
}

# 显示安装完成信息
show_completion() {
    echo ""
    log_info "========================================="
    log_info "JupyterHub 安装完成!"
    log_info "========================================="
    echo ""
    log_info "访问地址: http://服务器IP:8000"
    log_info "Hub地址: http://服务器IP:8080"
    echo ""
    log_info "管理命令:"
    log_info "启动服务: systemctl start jupyterhub"
    log_info "停止服务: systemctl stop jupyterhub"
    log_info "查看状态: systemctl status jupyterhub"
    log_info "查看日志: journalctl -u jupyterhub -f"
    echo ""
    log_info "用户管理:"
    log_info "用户列表文件: ${SCRIPT_DIR}/jupyterhub_users.txt"
    log_info "用户格式: 用户名:密码:UID(UID必填且不可重复)"
    log_info "添加用户后重新运行脚本即可创建"
    echo ""
}

# 主函数
main() {
    if [[ "${1:-}" == "--create-users-only" ]]; then
        check_root
        create_users
        log_info "JupyterHub用户处理完成"
        return
    fi

    if [[ $# -gt 0 ]]; then
        log_error "未知参数: $1"
        echo "用法: $0 [--create-users-only]"
        exit 2
    fi

    log_info "开始安装JupyterHub..."

    # 执行安装步骤
    check_root
    install_dependencies
    install_jupyterhub
    create_users
    configure_jupyterhub
    configure_sudo
    create_systemd_service
#    configure_firewall

    log_info "启动JupyterHub服务..."
    systemctl start jupyterhub

    show_completion
}

# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

执行:

chmod +x install_jupyterhub.sh
./install_jupyterhub.sh

脚本可重复执行,执行第一次后不会创建普通用户,待编辑完jupyterhub_users.txt 文件后才会创建用户