工作区、缓存与权限
管理员能构建,普通账号为什么失败
项目放在哪里、依赖缓存由谁写、测试临时文件落到哪块磁盘,看起来只是个人习惯,最终却会改变构建性能、文件监听、大小写语义、权限、磁盘容量和清理安全。Windows 与 WSL 并存时,同一棵源码被两侧工具交替修改,常会出现“管理员能构建、普通用户失败”“Git 状态反复变化”“依赖安装极慢”“删除缓存后丢了数据”。
这些现象通常不是一个权限位造成的,而是四个对象混在了一起:权威源码、可重建缓存、可重建输出和必须保留的持久数据。管理员构建会继续制造管理员拥有的缓存;递归 777 或“Everyone 完全控制”会扩大攻击面;不辨类型地清缓存,则可能把本地数据库、离线制品或密钥一起删除。正确处理顺序是先确定主文件系统和写入者,再按可重建性分类,最后才修复最小目录或执行清理。
服务器磁盘、生产日志和服务账号应遵守部署运维流程;数据库 volume、对象存储数据和镜像仓库也有自己的备份恢复责任。它们一旦出现在开发目录盘点中,应被标记为持久数据并从通用 clean 中排除。
先冻结现场再移动或清理
先不要移动项目,也不要执行递归删除或递归授权。选择一个真实但可恢复的开发项目,记录当前状态:
操作系统与文件系统:
项目真实路径:
主要编辑器运行侧:
主要 Git 运行侧:
主要构建运行侧:
当前普通账号:
缓存与临时目录:
当前磁盘可用空间:源码必须已经推送到受控远端,未提交改动应单独确认。若目录包含本地数据库、上传文件、私钥、证书、离线制品或无法重建的数据,先停止并按其数据 owner 的备份恢复规则处理;它们不是“构建垃圾”。
开始前还要识别四类路径:
| 类型 | 示例 | 默认处置原则 |
|---|---|---|
| 权威源码 | Git 工作区、项目配置、迁移脚本 | 不因构建失败而删除 |
| 可重建缓存 | Maven/Gradle/npm/pip 下载缓存 | 可按工具和范围清理,清理后必须重建验证 |
| 可重建输出 | dist、build、target、覆盖率与测试报告 | 由项目脚本清理,先确认真实路径 |
| 持久数据 | 数据库 volume、密钥、上传文件、本地制品 | 禁止套用通用清理命令 |
目录治理通常不需要安装新工具,需要启用的是一套明确的放置和验证规则。
选择主文件系统
先做一个二选一决策。Microsoft 的跨文件系统建议是:Linux 命令行主导的项目存放在 WSL 文件系统,Windows 工具主导的项目存放在 Windows 文件系统。这里的重点不是路径美观,而是让主要构建链贴近自己的文件系统语义:
| 主要工作方式 | 推荐主工作区 | 主要工具侧 |
|---|---|---|
| Windows IDE、Windows Git、Windows SDK 为主 | NTFS 用户目录下的专用工作区 | Windows |
| WSL/Linux CLI、Linux 容器与 Linux 构建链为主 | WSL 发行版的 Linux home 下 | WSL/Linux |
例如 Linux 主工作区可放在 ~/work/<project>,Windows 主工作区可放在用户拥有的 C:\work\<project> 或组织批准目录。路径不要位于系统目录、需要持续提权的目录、同步盘冲突区或未知网络盘。
这不是说另一侧完全不能访问,而是明确谁拥有主要编辑、Git 和构建责任。Windows 应通过 \\wsl.localhost\<distro>\home\... 访问 WSL 文件,而不是直接操作 WSL 虚拟磁盘文件;不要让 Windows Git 与 WSL Git 同时管理同一工作区。
建立目录分区
至少划分以下对象:
workspace/ # 权威源码与项目配置
tool-cache/ # 可重建的用户级依赖缓存
project-output/ # 项目可重建输出
temp/ # 有生命周期的临时文件
logs/ # 可轮换、可脱敏的本地日志
local-data/ # 必须另行治理的持久数据,不纳入通用清理它们不一定物理集中在一个目录,但必须能被盘点、分类和清理。运行时、IDE 和构建工具是否允许重定向目录,应以官方配置为准。
工作区主侧与跨侧访问
WSL 项目由 Linux 构建时,源码、依赖安装和文件监听应尽量都在 Linux 文件系统执行。把 Node.js 大型依赖树长期放在 /mnt/c,会引入跨文件系统调用开销、权限映射、大小写和监听差异。
NTFS 项目由 Windows 构建时,避免在 WSL 中对同一棵树运行另一套 Git、包管理器或格式化器。必须跨侧调用时,先验证以下行为:
只改变文件名大小写时 Git 是否正确识别;可执行位、符号链接和行尾是否保持预期;文件监听、热更新和依赖安装是否稳定;
生成文件的 owner/ACL 是否仍允许主要账号修改;IDE 与构建器是否解析到同一真实路径。
WSL 与 NTFS 权限
/mnt/c 的 WSL 权限规则由 Windows ACL 与 DrvFS 挂载选项共同决定。Linux mode bits 不能代表完整权限;启用 metadata 后可以保存 UID、GID 和 mode,但也不能突破 Windows 权限。出现“chmod 777 仍失败”时,应回到 Windows ACL、文件占用、只读属性和安全软件策略取证。
Linux 文件系统默认区分大小写,Windows 目录通常不区分;NTFS 目录可改变大小写属性,但跨侧大小写行为会影响 Git、编辑器和构建工具。不要为了让一个项目运行而全局改变 WSL automount、整盘大小写语义或用户目录 ACL。任何此类变更都应先在专用测试目录验证 Windows 工具和备份软件兼容性。
缓存、临时与日志目录
先查询实际路径:
$env:TEMP
$env:TMP
npm config get cache
python -m pip cache dirprintf 'TMPDIR=%s\n' "${TMPDIR:-/tmp}"
npm config get cache
python -m pip cache dirMaven 配置参考给出的默认本地仓库通常是 ~/.m2/repository,但仍应检查 settings.xml 是否修改 localRepository。Gradle 目录布局区分 GRADLE_USER_HOME 与项目 .gradle:前者包含全局配置、缓存、wrapper、daemon 与日志,后者只服务当前项目。清理全局目录会影响所有项目,也可能与仍在运行的 daemon 竞争。
npm 缓存位置由配置决定,应通过 npm config get cache 查询;npm cache 文档把它定义为可校验、可重新获取的内容寻址缓存,优先使用 npm cache verify,只有回收磁盘等明确目的才考虑清理。pip 缓存的内部布局同样不是接口,应按 pip 缓存命令查看和管理。
项目应在 .gitignore 或构建配置中排除可重建输出、覆盖率、日志和测试临时文件,但不能用宽泛规则误伤源码、样例配置或迁移文件。日志目录应定义最大保留量与脱敏要求;缓存目录应定义容量预算和清理入口。
权限基线
日常构建由普通账号执行。提权只用于修改系统状态,不能用管理员/root 构建“初始化”用户缓存,否则后续普通账号会继承无法写入的 owner。
Linux/WSL 可先观察而非修改:
id
pwd
stat -c '%U:%G %a %n' . 2>/dev/null || stat -f '%Su:%Sg %Sp %N' .
find . -maxdepth 2 ! -user "$(id -un)" -print 2>/dev/null | head -50Windows 可查看 ACL:
$workspace = (Resolve-Path -LiteralPath '.').Path
Get-Acl -LiteralPath $workspace | Format-List Owner,AccessToString
icacls $workspace /verify /T /Cicacls还能保存和恢复 DACL 证据,但递归命令可能很慢且输出包含用户名、路径,应限定到明确目录并在共享前脱敏。
最小验证需要同时证明“能写”“能构建”“能清理”“能恢复”,而不是只创建一个文件。
路径验证:打印工作区真实路径、文件系统类型、当前用户、磁盘可用空间和主要工具路径。写入验证:普通账号在项目指定临时目录创建、重命名并删除一个测试文件。项目验证:执行项目官方的依赖安装、最小测试或构建命令,记录输出目录和缓存命中位置。
清理验证:只通过项目定义的 clean 命令删除可重建输出,确认源码和本地持久数据不受影响。重建验证:清理后重新执行最小构建,结果与清理前一致。
Windows 可先收集路径证据:
$workspace = (Resolve-Path -LiteralPath '.').Path
$workspace
[System.IO.DriveInfo]::GetDrives() |
Where-Object { $_.IsReady } |
Select-Object Name,DriveFormat,AvailableFreeSpace
whoamiWSL/Linux/macOS 可使用:
pwd -P
id
df -T . 2>/dev/null || df -h .
mount | head成功标准包括:普通账号完成全部动作;项目主要工具来自预期系统侧;构建输出和缓存位置可解释;清理目标经过真实路径校验;清理后源码状态不变并可重新构建。
正向实验:证明输出可删除、项目可重建
先用项目自己的构建命令生成输出,再记录 Git 状态和输出目录。下面用 npm 项目示意,其他构建链替换为仓库声明的等价命令:
npm ci
npm test
npm run build
git status --short
du -sh -- ./dist 2>/dev/null || true随后只运行仓库提供的 clean 命令,不直接猜测缓存目录:
npm run clean
git status --short
npm run build
git status --short两次 Git 状态应一致且没有源码被删除,clean 后输出目录消失,重建后制品重新出现。若重建依赖旧机缓存中独有的包,这不是“缓存不能删”,而是 lockfile、制品源或离线供应链已经不可重建,应先修复依赖来源。
反向实验:制造最小权限拒绝
权限实验只在新建测试目录进行,绝不能对真实工作区递归执行。Linux、macOS 或 WSL 的普通账号可以用只读目录稳定暴露写入失败,再恢复原 mode:
probe="$(mktemp -d)"
chmod 0555 "$probe"
touch "$probe/should-fail"
chmod 0700 "$probe"
touch "$probe/recovered"
rm -rf -- "$probe"第三条预期返回 Permission denied,恢复后应能创建文件。若使用 root 执行,实验失去判别力;若恢复后仍失败,则继续检查 owner、ACL、挂载只读、EDR 或文件占用。这个反例证明“拒绝发生在写入层”,并不证明真实项目应该放大权限。
Windows 可在普通账号拥有的临时目录中通过安全属性界面或受控 ACL 测试构造拒绝,但必须先用 icacls /save 保存 ACL,并在同一测试目录完成恢复。企业受管设备上若 ACL 由策略下发,应停止修改并保留策略事件。
缓存实验:先验证,再决定是否清理
npm 提供了不删除缓存的完整性检查:
npm config get cache
npm cache verify预期输出包含已验证内容与垃圾回收结果,退出码为 0。它不能证明上游 registry 可用,因此还要在 lockfile 存在的项目中执行一次 npm ci。若 verify 成功而离线重建失败,说明缓存并未包含全部依赖;若 verify 报完整性问题而联网重建成功,说明内容可由上游恢复。两种现象对应不同处置,不能统一成“删掉所有缓存”。
并发与锁:先停写入者
依赖缓存和项目输出可能正被 IDE、测试进程、Gradle daemon 或包管理器写入。Gradle 依赖缓存使用文件锁协调可通信的 Gradle 进程,并明确限制容器等不能互相通信的并发场景。清理前先确认相关进程,等待其正常结束或用工具自己的停止命令,例如 gradle --stop;不要删除仍被持有的 lock 文件来强行解锁。Linux/macOS 可对明确路径使用 lsof +D <目录>,Windows 可用资源监视器或 Process Explorer 查询句柄。
锁文件消失不等于事务完整。若清理与写入并发,常见证据是临时文件残留、索引损坏、依赖解压不完整或 daemon 重复下载。恢复顺序应是停止写入者、保留故障目录用于取证、把旧目录原子重命名为隔离副本、由工具创建新目录并完成最小构建,最后再删除隔离副本。
项目应把目录契约写进仓库,而不是依赖口头约定。最低建议包含:
.gitignore
.editorconfig
docs/development/workspace.md
scripts/clean.*
scripts/doctor.*workspace.md 应说明支持的主文件系统、是否支持 WSL、推荐工作区位置、缓存与日志入口、不可删除目录和磁盘预算。doctor 脚本只做只读检查:真实路径、系统侧、运行时、可写性、剩余空间、大小写风险和异常 owner;不要在诊断脚本中静默递归授权或删除。
clean 脚本必须拥有保护条件:
目标来自项目根下的明确相对目录,而不是用户输入的任意路径;删除前解析为绝对路径,并确认仍位于项目根内;拒绝空路径、根目录、用户主目录、工作区上级目录和挂载点;
默认只删除可重建输出,清缓存需要独立显式参数;输出将删除的目标和占用空间,失败时保留错误证据;删除后执行 Git 状态与最小重建验证。
CI 工作区可以是一次性的,但不能把 CI 的 root 权限和容器内路径假设带回开发机。CI 缓存也应有独立 key、版本和淘汰策略,不与个人本地缓存共享物理目录。
查询缓存而不是猜路径
npm config get cache
python -m pip cache dir
python -m pip cache infoMaven 可查看用户 settings,Gradle 可记录 GRADLE_USER_HOME:
printf 'GRADLE_USER_HOME=%s\n' "${GRADLE_USER_HOME:-$HOME/.gradle}"不要根据默认路径直接删除这些目录。先确认当前工具实际配置、目标大小、是否有并发构建和是否包含无法重建内容。
查看磁盘占用
PowerShell 可针对明确目录测量文件总量:
$target = (Resolve-Path -LiteralPath '.\build' -ErrorAction Stop).Path
Get-ChildItem -LiteralPath $target -File -Recurse -Force |
Measure-Object -Property Length -SumLinux/macOS 可使用:
du -sh -- ./build 2>/dev/null始终显式写出目标,避免在不确定当前目录时使用通配符。
WSL 2 的 Linux 文件位于发行版虚拟磁盘中,Linux 侧 df 的可用空间与 Windows 宿主卷剩余空间是两份证据。Microsoft 的 WSL 磁盘说明指出 VHD 会按需增长;因此只看发行版内的容量上限,不能证明宿主磁盘还有空间。容量告警至少同时采集发行版文件系统使用率、宿主卷可用空间与 VHD 实际占用,并按趋势而不是单次截图判断。
保存 Windows ACL 证据
在组织策略允许时,可先对明确工作区保存 ACL:
$workspace = (Resolve-Path -LiteralPath 'C:\work\project' -ErrorAction Stop).Path
icacls $workspace /save .\workspace-acl.txt /T /C
icacls $workspace /verify /T /CACL 文件可能包含用户名和内部路径,不应提交公开仓库。恢复前必须在测试副本校验,并理解继承和所有者变化。
| 现象 | 证据 | 判断 | 修复与再验证 |
|---|---|---|---|
| 管理员能构建,普通用户失败 | owner、ACL、缓存文件创建者 | 曾用提权账号生成工作区或缓存 | 先备份 ACL,再只修复受影响目录,普通账号重建 |
| WSL 中依赖安装极慢 | 项目真实路径、挂载类型、IO 行为 | Linux 工具在 /mnt/c 跨文件系统运行 | 迁移测试副本到 Linux FS,对比构建后再决定 |
| Git 只改大小写不生效 | 文件系统大小写属性、Git 状态 | Windows/WSL 大小写语义不同 | 用受控两步重命名并验证跨侧工具 |
| 热更新或文件监听丢事件 | 编辑器侧、构建侧、项目路径 | 跨侧监听或同步盘干扰 | 统一主侧,在本地专用工作区复测 |
chmod 后仍无权限 | Windows ACL、DrvFS metadata、文件占用 | Linux mode 不是最终权限 | 在 Windows 侧查 ACL/占用,不继续放大 mode |
| 缓存清理后依赖无法恢复 | 代理、仓库可用性、lockfile | 把缓存当作离线备份或依赖未锁定 | 恢复网络/镜像源,修复项目声明后重建 |
| 系统盘持续增长 | 工具查询到的缓存、日志、测试产物 | 多类目录无预算和保留策略 | 按类型清理,建立容量阈值和 owner |
| clean 命令删到源码或数据 | 删除目标解析、脚本保护条件 | 宽泛通配符或路径变量为空 | 立即停止写入,走恢复流程,修复脚本保护条件 |
| CI 成功、本机失败 | 两侧 OS、文件系统、用户和缓存 | CI 的 root/容器环境掩盖本机权限 | 用普通账号和声明式 doctor 对齐关键差异 |
涉及损坏文件系统、WSL 虚拟磁盘修复、误删持久数据或生产目录时,停止通用排障,转交部署运维或数据 owner。不要继续运行“修复权限”和“清缓存”命令扩大损失。
目录本身也是安全边界。用户缓存可能保存私有包元数据、仓库地址、构建日志和临时凭证;IDE 索引、测试报告、浏览器状态和崩溃转储可能包含源码与业务数据。
工作区使用普通账号拥有,不把“Everyone 完全控制”或 chmod -R 777 作为修复。不把真实凭证、私钥、登录状态和生产数据放进可被通用 clean 删除或归档的目录。缓存、日志和临时目录不应默认上传到工单、制品或 AI 上下文;共享前先扫描并脱敏。
ACL 修复先记录所有者、继承和现有条目,再缩小到最小目录;不能直接对用户主目录或整块磁盘递归授权。提权生成的文件要找出产生它的命令和目录,修复工作流后再修复 owner,避免问题复发。私有包缓存不等同于凭证保险箱;访问 Token 应由凭证工具管理并支持轮换撤销。
若安全软件、受控文件夹访问或 MDM 策略拒绝写入,应保留策略证据并由设备管理 owner 处理,不能通过停用安全软件解决。
团队应定义“支持范围”,不要求所有人使用同一路径字符串。一个可执行基线至少包括:
| 基线对象 | 团队决策 |
|---|---|
| 主文件系统 | Windows 主侧或 WSL 主侧的支持组合 |
| 工作区位置 | 允许目录、禁止目录、同步盘与网络盘边界 |
| 运行账号 | 普通账号构建,哪些动作允许按需提权 |
| 缓存目录 | 查询方式、容量预算、清理工具和离线边界 |
| 临时与日志 | 保留时间、最大容量、脱敏和忽略提交规则 |
| 权限修复 | owner、取证项、审批、回退和升级入口 |
| 清理脚本 | 保护条件、dry-run/预览、重建验证和维护 owner |
新机验收应使用一个真实项目完成 clone、依赖安装、构建、测试、清理和重建;换机验收还要证明未依赖旧机未声明缓存。团队应定期检查系统盘剩余空间、异常大缓存、无人维护脚本和失效忽略规则。
对离线或受限网络团队,缓存可以提高可用性,但必须另建可追溯的内部制品源或离线包流程。个人 .m2、.gradle 或 npm cache 不能承担团队备份和供应链来源证明。
主文件系统是架构决策
Windows 与 WSL 的选择影响路径语义、IO、文件监听、权限和工具来源。把源码放哪边不是偏好投票,而应根据主要构建链决定。若团队允许两种模式,就必须分别验收并明确不支持的交叉组合;否则同一故障会在不同机器表现成完全不同的症状。
双侧工具会制造隐性漂移
Windows Git 与 WSL Git、Windows Node 与 WSL Node、两套 IDE 插件可能对同一工作区采用不同的行尾、大小写、可执行位和依赖二进制。判断标准不是“两边都能打开目录”,而是指定主侧完成安装、构建、Git 状态和文件监听后,另一侧只做被明确支持的访问。
权限放大掩盖根因
递归 777 或 Windows 全盘完全控制会暂时消除写入错误,也会破坏最小权限、掩盖错误 owner,并让恶意或误操作进程修改更多文件。正确路径是确定第一个拒绝写入的目录、查明创建者和继承来源、修复最小范围,再用普通账号重建。
缓存不是数据保护机制
缓存可以删除并重建,前提是上游制品仍可访问、版本已锁定、来源可追溯。若某个依赖只存在于个人缓存,问题不是“不能清缓存”,而是供应链已经不可重建。此时应先修复制品发布和依赖声明,不能把个人磁盘变成长期唯一来源。
清理脚本本身是高风险工具
路径变量为空、符号链接跳出项目根、挂载点重叠、工作目录错误和通配符扩张都可能让清理越界。任何递归删除前都应解析真实绝对路径并验证其位于允许根目录中;对符号链接、junction 和挂载点要单独拒绝或显式处理。安全脚本默认先输出计划,删除前记录目标清单与 Git 状态,删除后再用 Git 状态和最小重建证明没有误伤。
回滚不等于“再跑一次安装”。删除前若把目标原子重命名为同文件系统中的隔离目录,重建失败时可以先停进程、移走新目录,再把隔离目录改回原名;确认新目录稳定后才清除隔离副本。跨卷移动、已被并发写入或包含持久数据时不能承诺这种回滚语义,应进入备份恢复流程。
容量问题必须按类型治理
依赖缓存、IDE 索引、容器镜像、本地数据库、浏览器产物和日志增长方式不同。一个“清理全部开发缓存”的脚本无法安全覆盖它们。团队应按 owner、可重建性、容量阈值和清理成本分层;Docker 数据根、数据库 volume 和制品目录必须转交对应工具文章或部署运维治理。
已明确项目的主文件系统、主要编辑侧、Git 侧和构建侧。WSL/Linux 主构建项目优先位于 Linux 文件系统,Windows 主构建项目位于 NTFS 支持目录。没有让 Windows 与 WSL 两套 Git/包管理器同时管理同一工作区。
已区分权威源码、可重建缓存、可重建输出、临时日志和持久数据。Maven、Gradle、npm、pip 缓存位置通过当前工具查询或配置确认。普通账号可创建、构建、清理和重建,日常流程不依赖管理员/root。
权限问题已先记录 owner、ACL/mode、继承和文件占用,没有递归放大权限。清理前已解析真实绝对路径并确认目标位于允许根目录内。清理脚本拒绝根目录、用户主目录、工作区上级、挂载点和空路径。
通用清理不包含数据库、容器数据根、密钥、上传文件和本地制品。日志、测试产物和缓存有忽略提交、脱敏、容量与保留规则。清理后已检查 Git 状态并完成最小重建。
文件系统损坏、误删持久数据和生产权限问题已转交对应 owner。企业策略、EDR、加密盘和受控文件夹造成的拒绝已保留证据并交给设备管理 owner。
