Files
kefu/deploy/knowledge-20260916-100045/05-verify.sh
T
2026-09-21 10:34:06 +08:00

59 lines
4.5 KiB
Bash

set -euo pipefail
release=/home/ps/knowledge-20260916-100045
docker exec -i im-admin-api-1 python -u - <<'PY' | tee "$release/after.json"
import sqlite3,json,time
started=time.monotonic()
db=sqlite3.connect('file:/data/backend.db?mode=ro',uri=True,timeout=30)
tables=['archive_message','archive_conversation','archive_person','archive_source_account','desktop_accounts','model_providers','users']
counts={t:db.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in tables}
knowledge={t:db.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in ['knowledge_item','knowledge_job','knowledge_watch','knowledge_dirty','knowledge_retrieval_log']}
enabled=db.execute('SELECT COUNT(*) FROM knowledge_settings WHERE enabled!=0').fetchone()[0]
permissions=[r[0] for r in db.execute("SELECT permission_code FROM role_permissions WHERE role_code='admin' AND permission_code LIKE 'knowledge:%' ORDER BY permission_code")]
index=db.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_knowledge_archive_scan'").fetchone()[0]
triggers=db.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='trigger' AND name LIKE 'knowledge_%'").fetchone()[0]
check=db.execute('PRAGMA quick_check').fetchone()[0]
assert check=='ok' and index==1 and triggers==4
assert enabled==0 and all(v==0 for v in knowledge.values()) and len(permissions)==5
print(json.dumps({'counts':counts,'knowledge':knowledge,'enabled_tenants':enabled,'admin_permissions':permissions,'archive_index':index,'source_triggers':triggers,'quick_check':check,'seconds':round(time.monotonic()-started,1)}),flush=True)
PY
python3 - <<'PY' | tee "$release/verification.json"
from pathlib import Path
import json,hashlib,subprocess,urllib.request,urllib.error
release=Path('/home/ps/knowledge-20260916-100045')
before=json.loads((release/'backup-verification.json').read_text())['counts']
after=json.loads((release/'after.json').read_text())['counts']
assert all(after[t]>=v for t,v in before.items()),(before,after)
manifest=json.loads((release/'manifest.json').read_text(encoding='utf-8-sig'))
for row in manifest:
path=row['path'].replace('\\','/')
assert hashlib.sha256((Path('/opt/im-admin')/path).read_bytes()).hexdigest().lower()==row['sha256'].lower(),path
expected={row['path'].replace('\\','/'):row['sha256'].lower() for row in manifest if row['path'].endswith('.py') or '/dist/' in row['path'].replace('\\','/')}
code='from pathlib import Path; import json,hashlib,sys; paths=json.load(sys.stdin); print(json.dumps({p:hashlib.sha256((Path("/app")/p).read_bytes()).hexdigest() for p in paths}))'
actual=json.loads(subprocess.check_output(['docker','exec','-i','im-admin-api-1','python','-c',code],input=json.dumps(list(expected)).encode()))
assert actual==expected,'Runtime files differ from the release'
responses={}
for path,wanted in [('/api/v2/health',200),('/api/v2/knowledge/overview',401),('/api/v2/knowledge/items',401),('/api/v2/knowledge/desktop/summary',401),('/knowledge/center',200),('/js/center-Bwv00pY-.js',200)]:
url='http://127.0.0.1:18766'+path
try:
with urllib.request.urlopen(url,timeout=10) as response:
status=response.status
body=response.read()
if path=='/js/center-Bwv00pY-.js':
assert hashlib.sha256(body).hexdigest()==hashlib.sha256((release/'payload/admin-web/apps/web-antd/dist/js/center-Bwv00pY-.js').read_bytes()).hexdigest()
except urllib.error.HTTPError as error:
status=error.code
assert status==wanted,(path,status,wanted)
responses[path]=status
names=['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']
containers=json.loads(subprocess.check_output(['docker','inspect',*names]))
states={}
expected_image=subprocess.check_output(['docker','image','inspect','zyt/wecom-admin:knowledge-20260916-100045','--format','{{.Id}}'],text=True).strip()
for container in containers:
assert container['State']['Running'] and container['RestartCount']==0
assert container['Image']==expected_image
states[container['Name'].lstrip('/')]={'running':True,'restarts':container['RestartCount'],'health':container['State'].get('Health',{}).get('Status','not_configured')}
logs=subprocess.check_output(['docker','logs','--tail','150',container['Name']],stderr=subprocess.STDOUT,text=True)
assert 'Traceback (most recent call last)' not in logs and 'ERROR:' not in logs,container['Name']
print(json.dumps({'source_files_verified':len(manifest),'runtime_files_verified':len(actual),'http':responses,'containers':states,'image':expected_image,'archive_counts_preserved':True}))
PY