tmp
This commit is contained in:
@@ -1,101 +1,32 @@
|
|||||||
"""
|
import shutil, configparser
|
||||||
按组挂载 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 pathlib import Path
|
||||||
from jupyterhub.spawner import Spawner
|
|
||||||
|
|
||||||
# ---------- 路径常量 ----------
|
# 仓库在host上的地址
|
||||||
SHARED_ROOT = Path("/home/zhangyi/jupyter-collection") # Git 资源仓库
|
SHARED_ROOT = Path("/home/zhangyi/jupyter-collection")
|
||||||
USER_HOME = Path("/home/jupyter-{username}") # 用户家目录模板
|
CTRL_FILE = SHARED_ROOT / ".hub" / "resource_map.ini"
|
||||||
CTRL_FILE = SHARED_ROOT / ".hub" / "resource_map.ini" # 组-资源-用户映射
|
|
||||||
|
|
||||||
# ---------- 工具:读控制表 ----------
|
def load_map():
|
||||||
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(",")] for g, v in cfg.items("map")}
|
# 用户名 -> [res1, res2, ...]
|
||||||
usr_map = {g: [u.strip() for u in v.split(",")] for g, v in cfg.items("users")}
|
return {u: [r.strip() for r in v.split(",")]
|
||||||
return res_map, usr_map
|
for u, v in cfg.items("users")}
|
||||||
|
|
||||||
# ========== 最早钩子:预创建家目录 ==========
|
def copy_resources(spawner):
|
||||||
def ensure_homedir(spawner):
|
|
||||||
"""systemd 启动前保证目录存在且属主正确"""
|
|
||||||
username = spawner.user.name
|
username = spawner.user.name
|
||||||
user_dir = Path(str(USER_HOME).format(username=username))
|
shared = Path(spawner.environment.get("HOME", f"/home/jupyter-{username}")) / "shared"
|
||||||
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
|
# 清空或新建 shared
|
||||||
|
|
||||||
# ========== 认证后钩子:自动建用户+加组 ==========
|
|
||||||
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))
|
|
||||||
|
|
||||||
# ① 确保家目录 700 + 属主正确(首次/后续都适用)
|
|
||||||
user_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
||||||
shutil.chown(user_dir, user=username, group=username)
|
|
||||||
|
|
||||||
# ② 只清 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)
|
|
||||||
|
|
||||||
# 读取控制表
|
# 按 ini 拷贝资源
|
||||||
res_map, _ = load_ctrl()
|
for res in load_map().get(username, []):
|
||||||
|
src = SHARED_ROOT / res
|
||||||
|
dst = shared / res
|
||||||
|
if src.is_dir():
|
||||||
|
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||||
|
|
||||||
# 获取用户系统组
|
# ========== 用户容器启动完成后执行 ==========
|
||||||
try:
|
c.Spawner.post_spawn_hook = copy_resources
|
||||||
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_mounts(SystemdSpawner 语法)
|
|
||||||
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
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
# 第一个表定义了不同的组别可读取的资源路径名称
|
# 这个表定义了每个用户的权限 即能拿到哪些文件夹 逗号分隔
|
||||||
# <组名> = <路径名>, <路径名>
|
|
||||||
[map]
|
|
||||||
PrincipleOfGeophysics = geophysics_basic
|
|
||||||
GeophysicalInversion = geophysics_basic, geophysics_advanced
|
|
||||||
jupyterhub-admins = geophysics_basic, geophysics_advanced
|
|
||||||
|
|
||||||
# 第二个表定义了每个组内的用户 用逗号区分
|
|
||||||
[users]
|
[users]
|
||||||
jupyterhub-admins = zhangyi
|
zhangyi = geophysics_basic, geophysics_advanced
|
||||||
PrincipleOfGeophysics = student1, student2, student3
|
student1 = geophysics_basic
|
||||||
GeophysicalInversion = student4, student5
|
student2 = geophysics_basic
|
||||||
|
student3 = geophysics_basic
|
||||||
|
student4 = geophysics_basic, geophysics_advanced
|
||||||
|
student5 = geophysics_basic, geophysics_advanced
|
||||||
Reference in New Issue
Block a user