#!/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())