TorbatYar/scripts/deploy_accounting_finalize.py
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

148 lines
5.3 KiB
Python

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