Files
jupyter-collection/.hub/jupyter_config_backup.py
2025-10-22 20:53:55 +08:00

96 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
按组挂载 Git 资源(只读) + 自动建用户加组
资源路径 /home/zhangyi/jupyter-collection
控制文件 /home/zhangyi/jupyter-collection/.hub/resource_map.ini
"""
import os, grp, pwd, shutil, subprocess, configparser
from pathlib import Path
from jupyterhub.spawner import Spawner
# ---------- 路径常量 ----------
SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") # Git 资源仓库
USER_HOME = Path("/home/jupyter-{username}") # 用户家目录模板
CTRL_FILE = SHARED_ROOT / ".hub" / "resource_map.ini" # 组-资源-用户映射
# ---------- 工具:读控制表 ----------
def load_ctrl():
"""返回 (组->资源, 组->用户) 两个 dict"""
cfg = configparser.ConfigParser()
cfg.read(CTRL_FILE)
res_map = {g: [r.strip() for r in v.split(",")] for g, v in cfg.items("map")}
usr_map = {g: [u.strip() for u in v.split(",")] for g, v in cfg.items("users")}
return res_map, usr_map
# ========== 最早钩子:预创建家目录 ==========
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):
username = authentication['name']
res_map, usr_map = load_ctrl()
wanted_groups = [g for g, users in usr_map.items() if username in users]
if not wanted_groups:
return authentication
for g in wanted_groups: # 确保组存在
subprocess.run(["groupadd", "-f", g], check=False)
if subprocess.run(["id", "-u", username], capture_output=True).returncode != 0:
subprocess.run([
"useradd", "-m", "-s", "/bin/bash",
"-d", str(USER_HOME).format(username=username), username
], check=False)
for g in wanted_groups: # 加组
subprocess.run(["usermod", "-aG", g, username], check=False)
return authentication
c.Authenticator.post_auth_hook = ensure_user_and_groups
# ========== spawn 钩子:清 shared + 只读挂载 ==========
def prepare_user(spawner):
username = spawner.user.name
user_dir = Path(str(USER_HOME).format(username=username))
# 仅清 shared保留用户其它文件
shared = user_dir / "shared"
if shared.exists():
shutil.rmtree(shared)
shared.mkdir(parents=True, exist_ok=True)
shutil.chown(shared, user=username, group=username)
# 读取控制表
res_map, _ = load_ctrl()
# 获取用户系统组
try:
pwnam = pwd.getpwnam(username)
gid_list = os.getgrouplist(username, pwnam.pw_gid)
except (KeyError, OSError):
return
user_groups = {grp.getgrgid(g).gr_name for g in gid_list}
# 组装 bind_mountsSystemdSpawner 语法)
bind_list = []
for grp_name in user_groups:
for res in res_map.get(grp_name, []):
res_path = SHARED_ROOT / res
if res_path.is_dir():
bind_list.append({
'source': str(res_path),
'target': str(shared / res),
'read-only': True,
})
spawner.bind_mounts = bind_list
c.Spawner.pre_spawn_hook = prepare_user