fix: 完善低权限备份恢复与只读部署密钥支持

这个提交包含在:
wikiboss
2026-10-28 18:02:03 +08:00
父节点 d5f108b0c5
当前提交 d339926490
共有 3 个文件被更改,包括 20 次插入 和 17 次删除

查看文件

@@ -14,7 +14,7 @@
- 程序:`/opt/grain-auto-deploy/auto_deploy.py`
- 配置:`/etc/grain-auto-deploy.json`
- 版本及文件清单:`/var/lib/grain-auto-deploy/backend.json`、`frontend.json`
- 备份:`/www/backup/grain-auto-deploy/`
- 备份:`/var/lib/grain-auto-deploy/backups/`
- 日志:`/var/log/grain-auto-deploy.log`,以及 `journalctl -u grain-auto-deploy.service`
运维命令:

查看文件

@@ -9,6 +9,7 @@ import logging
import os
from pathlib import Path, PurePosixPath
import shutil
import shlex
import subprocess
import tarfile
import tempfile
@@ -64,6 +65,17 @@ def healthcheck(url, host):
raise RuntimeError('Granary API health check failed')
def replace_file(source, live):
live.parent.mkdir(parents=True, exist_ok=True)
temp = live.with_name(live.name + '.deploy-tmp')
shutil.copyfile(str(source), str(temp))
mode = live.stat().st_mode & 0o777 if live.exists() else 0o644
os.chmod(str(temp), mode)
# The service runs as www; replacing an older root-uploaded file must not
# attempt to chown it back to root or require elevated permissions.
os.replace(str(temp), str(live))
def publish(config, repo):
state_dir = Path(config['state'])
cache = state_dir / (repo['name'] + '.git')
@@ -71,6 +83,10 @@ def publish(config, repo):
if not cache.exists():
run(['git', 'init', '--bare', str(cache)])
git = ['git', '--git-dir=' + str(cache)]
if repo.get('key'):
ssh = 'ssh -i {} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile={}'.format(
shlex.quote(repo['key']), shlex.quote(repo['known_hosts']))
git += ['-c', 'core.sshCommand=' + ssh]
run(git + ['fetch', '--depth=1', repo['url'], '+refs/heads/master:refs/heads/deploy'])
revision = run(git + ['rev-parse', 'refs/heads/deploy']).decode().strip()
state_path = state_dir / (repo['name'] + '.json')
@@ -136,20 +152,7 @@ def publish(config, repo):
try:
for name in changes:
live = destination(root, name)
live.parent.mkdir(parents=True, exist_ok=True)
temp = live.with_name(live.name + '.deploy-tmp')
shutil.copyfile(str(stage / name), str(temp))
if live.exists():
stat = live.stat()
os.chmod(str(temp), stat.st_mode & 0o777)
if hasattr(os, 'chown'):
os.chown(str(temp), stat.st_uid, stat.st_gid)
else:
os.chmod(str(temp), 0o644)
if hasattr(os, 'chown'):
parent = live.parent.stat()
os.chown(str(temp), parent.st_uid, parent.st_gid)
os.replace(str(temp), str(live))
replace_file(stage / name, live)
for name in deleted:
live = destination(root, name)
if live.is_file():
@@ -160,7 +163,7 @@ def publish(config, repo):
for record in reversed(records):
live = destination(root, record['path'])
if record['existed']:
shutil.copy2(str(backup / 'files' / record['path']), str(live))
replace_file(backup / 'files' / record['path'], live)
elif live.is_file():
live.unlink()
LOG.exception('%s rolled back; backup=%s', repo['name'], backup)

查看文件

@@ -14,6 +14,6 @@ NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/grain-auto-deploy /www/backup/grain-auto-deploy /www/wwwroot/grain.gynhc.com /var/log/grain-auto-deploy.log
ReadWritePaths=/var/lib/grain-auto-deploy /www/wwwroot/grain.gynhc.com /var/log/grain-auto-deploy.log
ReadOnlyPaths=/www/wwwroot/grain.gynhc.com/.git /www/wwwroot/grain.gynhc.com/application/database.php /www/wwwroot/grain.gynhc.com/application/config.php /www/wwwroot/grain.gynhc.com/application/extra/site.php
RestrictSUIDSGID=true