| workflow_payload |
name: Runner maintenance storage preflight (read o name: Runner maintenance storage preflight (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-preflight.yml]
workflow_dispatch:
jobs:
preflight:
name: Verify actual storage capacity and schema metadata
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read storage sizes and public build metadata
run: |
set -eu
python3 <<'PY'
import configparser, datetime, hashlib, json, mmap, os, pathlib, socket, sqlite3, struct, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z': raise RuntimeError('EXPECTED_HOST_REQUIRED')
config_path=pathlib.Path('/home/git/gitea/config/app.ini')
if hashlib.sha256(config_path.read_bytes()).hexdigest()!='8923b93032ec5c303317b02a7011f756c0ee8e6c2756d4cd4286dd9b5c54d072': raise RuntimeError('CONFIG_CHANGED_REINVENTORY_REQUIRED')
report={'kind':'RunnerMaintenanceStoragePreflight','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'sizes':[],'storageSections':[]}
def command(args,timeout=40):
r=subprocess.run(args,stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout)
return r.returncode,r.stdout
for p in ['/home/git/gitea','/home/git/repositories','/usr/local/bin/data','/opt/gitea-runner','/root/gitea-ext','/root/gitea-code-review']:
if not pathlib.Path(p).exists(): continue
code,out=command(['du','-sx','--block-size=1',p])
report['sizes'].append({'path':p,'bytes':int(out.split()[0]) if code==0 else None,'exitCode':code})
st=os.statvfs('/')
report['rootFilesystem']={'availableBytes':st.f_bavail*st.f_frsize,'totalBytes':st.f_blocks*st.f_frsize}
cfg=configparser.RawConfigParser(strict=False);cfg.read(config_path)
for section in cfg.sections():
if section.lower().startswith(('storage','lfs','attachment','packages','actions')):
report['storageSections'].append({'section':section,'keys':[k for k in cfg[section] if not any(x in k.lower() for x in ['secret','password','token','access_key'])]})
db='/home/git/gitea/data/gitea.db'
connection=sqlite3.connect('file:'+db+'?mode=ro',uri=True,timeout=10)
connection.execute('PRAGMA query_only=ON')
tables={r[0] for r in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
report['database']={'path':db,'sqliteVersion':sqlite3.sqlite_version,'tableCount':len(tables),'hasAttemptTable':'action_run_attempt' in tables}
if 'version' in tables:
columns=[r[1] for r in connection.execute('PRAGMA table_info("version")')]
col=next((x for x in columns if x.lower()=='version'),None)
if col: report['database']['schemaVersions']=[r[0] for r in connection.execute('SELECT "'+col.replace('"','""')+'" FROM "version" LIMIT 5')]
report['database']['journalMode']=connection.execute('PRAGMA journal_mode').fetchone()[0]
connection.close()
def go_build(path):
try:
with open(path,'rb') as f, mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as data:
at=data.find(b'\xff Go buildinf:')
if at<0:return {'format':'not-found'}
ptr_size=data[at+14];flags=data[at+15]
if not flags&2:return {'format':'pointer-layout','pointerSize':ptr_size,'flags':flags}
pos=at+32
def readvar(pos):
value=0;shift=0
for i in range(10):
b=data[pos];pos+=1;value|=(b&127)<<shift
if b<128:return value,pos
shift+=7
raise ValueError('invalid-varint')
size,pos=readvar(pos);version=bytes(data[pos:pos+size]).decode(errors='replace');pos+=size
size,pos=readvar(pos);mod=bytes(data[pos:pos+size]).decode(errors='replace')
selected=[]
for line in mod.splitlines():
if line.startswith('path\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('mod\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('build\tvcs'): selected.append(line[:200])
return {'format':'inline','goVersion':version,'selectedModuleFields':selected}
except Exception as e:return {'errorClass':type(e).__name__}
report['giteaGoBuild']=go_build('/usr/local/bin/gitea')
report['runnerGoBuild']=go_build('/opt/gitea-runner/act-runner')
for p in ['/home/git/gitea/custom','/home/git/gitea/config','/usr/local/bin/data','/opt/gitea-runner']:
root=pathlib.Path(p)
if not root.is_dir():continue
children=[]
for child in sorted(root.iterdir()):
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','token','password','credential','private']):continue
if p=='/opt/gitea-runner' and child.name not in ['config.yaml','config.yml','act-runner','act_runner']:continue
children.append({'name':child.name,'directory':child.is_dir(),'bytes':child.stat().st_size})
report.setdefault('layoutMetadata',[]).append({'path':p,'children':children[:30]})
report['boundaries']={'serviceChanges':False,'dataWrites':False,'secretValuesPrinted':False,'backupsOrUpgrades':False}
print('RUNNER_MAINTENANCE_PREFLIGHT_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
... |