#!/usr/bin/env python3 """Local-only runtime; credentials and backups are kept in ignored .local/.""" import json, os, pathlib, secrets, signal, subprocess, sys, time, socket ROOT=pathlib.Path(__file__).resolve().parent.parent STATE=ROOT/'.local'; DATA=pathlib.Path('/tmp/nurinda-rec-mysql'); SOCKET=DATA/'mysql.sock' STATE.mkdir(exist_ok=True) def run(args, **kwargs): return subprocess.run(args,check=True,**kwargs) def wait_port(port): for _ in range(100): try: with socket.create_connection(('127.0.0.1',port),timeout=.2): return except OSError: time.sleep(.1) raise RuntimeError('Local service did not start: '+str(port)) def sql(text): run(['mysql','--defaults-extra-file='+str(STATE/'mysql-client.cnf')],input=text.encode(),stdout=subprocess.DEVNULL) def launch(name,args,env=None): pidfile=STATE/(name+'.pid') if pidfile.exists(): try: os.kill(int(pidfile.read_text()),0); return except ProcessLookupError: pass with open(STATE/(name+'.log'),'ab') as log: proc=subprocess.Popen(args,cwd=ROOT,env=env,stdin=subprocess.DEVNULL,stdout=log,stderr=log,start_new_session=True) pidfile.write_text(str(proc.pid)) if sys.argv[1]=='stop': cfg=json.loads((STATE/'credentials.json').read_text()) with open(STATE/'local-backup.sql','wb') as out: run(['mysqldump','--defaults-extra-file='+str(STATE/'mysql-client.cnf'),'--single-transaction','--no-tablespaces','recruitment_local'],stdout=out) for name in ['web','phpmyadmin']: p=STATE/(name+'.pid') if p.exists(): try: os.kill(int(p.read_text()),signal.SIGTERM) except ProcessLookupError: pass p.unlink() run(['mysqladmin','--defaults-extra-file='+str(STATE/'mysql-client.cnf'),'shutdown']) print('Local services stopped. Database backup: .local/local-backup.sql') sys.exit() if '.local/db.php' not in (ROOT/'includes/conn.php').read_text(): raise SystemExit('Preview lokal dibatalkan: arahkan includes/conn.php ke ../.local/db.php terlebih dahulu. Konfigurasi server tidak diubah.') credentials=STATE/'credentials.json' if not credentials.exists(): credentials.write_text(json.dumps({'db_user':'rec_local','db_password':secrets.token_urlsafe(20),'root_password':secrets.token_urlsafe(24),'admin_email':'admin@nurinda.local','admin_password':secrets.token_urlsafe(15),'blowfish':secrets.token_hex(16)},indent=2)); credentials.chmod(0o600) cfg=json.loads(credentials.read_text()) fresh=not (DATA/'mysql').exists() if fresh: DATA.mkdir(exist_ok=True) run(['mysqld','--no-defaults','--initialize-insecure','--datadir='+str(DATA),'--log-error='+str(DATA/'init.log')]) if not SOCKET.exists(): run(['mysqld','--no-defaults','--daemonize','--datadir='+str(DATA),'--socket='+str(SOCKET),'--bind-address=127.0.0.1','--port=33317','--mysqlx=0','--pid-file='+str(DATA/'mysql.pid'),'--log-error='+str(DATA/'server.log')]) wait_port(33317) client=STATE/'mysql-client.cnf' client.write_text('[client]\nuser=root\npassword='+('' if fresh else cfg['root_password'])+'\nsocket='+str(SOCKET)+'\n'); client.chmod(0o600) if fresh: sql("ALTER USER 'root'@'localhost' IDENTIFIED BY '"+cfg['root_password']+"';") client.write_text('[client]\nuser=root\npassword='+cfg['root_password']+'\nsocket='+str(SOCKET)+'\n') sql("CREATE DATABASE IF NOT EXISTS recruitment_local CHARACTER SET utf8mb4; CREATE USER IF NOT EXISTS 'rec_local'@'127.0.0.1' IDENTIFIED BY '"+cfg['db_password']+"'; GRANT ALL ON recruitment_local.* TO 'rec_local'@'127.0.0.1';") if fresh: dump=STATE/'local-backup.sql' if not dump.exists(): exports=sorted((ROOT/'DB').glob('*.sql')) if not exports: raise RuntimeError('No database export found in DB/') dump=exports[-1] with open(dump,'rb') as source: run(['mysql','--defaults-extra-file='+str(client),'recruitment_local'],stdin=source,stdout=subprocess.DEVNULL) env=os.environ.copy() for key in list(env): if key.startswith('REC_DB_'): env.pop(key) env.update(LOCAL_ADMIN_EMAIL=cfg['admin_email'],LOCAL_ADMIN_PASSWORD=cfg['admin_password']) run(['php','dev/setup-local.php'],env=env) # Use the system phpMyAdmin distribution without touching its global configuration. pma=STATE/'phpmyadmin' if not pma.exists(): import shutil shutil.copytree('/usr/share/phpmyadmin',pma) vendor=pma/'libraries/vendor_config.php' vendor.write_text(vendor.read_text().replace("'/etc/phpmyadmin/config.inc.php'", "ROOT_PATH . 'config.inc.php'").replace("'/var/lib/phpmyadmin/tmp/'", "ROOT_PATH . 'tmp/'")) (pma/'tmp').mkdir(exist_ok=True) (pma/'config.inc.php').write_text("'cookie','host'=>'127.0.0.1','port'=>'33317','AllowNoPassword'=>false,'only_db'=>'recruitment_local'];\n$cfg['TempDir']=__DIR__.'/tmp';\n$cfg['VersionCheck']=false;\n$cfg['SendErrorReports']='never';\n") launch('web',['php','-S','127.0.0.1:8086','-t',str(ROOT),str(ROOT/'dev/router.php')],env) launch('phpmyadmin',['php','-d','session.name=rec_phpmyadmin','-S','127.0.0.1:8087','-t',str(pma)],env) wait_port(8086); wait_port(8087) print('Dashboard: http://127.0.0.1:8086/dashboard/login.php\nPeserta: http://127.0.0.1:8086/\nphpMyAdmin: http://127.0.0.1:8087/\nCredentials: .local/credentials.json')