This commit is contained in:
2025-10-22 20:53:55 +08:00
parent 5506b950e3
commit d69b9e126b

View File

@@ -3,70 +3,76 @@
资源路径 /home/zhangyi/jupyter-collection 资源路径 /home/zhangyi/jupyter-collection
控制文件 /home/zhangyi/jupyter-collection/.hub/resource_map.ini 控制文件 /home/zhangyi/jupyter-collection/.hub/resource_map.ini
""" """
import os, grp, pwd, shutil, subprocess, configparser import os, grp, pwd, shutil, subprocess, configparser
from pathlib import Path from pathlib import Path
from jupyterhub.spawner import Spawner from jupyterhub.spawner import Spawner
# ---------- 基础路径 ---------- # ---------- 路径常量 ----------
SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") # Git 仓库 SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") # Git 资源仓库
USER_HOME = Path("/home/jupyter-{username}") USER_HOME = Path("/home/jupyter-{username}") # 用户家目录模板
CTRL_FILE = SHARED_ROOT / ".hub" / "resource_map.ini" CTRL_FILE = SHARED_ROOT / ".hub" / "resource_map.ini" # 组-资源-用户映射
# ---------- 工具:读控制表 ---------- # ---------- 工具:读控制表 ----------
def load_ctrl(): def load_ctrl():
"""返回 组->资源 组->用户 两个字典""" """返回 (组->资源, 组->用户) 两个 dict"""
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg.read(CTRL_FILE) cfg.read(CTRL_FILE)
res_map = {g: [r.strip() for r in v.split(",")] res_map = {g: [r.strip() for r in v.split(",")] for g, v in cfg.items("map")}
for g, v in cfg.items("map")} usr_map = {g: [u.strip() for u in v.split(",")] for g, v in cfg.items("users")}
usr_map = {g: [u.strip() for u in v.split(",")]
for g, v in cfg.items("users")}
return res_map, usr_map return res_map, usr_map
# ---------- post_auth_hook自动建用户+加组 ---------- # ========== 最早钩子:预创建家目录 ==========
def ensure_homedir(spawner):
"""systemd 启动前保证目录存在且属主正确"""
username = spawner.user.name
user_dir = Path(str(USER_HOME).format(username=username))
if not user_dir.exists():
user_dir.mkdir(mode=0o700, parents=True)
shutil.chown(user_dir, user=username, group=username)
c.Spawner.pre_spawn_hook = ensure_homedir
# ========== 认证后钩子:自动建用户+加组 ==========
def ensure_user_and_groups(authenticator, handler, authentication): def ensure_user_and_groups(authenticator, handler, authentication):
username = authentication['name'] username = authentication['name']
res_map, usr_map = load_ctrl() res_map, usr_map = load_ctrl()
wanted_groups = [g for g, users in usr_map.items() if username in users] wanted_groups = [g for g, users in usr_map.items() if username in users]
if not wanted_groups: if not wanted_groups:
return authentication # 控制文件里没有就什么都不做 return authentication
# 确保组存在 for g in wanted_groups: # 确保组存在
for g in wanted_groups:
subprocess.run(["groupadd", "-f", g], check=False) subprocess.run(["groupadd", "-f", g], check=False)
# 系统账号不存在就创建TLJH 会自己建 home这里只确保用户存在
if subprocess.run(["id", "-u", username], capture_output=True).returncode != 0: if subprocess.run(["id", "-u", username], capture_output=True).returncode != 0:
subprocess.run([ subprocess.run([
"useradd", "-m", "-s", "/bin/bash", "useradd", "-m", "-s", "/bin/bash",
"-d", str(USER_HOME).format(username=username), username "-d", str(USER_HOME).format(username=username), username
], check=False) ], check=False)
# 加组(幂等) for g in wanted_groups: # 加组
for g in wanted_groups:
subprocess.run(["usermod", "-aG", g, username], check=False) subprocess.run(["usermod", "-aG", g, username], check=False)
return authentication return authentication
c.Authenticator.post_auth_hook = ensure_user_and_groups c.Authenticator.post_auth_hook = ensure_user_and_groups
# ---------- pre_spawn_hook只清 shared 并挂载 ---------- # ========== spawn 钩子:清 shared + 只读挂载 ==========
def prepare_user(spawner: Spawner): def prepare_user(spawner):
username = spawner.user.name username = spawner.user.name
user_dir = Path(str(USER_HOME).format(username=username)) user_dir = Path(str(USER_HOME).format(username=username))
# 1. 只清 shared 目录,保留用户其它文件 # 清 shared保留用户其它文件
shared = user_dir / "shared" shared = user_dir / "shared"
if shared.exists(): if shared.exists():
shutil.rmtree(shared) shutil.rmtree(shared)
shared.mkdir(parents=True, exist_ok=True) shared.mkdir(parents=True, exist_ok=True)
shutil.chown(shared, user=username, group=username) shutil.chown(shared, user=username, group=username)
# 2. 读控制表 # 读控制表
res_map, _ = load_ctrl() res_map, _ = load_ctrl()
# 3. 取系统组 # 获取用户系统组
try: try:
pwnam = pwd.getpwnam(username) pwnam = pwd.getpwnam(username)
gid_list = os.getgrouplist(username, pwnam.pw_gid) gid_list = os.getgrouplist(username, pwnam.pw_gid)
@@ -74,7 +80,7 @@ def prepare_user(spawner: Spawner):
return return
user_groups = {grp.getgrgid(g).gr_name for g in gid_list} user_groups = {grp.getgrgid(g).gr_name for g in gid_list}
# 4. 组装 bind_mountsSystemdSpawner 语法) # 组装 bind_mountsSystemdSpawner 语法)
bind_list = [] bind_list = []
for grp_name in user_groups: for grp_name in user_groups:
for res in res_map.get(grp_name, []): for res in res_map.get(grp_name, []):