fix: 完善低权限备份恢复与只读部署密钥支持
这个提交包含在:
@@ -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
|
||||
|
||||
在新工单中引用
屏蔽一个用户