服务器配置仅允许 git 用户使用 rsa 密钥登录,web 的目录及子目录和文件所有者为 www,755 权限,并且 git 已经添加到 www 组,在当前用户为 git 的情况下。

执行

rsync -avpz -e "ssh -i ~/.ssh/id_rsa -p 22" --exclude '.git' --exclude '.git/*' ./local/ git@server:/www/wwwroot/site/

提示

rsync: failed to set times on "/www/wwwroot/site/theme/images/left": Operation not permitted (1) rsync

报错的根因是:rsync 在 -a(归档)模式下会保留时间戳(-t),而“修改目录/文件时间戳”在 Linux 上需要所有者权限或特权(CAP_FOWNER)。

远端目录/文件的 owner=www,用的是 git 用户(虽在 www 组,但不是 owner),于是在设置时间戳时就出现:

rsync: failed to set times on ".../images/left": Operation not permitted (1)

常见解决方案:

方案 A(推荐):把站点目录做成“组可写 + 继承组”

适合多人协作、持续部署,安全且不需要 root。

一步到位修正现有权限:

# 1) 目录归属给 www 组(已是则可略过)
sudo chgrp -R www /www/wwwroot
# 2) 目录:组可写 + 设置 setgid 位(2),让新建文件/目录自动继承 www 组
sudo find /www/wwwroot -type d -exec chmod 2775 {} \;
# 3) 文件:组可写
sudo find /www/wwwroot -type f -exec chmod 664 {} \;
# 4)(可选更稳)用 ACL 让默认权限继承,防止将来被 umask/工具打回去
sudo setfacl -R -m g:www:rwx /www/wwwroot
sudo setfacl -R -m d:g:www:rwx /www/wwwroot

保证 git 的 umask 不破坏组写:

# 让新建文件默认 664 / 目录 775(umask 002)
# 如果 git 通过 shell、systemd 或 CI 跑,同步设置到对应环境
umask 002

rsync 命令示例(最常用)

rsync -avz --delete \
  --no-owner --no-group --chmod=Dg+w,ug+rX,Fg+w,ug+r \
  -e "ssh -i ~/.ssh/id_rsa -p 22" \
  ./local/ git@server:/www/wwwroot/site/

说明:

  • --no-owner --no-group:不去改属主属组(由目录继承 www 组)。
  • --chmod=...:确保落地权限至少满足组可写/可执行(对目录加 +x)。
  • --delete:目标端多余文件清理(按需用)。
  • 有了目录 2775(setgid)+ ACL,以后新文件也会自动属于 www 组且组可写,不再掉坑。

方案 B:保留 755,但用 sudo 提权并在 rsync 里改属主/权限

适合你不想改现有权限模型,但允许受控提权的场景。

  1. 允许 git 免密执行 rsync:
# /etc/sudoers.d/rsync (用 visudo 编辑)
git ALL=(root) NOPASSWD:/usr/bin/rsync
  1. rsync 命令(远端以 root 执行,落地即改属主/权限):
rsync -avz --delete \
  --chown=www:www --chmod=D2755,F0644 \
  --rsync-path="sudo rsync" \
  -e "ssh -i ~/.ssh/id_rsa -p 22" \
  ./local/ git@server:/www/wwwroot/site/

说明:

  • --rsync-path="sudo rsync":让远端 rsync 以 root 跑,才能生效 --chown
  • --chown=www:www:落地即属主为 www。
  • --chmod=D2755,F0644:目录 2755(含 setgid),文件 0644。

注意:这条路需要你信任 git 账号;请仅授予最小 sudo 权限。

额外排查/验证小抄

# 看 git 的有效身份
id git
# 逐级查看目录权限是否可写(w/x 很关键)
namei -l /www/wwwroot/site
# 看 ACL 是否就位
getfacl /www/wwwroot /www/wwwroot/site | sed -n '1,30p'

关于 --inplace 的提醒

有人会用 --inplace 来避免临时文件改名失败,但 它并不能绕过目录写权限,而且断传可能把目标文件写坏。不建议用它来“解决权限问题”。

TL;DR

  • 你要么把站点目录做成 组可写 + setgid +(可选)ACL(方案 A,最稳);
  • 要么在远端让 rsync 以 root 身份落地并 --chown=www:www(方案 B)。
  • 单纯把 git 加进 www 组,在 755 目录上依然无法写入——因为组没有写权限,这是设计使然。

附:让远端 rsync 以 www** 身份运行**

让远端 rsync 以 www 身份跑(--rsync-path="sudo -u www rsync"),并用 --chown/--chmod 规范落地权限。

让 rsync 落地时就是 owner=www,这样保留时间戳完全没障碍。

  1. 在目标机的 sudoers 里允许 git 以 www 运行 rsync(最小权限):
# 用 visudo 创建/编辑:
/etc/sudoers.d/rsync-www
# 内容:
git ALL=(www) NOPASSWD:/usr/bin/rsync
  1. 本地命令这样写:
rsync -avpz --delete \
  --chown=www:www --chmod=D2775,F664 \
  --rsync-path="sudo -u www rsync" \
  -e "ssh -i ~/.ssh/id_rsa -p 22" \
  --exclude '...' \
  ./local/ git@server:/www/wwwroot/site/
  • --rsync-path="sudo -u www rsync":远端以 www 执行 rsync(拥有者就是 www,可设 times)。
  • --chown=www:www:新文件直接归属 www(与站点一致)。
  • --chmod=D2775,F664:目录 2775(含 setgid,继承组),文件 664,确保组可写。
  • 你现在的 775 目录配合 setgid(2)更利于持续协作(新建内容继续属组 www)。