ops: migrate HN subscription hostname to t34
This commit is contained in:
parent
ffa338f0ee
commit
360c155fae
@ -2,7 +2,7 @@
|
||||
|
||||
This deployment reuses the fixed, already-running DPL SubSieve images while keeping HN configuration, credentials, volumes, network, ports, certificates and upstream independent. It does not update or restart DPL/FUC containers.
|
||||
|
||||
Target: registered gateway-01 via the white SSH jump host. Public subscription host: c2.hardnerd-nodes.com. Upstream: https://hardnerd.net. Local management: https://127.0.0.1:26444 only; no public management virtual host is installed.
|
||||
Target: registered gateway-01 via the white SSH jump host. Public subscription host: t34.hardnerd-nodes.com. Upstream: https://hardnerd.net. Local management: https://127.0.0.1:26444 only; no public management virtual host is installed.
|
||||
|
||||
Publish the repository through Git before running the exact checked-out release. Run install.py prepare, then install.py certificate after public DNS/ACME readiness, then install.py start with an authorized administrator JSON object on stdin. Never place credentials in arguments or this repository. Each phase has state guards; inspect partial state before resuming rather than rerunning initialization.
|
||||
|
||||
@ -13,3 +13,5 @@ Public paths are limited to /s/{token} and /api/v1/client/subscribe. Host access
|
||||
Before changing HN's application subscription URL, verify a valid restricted QA token in supported client formats plus invalid/disabled tokens, no-store response headers, and the original-instance baseline. Empty HN node lists must be reported as empty; they do not constitute node connectivity acceptance. Cloudflare cache bypass is required for this dedicated subscription zone; preserve platform protections unless a specific compatibility change is authorized.
|
||||
|
||||
Rollback: restore the backed-up HN HTTP-only host file, nginx -t then graceful reload; stop only HN Compose services and HN timers, preserving HN volumes, credentials and backups for investigation. Do not run compose down on DPL/FUC or restart shared services. Current production evidence is maintained in docs/maintenance/HN_PRODUCTION.md and STATUS.md.
|
||||
|
||||
2026-10-28 endpoint migration: publish first, then run migrate-t34.py prepare, certificate, activate in sequence. The old c2 certificate is retained for rollback; the active certificate, SNI and isolated renewal service use t34. Retire old DNS only after new endpoint and application URL acceptance. Backup: /root/codex-backups/hn-gateway/20261028-t34.
|
||||
|
||||
@ -8,8 +8,8 @@ http {
|
||||
server_tokens off;
|
||||
server {
|
||||
listen 64444 ssl;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/privkey.pem;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/privkey.pem;
|
||||
root /var/www/html;
|
||||
index index.php;
|
||||
location / { try_files $uri /index.php$is_args$args; }
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
Description=Renew isolated HN subscription gateway certificate
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/certbot renew --config-dir /etc/hn-gateway-letsencrypt --work-dir /var/lib/hn-sgw/certbot --logs-dir /var/log/hn-gateway-certbot --cert-name c2.hardnerd-nodes.com --no-directory-hooks --no-random-sleep-on-renew --quiet --deploy-hook /opt/hn-gateway/provision/scripts/hn/gateway/renew-gateway.sh
|
||||
ExecStart=/usr/bin/certbot renew --config-dir /etc/hn-gateway-letsencrypt --work-dir /var/lib/hn-sgw/certbot --logs-dir /var/log/hn-gateway-certbot --cert-name t34.hardnerd-nodes.com --no-directory-hooks --no-random-sleep-on-renew --quiet --deploy-hook /opt/hn-gateway/provision/scripts/hn/gateway/renew-gateway.sh
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name c2.hardnerd-nodes.com hardnerd-nodes.com www.hardnerd-nodes.com;
|
||||
server_name t34.hardnerd-nodes.com hardnerd-nodes.com www.hardnerd-nodes.com;
|
||||
access_log off;
|
||||
location ^~ /.well-known/acme-challenge/ { root /var/lib/hn-sgw/acme; }
|
||||
location / { return 301 https://$host$request_uri; }
|
||||
@ -9,10 +9,10 @@ server {
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name c2.hardnerd-nodes.com;
|
||||
server_name t34.hardnerd-nodes.com;
|
||||
include /etc/nginx/hn-cloudflare-realip.conf;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/privkey.pem;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
access_log off;
|
||||
error_log /var/log/nginx/hn-subscribe-error.log crit;
|
||||
@ -31,8 +31,8 @@ server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name hardnerd-nodes.com www.hardnerd-nodes.com;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com/privkey.pem;
|
||||
ssl_certificate /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
access_log off;
|
||||
return 301 https://hardnerd.net$request_uri;
|
||||
|
||||
@ -61,19 +61,19 @@ def prepare():
|
||||
def certificate():
|
||||
assert STATE.is_dir() and CERT.is_dir()
|
||||
verify_original()
|
||||
args=['certbot','certonly','--webroot','-w',str(STATE/'acme'),'--config-dir',str(CERT),'--work-dir',str(STATE/'certbot'),'--logs-dir','/var/log/hn-gateway-certbot','--cert-name','c2.hardnerd-nodes.com','-d','c2.hardnerd-nodes.com','-d','hardnerd-nodes.com','-d','www.hardnerd-nodes.com','--non-interactive','--no-directory-hooks']
|
||||
args=['certbot','certonly','--webroot','-w',str(STATE/'acme'),'--config-dir',str(CERT),'--work-dir',str(STATE/'certbot'),'--logs-dir','/var/log/hn-gateway-certbot','--cert-name','t34.hardnerd-nodes.com','-d','t34.hardnerd-nodes.com','-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)
|
||||
print(output(['openssl','x509','-in',str(CERT/'live/c2.hardnerd-nodes.com/fullchain.pem'),'-noout','-dates','-ext','subjectAltName']))
|
||||
print(output(['openssl','x509','-in',str(CERT/'live/t34.hardnerd-nodes.com/fullchain.pem'),'-noout','-dates','-ext','subjectAltName']))
|
||||
|
||||
def start():
|
||||
verify_original();assert (CERT/'live/c2.hardnerd-nodes.com/fullchain.pem').is_file()
|
||||
verify_original();assert (CERT/'live/t34.hardnerd-nodes.com/fullchain.pem').is_file()
|
||||
secret=json.load(sys.stdin)
|
||||
assert isinstance(secret.get('username'),str)and isinstance(secret.get('password'),str)
|
||||
assert all('\n' not in secret[k]for k in ['username','password'])
|
||||
environment='ADMIN_USER='+secret['username']+'\nADMIN_PASS='+secret['password']+'\nV2B_BACKEND=https://hardnerd.net\nV2B_HOST=hardnerd.net\n'
|
||||
if (STATE/'admin.env').exists():assert (STATE/'admin.env').read_text()==environment,'Existing HN credentials differ; stop for inspection'
|
||||
else:write(STATE/'admin.env',environment,0o600)
|
||||
for name,target in [('cert.pem',CERT/'live/c2.hardnerd-nodes.com/fullchain.pem'),('key.pem',CERT/'live/c2.hardnerd-nodes.com/privkey.pem')]:
|
||||
for name,target in [('cert.pem',CERT/'live/t34.hardnerd-nodes.com/fullchain.pem'),('key.pem',CERT/'live/t34.hardnerd-nodes.com/privkey.pem')]:
|
||||
link=STATE/'ssl'/name
|
||||
if link.is_symlink():assert link.resolve()==target.resolve()
|
||||
else:link.symlink_to(target)
|
||||
@ -96,7 +96,7 @@ def start():
|
||||
for name in ['hn-gateway-cert.service','hn-gateway-cert.timer','hn-gateway-cloudflare-ips.service','hn-gateway-cloudflare-ips.timer']:
|
||||
write(Path('/etc/systemd/system')/name,(BASE/name).read_text())
|
||||
run(['systemctl','daemon-reload']);run(['systemctl','enable','--now','hn-gateway-cert.timer','hn-gateway-cloudflare-ips.timer'])
|
||||
write(STATE/'deployment.json',json.dumps({'commit':output(['git','-C',str(BASE),'rev-parse','HEAD']).strip(),'host':'c2.hardnerd-nodes.com','upstream':'https://hardnerd.net','containers':NEW,'admin_address':'https://127.0.0.1:26444','public_admin':False},indent=2),0o600)
|
||||
write(STATE/'deployment.json',json.dumps({'commit':output(['git','-C',str(BASE),'rev-parse','HEAD']).strip(),'host':'t34.hardnerd-nodes.com','upstream':'https://hardnerd.net','containers':NEW,'admin_address':'https://127.0.0.1:26444','public_admin':False},indent=2),0o600)
|
||||
verify_original();print('HN isolated gateway started; valid-token acceptance still required.')
|
||||
|
||||
if __name__=='__main__':
|
||||
|
||||
63
scripts/hn/gateway/migrate-t34.py
Normal file
63
scripts/hn/gateway/migrate-t34.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/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]()
|
||||
@ -1,10 +1,10 @@
|
||||
proxy_set_header Host c2.hardnerd-nodes.com;
|
||||
proxy_set_header Host t34.hardnerd-nodes.com;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_ssl_name c2.hardnerd-nodes.com;
|
||||
proxy_ssl_name t34.hardnerd-nodes.com;
|
||||
proxy_ssl_verify on;
|
||||
proxy_ssl_verify_depth 3;
|
||||
proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
[ "${RENEWED_LINEAGE:-}" = /etc/hn-gateway-letsencrypt/live/c2.hardnerd-nodes.com ] || exit 0
|
||||
[ "${RENEWED_LINEAGE:-}" = /etc/hn-gateway-letsencrypt/live/t34.hardnerd-nodes.com ] || exit 0
|
||||
nginx -t
|
||||
for container in hn-subscribe-gateway hn-subscribe-admin; do
|
||||
docker exec "$container" nginx -t
|
||||
|
||||
Loading…
Reference in New Issue
Block a user