Complete Classic telemetry, authenticated player replies and reversible cutover
Core tests / core (push) Successful in 27s

This commit is contained in:
Codex
2026-09-25 18:19:50 -07:00
parent d7d4e899f1
commit 6f9cdcb5a4
19 changed files with 394 additions and 17 deletions
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Prepare isolated Classic authentication, preserving ENDAMI's SRP credentials.
Does not stop Wrath or alter any Wrath account. Secrets are never printed.
"""
import argparse,base64,hashlib,json,os,pathlib,re,secrets,subprocess
def main(root):
credentials=json.loads((root/'database-credentials.json').read_text())
def query(container,password,sql):
result=subprocess.run(['docker','exec','-i','-e','MYSQL_PWD',container,'mysql','-uroot','-N','-B'],input=sql,text=True,capture_output=True,env=dict(os.environ,MYSQL_PWD=password))
if result.returncode:raise RuntimeError('Authentication database operation failed')
return result.stdout.strip()
def classic(sql):return query('azerothai-classic-db',credentials['root'],sql)
account_file=root/'classic-gm-credentials.json'
if account_file.exists():gm=json.loads(account_file.read_text())
else:
gm={'username':'AZCLASSICGM','password':secrets.token_hex(8)}
account_file.write_text(json.dumps(gm));account_file.chmod(0o600)
salt=secrets.token_bytes(32)
digest=hashlib.sha1((gm['username'].upper()+':'+gm['password'].upper()).encode()).digest()
exponent=int.from_bytes(hashlib.sha1(salt+digest).digest(),'little')
verifier=pow(7,exponent,int('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7',16))
assert re.fullmatch('[A-Z0-9]+',gm['username'])
classic(f"INSERT INTO classicrealmd.account(username,s,v,gmlevel) VALUES ('{gm['username']}','{salt[::-1].hex()}','{verifier:X}',3) ON DUPLICATE KEY UPDATE gmlevel=3,s=VALUES(s),v=VALUES(v);")
# Disable upstream sample logins without deleting accounts or characters.
classic("UPDATE classicrealmd.account SET locked=1,lockedIp='disabled' WHERE id IN (1,2,3,4) AND username IN ('ADMINISTRATOR','GAMEMASTER','MODERATOR','PLAYER');")
config=json.loads(subprocess.check_output(['docker','inspect','ac-database']))[0]
env=dict(v.split('=',1) for v in config['Config']['Env'] if '=' in v)
password=env.get('MYSQL_ROOT_PASSWORD') or env.get('MARIADB_ROOT_PASSWORD')
row=query('ac-database',password,"SELECT a.username,HEX(a.salt),HEX(a.verifier),MAX(x.gmlevel) FROM acore_auth.account a JOIN acore_auth.account_access x ON x.id=a.id WHERE a.username='ENDAMI' GROUP BY a.id;").split('\t')
if len(row)!=4 or row[0]!='ENDAMI':raise RuntimeError('Expected existing human GM account was not found')
salt=bytes.fromhex(row[1])[::-1].hex();verifier=bytes.fromhex(row[2])[::-1].hex()
classic(f"INSERT INTO classicrealmd.account(username,s,v,gmlevel) VALUES ('ENDAMI','{salt}','{verifier}',3) ON DUPLICATE KEY UPDATE s=VALUES(s),v=VALUES(v),gmlevel=3;")
classic("UPDATE classicrealmd.realmlist SET name='AzerothAI Classic',address='10.0.0.125',port=8086,realmflags=0,allowedSecurityLevel=0 WHERE id=1;")
text=(root/'run/etc/realmd.conf.dist').read_text()
values={'LoginDatabaseInfo':'"azerothai-classic-db;3306;'+credentials['user']+';'+credentials['password']+';classicrealmd"','RealmServerPort':'3724','BindIP':'"0.0.0.0"','LogsDir':'"/work/run/logs"'}
for key,value in values.items():
pattern=r'^'+re.escape(key)+r'\s*=.*$';line=key+' = '+value
text=re.sub(pattern,lambda _:line,text,flags=re.M) if re.search(pattern,text,re.M) else text+'\n'+line+'\n'
path=root/'run/etc/realmd.conf';path.write_text(text);path.chmod(0o600)
print('Classic GM credentials prepared; ENDAMI password verifier preserved; sample logins disabled.')
if __name__=='__main__':
parser=argparse.ArgumentParser();parser.add_argument('--root',type=pathlib.Path,required=True)
main(parser.parse_args().root.resolve())