| workflow_payload |
name: Repair runner registration file permissions
name: Repair runner registration file permissions
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-runner-permissions.yml]
workflow_dispatch:
jobs:
repair:
name: repair
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Limit existing root-owned registration to owner read-write
run: |
set -eu
python3 <<'PY'
import datetime, json, os, pathlib, socket, stat, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_RECEIPT_BASE_REQUIRED')
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
stamp=datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')
target=base/('registration-permissions-'+runid+'-'+stamp+'.json')
def unit():
r=subprocess.run(['systemctl','show','gitea-runner.service','-p','User','-p','ActiveState','-p','MainPID'],capture_output=True,text=True,check=True,timeout=10)
return dict(row.split('=',1) for row in r.stdout.splitlines() if '=' in row)
before_unit=unit()
if before_unit.get('User') not in ['','root'] or before_unit.get('ActiveState')!='active' or not before_unit.get('MainPID','').isdigit():raise RuntimeError('ACTIVE_ROOT_RUNNER_REQUIRED')
path=pathlib.Path('/opt/gitea-runner/.runner')
parent=path.parent.lstat()
if not stat.S_ISDIR(parent.st_mode) or parent.st_uid!=0 or stat.S_IMODE(parent.st_mode)&0o022:raise RuntimeError('PROTECTED_REGISTRATION_DIRECTORY_REQUIRED')
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW)
try:
before=os.fstat(fd)
if not stat.S_ISREG(before.st_mode) or before.st_uid!=0 or before.st_gid!=0 or before.st_nlink!=1:raise RuntimeError('EXPECTED_ROOT_REGISTRATION_FILE_REQUIRED')
mode=stat.S_IMODE(before.st_mode)
if mode not in [0o644,0o600]:raise RuntimeError('UNEXPECTED_REGISTRATION_PERMISSIONS')
if mode!=0o600:os.fchmod(fd,0o600)
after=os.fstat(fd);current=path.lstat()
if (after.st_dev,after.st_ino)!=(current.st_dev,current.st_ino):raise RuntimeError('REGISTRATION_PATH_CHANGED')
if (after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns)!=(before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns):raise RuntimeError('REGISTRATION_CONTENT_METADATA_CHANGED')
if stat.S_IMODE(after.st_mode)!=0o600:raise RuntimeError('PERMISSION_REPAIR_FAILED')
finally:os.close(fd)
after_unit=unit()
if after_unit!=before_unit:raise RuntimeError('RUNNER_SERVICE_STATE_CHANGED')
report={'kind':'GiteaRunnerRegistrationPermissionRepair','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'path':str(path),'beforeMode':oct(mode),'afterMode':'0o600','ownerUid':0,'ownerGid':0,'changed':mode!=0o600,'contentRead':False,'contentWritten':False,'contentMetadataUnchanged':True,'runnerServiceBefore':before_unit,'runnerServiceAfter':after_unit,'serviceRestarted':False,'giteaBinaryOrDatabaseChanged':False,'fullExecutorIsolationAccepted':False}
with open(target,'x') as f:json.dump(report,f,indent=2);f.write('\n')
print('GITEA_RUNNER_PERMISSION_REPAIR_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
... |