1. 命令速查(按场景分组,逐行可直接用)
══ S1 环境日常(建 / 装 / 列 / 删) ══
uv venv --python 3.13 /s500data/envs/xxx # 建(解释器自动从托管目录取)
uv pip install --python /s500data/envs/xxx/bin/python numpy # 装(一次一个包,别用 -r 批量)
uv pip list --python /s500data/envs/xxx/bin/python # 列
rm -rf /s500data/envs/xxx # 删(删目录即删环境)
══ S2 从清单重建(Windows conda yml → Linux) ══
/s500data/softnixos/wheel/rebuild_envs.sh py313 # 重建某环境
/s500data/softnixos/wheel/rebuild_envs.sh py313 py313B # 重建为新名字(副本)
tail -3 /s500data/envs/.import-logs/py313.failed.txt # 看失败清单
══ S3 私有包(索引里没有,必须传本地文件) ══
uv pip install --python <env>/bin/python /s500data/softnixos/wheel/imagetools-1.0.1.tar.gz
uv pip install --python <env>/bin/python /s500data/softnixos/wheel/mpa-sdk/mpa-sdk-d357dd9.zip
<env>/bin/python -c "import mpa,mpa._version as v;print(v.__version__)" # 版本自证
══ S4 给 jupyter 注册内核(A 环境的内核给 B 用) ══
<A>/bin/python -m ipykernel install --user --name <A> # 注册(用户级⇒所有环境可见)
<B>/bin/jupyter kernelspec list # B 能看到 A
<A>/bin/python -m jupyter kernelspec uninstall <A> # 撤销
══ S5 看真实占盘 / 回收空间 ══
du -sh /s500data/envs/xxx # 看起来多大(含硬链名字)
df -k / | awk 'NR==2{print $3}' # 建/删前后各记一次 ⇒ 差=真实增量
find /s500data/envs/xxx -type f -links +1 | wc -l # 硬链数(与缓存共享数据块)
find /s500data/envs/xxx -type f -links 1 | wc -l # 独立文件数(真属于它自己)
uv cache clean # 真正回收空间的地方
══ S6 老版本 Python(<3.8,如 3.6.8) ══
uv python list --all-versions | grep 3.6 # 清单里有没有
uv python install 3.6.8 # 装(本机已配好自备清单)
uv venv --python 3.6.8 /s500data/envs/py368 # 建环境
python3 -c "import json;print(len(json.load(open('/etc/uv/python-downloads.json'))))" # 合并清单条数
══ S7 查 / 导 ══
uv pip freeze --python <env>/bin/python > /s500data/envs/<env>.requirements.txt # 导出清单
grep -n UV_PYTHON_DOWNLOADS_JSON_URL /etc/set-environment # 关键变量落点
2. 场景要点(仅记关键词)
| 场景 | 要点 / 判据 |
|---|
| S1 日常 | 环境就是个普通 venv 目录;装包逐条装(批量一条冲突则全灭) |
| S2 重建 | 只取 Windows yml 的 pip: 段;脚本已内置全部铁律(逐包 / 私有包走本地 / 跳过 Windows 专属包) |
| S3 私有包 | 与公开包同名时会报 found on 镜像, but not at the requested version ⇒ 直接传本地文件路径 |
| S4 内核 | --user = 用户级 ⇒ 所有环境可见(A 给 B 用的原理);判据:kernel.json 的 argv[0] 指向 A 的 python |
| S5 占盘 | du 是假的(硬链名),真实看 df 前后差;要回收就 uv cache clean(删环境几乎不回收) |
| S6 老版本 | uv 内置下载单地板 3.8;3.6.8 靠自备制品 + 合并清单(见附录 A) |
3. 机制速通(五条)
| # | 机制 | 后果 |
|---|
| M1 | 环境 = 普通 venv 目录 | 无索引库、无全局状态;删目录即删环境 |
| M2 | 包文件从 ~/.cache/uv 硬链接进来 | 同版本再建一个环境几乎不占盘 |
| M3 | 解释器是符号链接 | <env>/bin/python → ~/.local/share/uv/python/cpython-版本-…/bin/python3.x |
| M4 | 私有包不走索引 | 必须传本地文件;不要为了“装得上”而放宽索引策略 |
| M5 | 托管下载单可整体替换(全量,不是追加) | 新增老版本须合并官方清单,否则其余版本全部失去 download available |
4. 本机事实
| 项 | 值 |
|---|
| uv / 环境根 / 缓存 | 0.11.21 / /s500data/envs/ / ~/.cache/uv(3.1G,与环境同一文件系统) |
| 托管 Python / 内核目录 | ~/.local/share/uv/python / ~/.local/share/jupyter/kernels/ |
| 私有包仓库 | /s500data/softnixos/wheel/(imagetools、mpa-sdk、自备 3.6.8 制品) |
| 清单源 / 重建脚本 / 日志 | conda_{py368,ju314,py313}.yml / rebuild_envs.sh / .import-logs/ |
| 四个环境 | py368 3.6.8/27 ・ ju314 3.14.7/137 ・ py313 3.13.15/275 ・ py313B 3.13.15/275 |
| 出网 / nix 接线 | 代理 127.0.0.1:7890 / /etc/nixos/modules/system/service/uv.nix |
5. 附录(实测记录、过程、踩坑)
A. 老版本 Python 3.6.8 自备制品
# ① 官方镜像 + 收拢依赖 + patchelf 成 $ORIGIN 相对 RPATH
docker run --rm -v <输出目录>:/out python:3.6.8-slim sh -c 'cp -a /usr/local /tmp/python; cp -a /tmp/python /out'
# ② 扫 lib/python3.6/lib-dynload 与 site-packages 下所有 .so 的 ldd 依赖
# 过滤掉 glibc 核心:libc / libm / libpthread / libdl / libutil / libgcc_s / libstdc++ / ld-linux
# 其余 cp 进 python/lib(实测 15 个:libssl.so.1.1 libcrypto libffi libsqlite3 libbz2 libexpat
# liblzma libz libncursesw libtinfo libpanelw libreadline libgdbm libgdbm_compat)
# ③ patchelf:bin/python3.6 → $ORIGIN/../lib ;lib/*.so → $ORIGIN ;lib/python3.6/*.so → $ORIGIN/../../../lib
# ④ tar -czf … -C pack python(顶层目录必须是 python/)
# ⑤ 自证:换到别的目录解包 → python3.6 -V / import ssl,sqlite3 / 建 venv
两条硬约束:不能把 glibc 核心库拷进 lib(启动会加载错 libc ⇒ 段错误);必须 patchelf 成 $ORIGIN 相对路径(uv 会解包到自己的目录,绝对 RPATH 必失效)。
清单合并:uv 里生成 /etc/uv/python-downloads.json=官方清单(pkgs.fetchurl + SRI hash)+ 一条 3.6.8(url=file://<制品>、sha256),由 environment.variables.UV_PYTHON_DOWNLOADS_JSON_URL 指向。
实测结果:制品 25MB(sha256 bfd8332c…2fd4,换目录解包可运行);uv python install 3.6.8 = Installed in 291ms;uv venv --python 3.6.8 ⇒ 3.6.8 + import ssl/sqlite3 正常;官方 3.8~3.14 条目仍在(并入而非替换)。
关键变量:UV_PYTHON_DOWNLOADS_JSON_URL(整体替换下载单,唯一能新增老版本的口子)/UV_PYTHON_INSTALL_MIRROR(只换基址)/UV_PYTHON_DOWNLOADS(automatic/manual/never)。
B. 硬链占盘实测(py313B)
du -sh py313B 1.9G 看起来
df -k / 建前后差 +22 MB 真的写了(约 1.2%)
df -i / 建前后差 +5277
硬链(-links +1) 34949 数据块在 ~/.cache/uv,只有一个备份
独立文件(-links 1) 968 个 / 5.7 MiB
符号链接 4 个(bin/python 等)
三个前提:同一文件系统;版本完全相同;默认链接模式(别用 --link-mode=copy)。一次 du -sh ~/.cache/uv /s500data/envs 可看“缓存+环境”去重后的真实合计(实测环境侧仅 201M)。
C. 报错 → 根因 → 动作
| 报错 | 根因 | 动作 |
|---|
| No solution found … unsatisfiable | 批量装时一条冲突连坐整批(例:jupyterlab 4.6.4 要 jupyter-server>=2.19,清单钉了 2.17) | 改逐包装 |
| …was found on 镜像, but not at the requested version | 私有包与公开包同名(imagetools:索引那个 1.0.0 作者是 zhaoxuhui) | 传本地文件 |
| No download found for request: cpython-3.x.y | 该版本不在托管下载单里(<3.8) | 见附录 A |
| 环境“占” 1.9G | du 看到的是硬链名 | 看 df 前后差 |
D. 执行脚本时踩过的坑(已修)
pgrep -f <脚本名> 会匹配到自己的命令行 ⇒ 等待循环死循环,后续步骤全不执行。- 向
/etc/nixos/backup 这类 root 目录备份必须 sudo,且备份后要断言文件存在(否则“静默未备份”)。 - 系统自带 python3 无第三方库(PyYAML/pandas)⇒ 校验 yaml、跑仓库配置要用目标环境的 python。
- nix
runCommand 里带引号 heredoc 时 $out 不展开 ⇒ 改用 python3 - "$out" + sys.argv[1]。 pkgs.fetchurl 的 hash 必须 SRI(sha256-<base64>);裸 base32 报 does not include a type。- 验收命令不要
2>&1 >/dev/null、不要 tail -N 截断:真错误被吞后只能反复重跑。
E. 边界与原则
- 项目自身的产物归项目管:如 mcp-portfolio-algo 的
savedata/sdk/ 是构建/调试产物,位置由项目配置 api.sdk_local 决定;系统层不擅自搬走或删除。 - 后续若在 agent 工具 shell 里读
UV_PYTHON_DOWNLOADS_JSON_URL 为空,先查 __ETC_PROFILE_SOURCED,用 env -i bash -lc 复测再下结论。
6. 相关文件
- 重建脚本
/s500data/softnixos/wheel/rebuild_envs.sh;硬链说明 /s500data/envs/.import-logs/py313B说明.txt - 清单源
/s500data/envs/conda_{py368,ju314,py313}.yml;日志 /s500data/envs/.import-logs/ - 私有包仓库
/s500data/softnixos/wheel/;nix 接线 /etc/nixos/modules/system/service/uv.nix - 同类经验已收录为 skill
uv-python-env-ops(含 references/python-old-version-artifact.md)