#!/usr/bin/env python3 """Finish nginx routing + ensure core/frontend env for accounting.""" from __future__ import annotations import base64 from pathlib import Path import paramiko ROOT = Path(__file__).resolve().parents[1] APP_HOST = "192.168.10.162" APP_USER = "morteza" APP_PASS = "Moli@5404" NGX_HOST = "192.168.10.156" NGX_USER = "torbatyaruser" NGX_PASS = "Moli@5404" REMOTE_DIR = "/home/morteza/torbatyar" def connect(host: str, user: str, password: str) -> paramiko.SSHClient: c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect(host, username=user, password=password, timeout=30, allow_agent=False, look_for_keys=False) return c def run(client, cmd: str, *, sudo=False, password="", timeout=600): cmd = cmd.replace("\r\n", "\n").replace("\r", "\n").strip() + "\n" b64 = base64.b64encode(cmd.encode()).decode() if sudo: full = f"echo {password!r} | sudo -S -p '' bash -c 'echo {b64} | base64 -d | bash'" else: full = f"bash -lc 'echo {b64} | base64 -d | bash'" print("\n>>>", cmd[:200].replace("\n", " | "), "...") _i, out, err = client.exec_command(full, timeout=timeout, get_pty=True) text = (out.read() + err.read()).decode("utf-8", "replace") code = out.channel.recv_exit_status() print(text[-5000:].encode("ascii", "replace").decode("ascii")) return code, text def put_text_sudo(client, password: str, remote: str, content: str) -> None: b64 = base64.b64encode(content.encode()).decode() cmd = f"mkdir -p $(dirname {remote!r}) && echo {b64} | base64 -d > {remote!r} && chmod 644 {remote!r}" code, _ = run(client, cmd, sudo=True, password=password, timeout=60) if code != 0: raise RuntimeError(f"write failed: {remote}") def main() -> int: app = connect(APP_HOST, APP_USER, APP_PASS) try: code, _ = run( app, f""" set -e cd {REMOTE_DIR} # ensure env python3 - <<'PY' from pathlib import Path p = Path('.env') text = p.read_text(encoding='utf-8') if p.exists() else '' keys = {{ 'NEXT_PUBLIC_ACCOUNTING_API_URL': 'https://accounting.torbatyar.ir', 'ACCOUNTING_DATABASE_URL': 'postgresql+asyncpg://superapp:superapp_password@postgres:5432/accounting_db', 'ACCOUNTING_DATABASE_URL_SYNC': 'postgresql+psycopg://superapp:superapp_password@postgres:5432/accounting_db', 'ACCOUNTING_SERVICE_NAME': 'accounting-service', 'KEYCLOAK_SERVER_URL': 'http://keycloak:8080', 'KEYCLOAK_REALM': 'superapp', }} lines = text.splitlines() seen=set(); out=[] for line in lines: if not line.strip() or line.lstrip().startswith('#') or '=' not in line: out.append(line); continue k=line.split('=',1)[0].strip() if k in keys: out.append(f"{{k}}={{keys[k]}}"); seen.add(k) else: out.append(line) for k,v in keys.items(): if k not in seen: out.append(f'{{k}}={{v}}') p.write_text('\\n'.join(out).rstrip()+'\\n', encoding='utf-8') print('env_ok') PY grep -E 'NEXT_PUBLIC_ACCOUNTING|ACCOUNTING_DATABASE|KEYCLOAK_SERVER_URL|KEYCLOAK_PUBLIC_URL' .env # bring core back if missing + recreate frontend to pick env docker compose up -d core-service identity-access-service accounting-service docker compose up -d --force-recreate frontend sleep 8 docker compose ps curl -sf http://127.0.0.1:8000/health || curl -sf http://127.0.0.1:8000/api/v1/health || echo CORE_HEALTH_CHECK curl -sf http://127.0.0.1:8002/health echo docker compose exec -T frontend printenv NEXT_PUBLIC_ACCOUNTING_API_URL || true """, sudo=True, password=APP_PASS, timeout=600, ) if code != 0: raise RuntimeError("app finalize failed") finally: app.close() ngx = connect(NGX_HOST, NGX_USER, NGX_PASS) try: conf = (ROOT / "infrastructure" / "nginx" / "torbatyar.ir.conf").read_text(encoding="utf-8") put_text_sudo(ngx, NGX_PASS, "/etc/nginx/conf.d/torbatyar.ir.conf", conf) # also update provision script on nginx host if present prov = (ROOT / "infrastructure" / "nginx" / "provision_ssl.py").read_text(encoding="utf-8") put_text_sudo(ngx, NGX_PASS, "/opt/torbatyar/bin/provision_ssl.py", prov) code, _ = run( ngx, r""" set -e mkdir -p /var/www/html certbot certonly --webroot -w /var/www/html \ -d torbatyar.ir -d www.torbatyar.ir \ -d api.torbatyar.ir -d identity.torbatyar.ir -d auth.torbatyar.ir -d accounting.torbatyar.ir \ --non-interactive --agree-tos --email support@torbatyar.ir --expand || true openssl x509 -in /etc/letsencrypt/live/torbatyar.ir/fullchain.pem -noout -ext subjectAltName | tr ',' '\n' | grep -i accounting || echo SAN_ACCOUNTING_MISSING nginx -t systemctl reload nginx echo NGINX_OK curl -skI https://accounting.torbatyar.ir/health | head -n 8 echo --- curl -sk https://accounting.torbatyar.ir/health || echo PUBLIC_HEALTH_FAIL echo curl -skI https://torbatyar.ir/accounting | head -n 8 """, sudo=True, password=NGX_PASS, timeout=300, ) if code != 0: raise RuntimeError("nginx finalize failed") finally: ngx.close() print("\n=== FINALIZE DONE ===") return 0 if __name__ == "__main__": raise SystemExit(main())