This commit is contained in:
2025-10-22 19:40:22 +08:00
parent 06dc713576
commit 5506b950e3

View File

@@ -1,15 +1,18 @@
"""
按组挂载 Git 资源(只读) + 自动建用户加组
资源路径 /home/zhangyi/jupyter-collection
控制文件 /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
# host端的仓库目录 # ---------- 基础路径 ----------
SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") # Git 仓库
# jyputerhub用户home目录
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"
# ========== 2. 读控制表 ========== # ---------- 工具:读控制表 ----------
def load_ctrl(): def load_ctrl():
"""返回 组->资源 和 组->用户 两个字典""" """返回 组->资源 和 组->用户 两个字典"""
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
@@ -20,26 +23,25 @@ def load_ctrl():
for g, v in cfg.items("users")} for g, v in cfg.items("users")}
return res_map, usr_map return res_map, usr_map
# ========== 3. post_auth_hook自动建用户+加组 ========== # ---------- post_auth_hook自动建用户+加组 ----------
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)
# 系统账号不存在创建(Hub 自己会创 home这里确保 shell 账号 # 系统账号不存在创建(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:
@@ -49,17 +51,17 @@ def ensure_user_and_groups(authenticator, handler, authentication):
c.Authenticator.post_auth_hook = ensure_user_and_groups c.Authenticator.post_auth_hook = ensure_user_and_groups
# ========== 4. pre_spawn_hook重建干净 home + 只读挂 Git 资源 ========== # ---------- pre_spawn_hook只清 shared 并挂载 ----------
def prepare_user(spawner: Spawner): def prepare_user(spawner: 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. 重建空 home保证无残留 # 1. 只清 shared 目录,保留用户其它文件
if user_dir.exists(): shared = user_dir / "shared"
shutil.rmtree(user_dir) if shared.exists():
user_dir.mkdir(mode=0o700, parents=True, exist_ok=True) shutil.rmtree(shared)
# 关键:让 systemd 能进去 shared.mkdir(parents=True, exist_ok=True)
shutil.chown(user_dir, user=username, group=username) shutil.chown(shared, user=username, group=username)
# 2. 读控制表 # 2. 读控制表
res_map, _ = load_ctrl() res_map, _ = load_ctrl()
@@ -72,10 +74,8 @@ 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. 挂载该用户该看的资源(只读 # 4. 组装 bind_mountsSystemdSpawner 语法
shared = user_dir / "shared" bind_list = []
shared.mkdir(exist_ok=True)
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, []):
res_path = SHARED_ROOT / res res_path = SHARED_ROOT / res