Files
MailUI4Agents/deploy/check-file-modes.sh
JianFeeeee b7dc9e90e6 fix(deploy): 权限政策按**文件类型分岔**开药方 —— 我上一版把 release-linux.sh 的执行位改掉了
## 我在上一个提交里弄坏了一个脚本

`client/electron/scripts/release-linux.sh` 原本是 **711**:
group/other 读位缺失 ⇒ 本判据判红(**判得对**),但我给的药方是无脑 `chmod 644`
⇒ **去掉执行位** —— 把"读位缺失"换成"不能执行",**比原来更坏**,
而判据随后报"通过",工作区显示这个文件被改(`M`)。是我在提交前扫 `git status` 时
看见那一行 `M client/electron/scripts/release-linux.sh` 才发现的 —— **不是判据发现的**。

已恢复 755(`git diff` 干净,因为它 git 记录是 100755)。

★ 教训:**一条判据说"这个文件被改错了"时,药方必须按文件类型分岔**;
只有一个药方(`chmod 644`)就会把另一类文件改坏,而它同样报"已修好"。
这与"判据的适用范围没写出来"同族,只是这次代价落在了**修的动作**上,不是读的动作上。

## 改法

可执行与否按 **git 记录的那个位**(`git ls-files -s` 的 `100755`)判,
不按当前文件系统权限 —— 否则"执行位被谁弄丢了"会被这条判据**追认**成正常。

三条区分力实测(都当场恢复 + `cmp` 校验):
1. 非可执行文件改 0600 ⇒ 红,文案"权限过严";
2. 可执行文件丢掉执行位 ⇒ 红,文案"**注意别去掉执行位**";
3. 可执行文件 711(= `release-linux.sh` 原来的形态)⇒ 红 —— 即**最初那次判红是对的**,
   错的是药方。

另外把计数循环改成读 `git ls-files -s` 一次(不再在循环里对每个文件调
`git ls-files --error-unmatch`,也不再让计数落在管道子 shell 里)。

验证:`check-file-modes.sh` 正常态 exit 0;三条变异行为如上;
`git status` 只剩本文件本身的改动。
2026-09-15 10:27:20 +08:00

89 lines
4.4 KiB
Bash
Executable File
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.

#!/usr/bin/env bash
# 源文件的权限位**政策**:不许比 0644 更严。
#
# # 为什么要这条(一个实测出来的实例)
#
# 这台机器上写文件的工具**不理会 umask**(umask 是 022,但它建的仍是 0600)。
# 后果不是"本地难看",而是它会**进生产**:`deploy/redeploy-plugin.sh` 用
# `cp -a "$SRC/." "$STAGING/"` 打快照,`-a` 保留权限位 ——
# 于是快照里 `src/paths.mjs`/`src/turn-cwd.mjs` 是 **0600**,而仓库是 0644。
#
# 两头都不报警:
# · `cmp` 只比**内容** ⇒ 报"逐字节相同"(实测就是这样);
# · `check-deploy-drift.mjs` 只比**内容**、且只在脚本上判**可执行位**(`mode & 0o111`)。
# ⇒ "线上与仓库不一致"里,**只有权限位这一类**完全没有判据看着。
#
# # 与漂移侧那条的分工(pi 评审 2026-09-15 §三 明确要求分开)
#
# · **本脚本 = 政策**:源文件不得比 0644 更严。它**不**看快照,
# 所以"两边都是 0600"这种情况它能抓到,而漂移侧永远抓不到(两边一致 ⇒ 恒绿)。
# · **漂移侧 = 一致性**:部署副本的权限位 = 仓库那一份。
# 它抓不到"两边都错"。
# 两条别合成一条:合成的结果是"看起来覆盖了、其实只覆盖一半"。
#
# # 为什么"更严"也算错
#
# 这些文件是**源码**,要被构建/测试/部署以不同身份读(`install.sh` 会切身份做可写性判定,
# 判据里还有以 nobody 跑的分支)。0600 在"跑的人恰好是属主"时不炸,一旦换身份就是
# EACCES —— 而那串报错看起来是**代码问题**。这条链一路上在消的就是这一类。
set -uo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO"
# 允许的最严权限:0644。比它更严(组/其他读位缺失)即判红。
# 例外:**可执行**脚本的 0755 不是"更严",它多了执行位、读位齐全,属正常。
# 允许的最严权限:0644(非可执行)/ 0755(可执行)。
#
# ★ 可执行与否按 **git 记录的那个位**判(`git ls-files -s` 的 100755),不按当前文件系统权限
# —— 否则"执行位被谁弄丢了"会被这条判据**追认**成正常。
#
# ★ 这条我第一版写错了,代价是**真的弄坏了一个脚本**:`client/electron/scripts/release-linux.sh`
# 原本是 **711**(group/other 读位缺失 ⇒ 本判据判红,判得对),但我给的药方是无脑
# `chmod 644` —— 那是**去掉执行位**,等于把"读位缺失"换成"不能执行",比原来更坏,
# 而判据随后报"通过"。⇒ 一条判据说"这个文件被改错了"时,**药方必须按文件类型分岔**;
# 只有一个药方就会把另一类文件改坏,且同样显示"已修好"。
fail=0
worst=""
count=0
while IFS= read -r line; do
gmode="${line%% *}"
f="${line##* }"
[ -n "$f" ] || continue
[ -f "$f" ] || continue
mode="$(stat -c '%a' "$f" 2>/dev/null)" || continue
m="${mode: -3}"
o="${m: -1}"; g="${m:1:1}"; u="${m:0:1}"
bad=0
# group 与 other 必须有读位
[ "$(( o & 4 ))" -eq 0 ] && bad=1
[ "$(( g & 4 ))" -eq 0 ] && bad=1
# git 记为可执行的,三处执行位都要在
if [ "$gmode" = "100755" ]; then
[ "$(( u & 1 ))" -eq 0 ] && bad=1
[ "$(( g & 1 ))" -eq 0 ] && bad=1
[ "$(( o & 1 ))" -eq 0 ] && bad=1
fi
[ "$bad" -eq 0 ] && continue
count=$((count + 1))
[ -z "$worst" ] && worst="$f"
if [ "$gmode" = "100755" ]; then
printf ' [FAIL] 权限不对:%s 是 %s(git 记为可执行 ⇒ 应为 0755,注意别去掉执行位)\n' "$f" "$mode" >&2
else
printf ' [FAIL] 权限过严:%s 是 %s(政策要求不严于 0644)\n' "$f" "$mode" >&2
fi
done < <(git ls-files -s | awk '{print $1" "$4}')
if [ "$count" -gt 0 ]; then
printf '\n 共 %d 个源文件权限不对(首个:%s)。\n' "$count" "$worst" >&2
printf ' 药方(**按类型分岔**):非可执行文件补齐到 644;git 记为 100755 的补齐到 755。\n' >&2
printf ' 原因:写文件的工具不理会 umask,而 `cp -a` 会把这个权限位带进生产快照,\n' >&2
printf ' 且 `cmp` / ① 内容判据都看不见它。\n' >&2
printf ' 这是**政策问题**(源文件应当可被构建/测试/部署以不同身份读),不是代码问题。\n' >&2
exit 1
fi
printf ' 源文件权限政策:通过(已跟踪文件都够读;git 记为可执行的都保留了执行位)\n'
exit 0