64 lines
4.5 KiB
Python
64 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""One-time, guarded HN endpoint migration; no DPL/FUC mutations."""
|
|
from pathlib import Path
|
|
import argparse,json,os,shutil,subprocess,sys,time
|
|
sys.dont_write_bytecode=True
|
|
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
import install as deployment
|
|
BASE=Path(__file__).resolve().parent
|
|
BACKUP=Path('/root/codex-backups/hn-gateway/20261028-t34')
|
|
STATE=deployment.STATE
|
|
OLD='c2.hardnerd-nodes.com'
|
|
NEW='t34.hardnerd-nodes.com'
|
|
HOST=deployment.HOST
|
|
PROXY=Path('/etc/nginx/hn-subscribe-proxy.conf')
|
|
UNIT=Path('/etc/systemd/system/hn-gateway-cert.service')
|
|
def run(args,**kw):return subprocess.run(args,check=True,**kw)
|
|
def protected():
|
|
before=json.loads((BACKUP/'protected.json').read_text());now=deployment.original_state()
|
|
assert now['containers']==before['containers'],'Original containers changed'
|
|
assert all(now['nginx'].get(k)==v for k,v in before['nginx'].items()),'Original virtual hosts changed'
|
|
print('DPL/FUC container and original Nginx baseline preserved.')
|
|
def prepare():
|
|
assert not BACKUP.exists() and OLD in HOST.read_text()
|
|
assert json.loads((STATE/'deployment.json').read_text())['host']==OLD
|
|
BACKUP.mkdir(mode=0o700,parents=True)
|
|
(BACKUP/'protected.json').write_text(json.dumps(deployment.original_state()));(BACKUP/'protected.json').chmod(0o600)
|
|
for source,name in [(HOST,'host.conf'),(PROXY,'proxy.conf'),(UNIT,'cert.service'),(STATE/'deployment.json','deployment.json')]:
|
|
shutil.copy2(source,BACKUP/name);(BACKUP/name).chmod(0o600)
|
|
(BACKUP/'symlinks.json').write_text(json.dumps({name:os.readlink(STATE/'ssl'/name) for name in ['cert.pem','key.pem']}));(BACKUP/'symlinks.json').chmod(0o600)
|
|
http='server_name '+OLD+' hardnerd-nodes.com www.hardnerd-nodes.com;'
|
|
s=HOST.read_text();assert s.count(http)==1
|
|
HOST.write_text(s.replace(http,'server_name '+OLD+' '+NEW+' hardnerd-nodes.com www.hardnerd-nodes.com;',1))
|
|
try:run(['nginx','-t']);run(['systemctl','reload','nginx'])
|
|
except Exception:shutil.copy2(BACKUP/'host.conf',HOST);raise
|
|
protected();print('HN t34 ACME route ready; c2 subscription still active.')
|
|
def certificate():
|
|
protected()
|
|
args=['certbot','certonly','--webroot','-w',str(STATE/'acme'),'--config-dir',str(deployment.CERT),'--work-dir',str(STATE/'certbot'),'--logs-dir','/var/log/hn-gateway-certbot','--cert-name',NEW,'-d',NEW,'-d','hardnerd-nodes.com','-d','www.hardnerd-nodes.com','--non-interactive','--no-directory-hooks']
|
|
with (BACKUP/'certificate.log').open('w')as log:run(args,stdout=log,stderr=subprocess.STDOUT)
|
|
run(['openssl','x509','-in',str(deployment.CERT/'live'/NEW/'fullchain.pem'),'-noout','-dates','-ext','subjectAltName'])
|
|
def activate():
|
|
protected();assert not (BACKUP/'activated.json').exists()
|
|
assert (deployment.CERT/'live'/NEW/'fullchain.pem').is_file()
|
|
for name,file in [('cert.pem','fullchain.pem'),('key.pem','privkey.pem')]:
|
|
temp=STATE/'ssl'/(name+'.new');assert not temp.exists();temp.symlink_to(deployment.CERT/'live'/NEW/file);temp.replace(STATE/'ssl'/name)
|
|
PROXY.write_text((BASE/'proxy.conf').read_text());HOST.write_text((BASE/'host.conf').read_text());UNIT.write_text((BASE/'hn-gateway-cert.service').read_text())
|
|
# Recreate only HN so bind-mounted templates pick up their new inode, retaining volumes and credentials.
|
|
deployment.compose('up','-d','--force-recreate','gateway','admin')
|
|
deadline=time.monotonic()+55
|
|
for name in deployment.NEW:
|
|
while subprocess.run(['docker','exec',name,'nginx','-t'],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode:
|
|
assert time.monotonic()<deadline,'HN readiness timed out; inspect and restore backup'
|
|
time.sleep(2)
|
|
run(['docker','exec',name,'nginx','-t'])
|
|
run(['nginx','-t']);run(['systemctl','reload','nginx']);run(['systemctl','daemon-reload'])
|
|
data=json.loads((STATE/'deployment.json').read_text());data['host']=NEW;data['commit']=subprocess.check_output(['git','-C',str(BASE),'rev-parse','HEAD'],text=True).strip()
|
|
(STATE/'deployment.json').write_text(json.dumps(data,indent=2));(STATE/'deployment.json').chmod(0o600)
|
|
protected();(BACKUP/'activated.json').write_text(json.dumps({'host':NEW,'commit':data['commit']}));(BACKUP/'activated.json').chmod(0o600)
|
|
print('HN t34 gateway activated. Application URL and DNS retirement require final acceptance.')
|
|
if __name__=='__main__':
|
|
assert os.geteuid()==0
|
|
parser=argparse.ArgumentParser();parser.add_argument('phase',choices=['prepare','certificate','activate','verify-original']);args=parser.parse_args()
|
|
{'prepare':prepare,'certificate':certificate,'activate':activate,'verify-original':protected}[args.phase]()
|