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>
134 lines
4.8 KiB
Python
134 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Diagnose and fix NEXT_PUBLIC_* URLs to HTTPS on production."""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import paramiko
|
|
|
|
APP_HOST, APP_USER, APP_PASS = "192.168.10.162", "morteza", "Moli@5404"
|
|
NGX_HOST, NGX_USER, NGX_PASS = "192.168.10.156", "torbatyaruser", "Moli@5404"
|
|
REMOTE = "/home/morteza/torbatyar"
|
|
|
|
HTTPS_KEYS = {
|
|
"NEXT_PUBLIC_BACKEND_URL": "https://api.torbatyar.ir",
|
|
"NEXT_PUBLIC_API_BASE_URL": "https://api.torbatyar.ir",
|
|
"NEXT_PUBLIC_IDENTITY_API_URL": "https://identity.torbatyar.ir",
|
|
"NEXT_PUBLIC_KEYCLOAK_URL": "https://auth.torbatyar.ir",
|
|
"NEXT_PUBLIC_ACCOUNTING_API_URL": "https://accounting.torbatyar.ir",
|
|
"KEYCLOAK_PUBLIC_URL": "https://auth.torbatyar.ir",
|
|
"FRONTEND_CALLBACK_URL": "https://torbatyar.ir/auth/callback",
|
|
}
|
|
|
|
|
|
def connect(host, user, password):
|
|
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(c, cmd, password=None, timeout=600):
|
|
b64 = base64.b64encode(cmd.encode()).decode()
|
|
if password:
|
|
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", " | "))
|
|
_, out, err = c.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 main() -> int:
|
|
# Public smoke first
|
|
ngx = connect(NGX_HOST, NGX_USER, NGX_PASS)
|
|
try:
|
|
run(
|
|
ngx,
|
|
"""
|
|
echo === identity https ===
|
|
curl -sI https://identity.torbatyar.ir/health | head -n 12 || true
|
|
curl -s https://identity.torbatyar.ir/health || curl -sk https://identity.torbatyar.ir/health || echo FAIL
|
|
echo
|
|
echo === identity http ===
|
|
curl -sI http://identity.torbatyar.ir/health | head -n 12 || true
|
|
""",
|
|
password=NGX_PASS,
|
|
timeout=60,
|
|
)
|
|
finally:
|
|
ngx.close()
|
|
|
|
app = connect(APP_HOST, APP_USER, APP_PASS)
|
|
try:
|
|
keys_repr = repr(HTTPS_KEYS)
|
|
code, _ = run(
|
|
app,
|
|
f"""
|
|
set -e
|
|
cd {REMOTE}
|
|
echo === BEFORE ===
|
|
grep -E 'NEXT_PUBLIC_|KEYCLOAK_PUBLIC_URL|FRONTEND_CALLBACK' .env || true
|
|
python3 - <<'PY'
|
|
from pathlib import Path
|
|
p = Path('.env')
|
|
text = p.read_text(encoding='utf-8') if p.exists() else ''
|
|
keys = {keys_repr}
|
|
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:
|
|
# also rewrite accidental http://*.torbatyar.ir public URLs
|
|
if k.startswith('NEXT_PUBLIC_') or k in ('KEYCLOAK_PUBLIC_URL', 'FRONTEND_CALLBACK_URL', 'CORS_ORIGINS'):
|
|
v = line.split('=', 1)[1]
|
|
v2 = v.replace('http://api.torbatyar.ir', 'https://api.torbatyar.ir') \\
|
|
.replace('http://identity.torbatyar.ir', 'https://identity.torbatyar.ir') \\
|
|
.replace('http://auth.torbatyar.ir', 'https://auth.torbatyar.ir') \\
|
|
.replace('http://torbatyar.ir', 'https://torbatyar.ir') \\
|
|
.replace('http://www.torbatyar.ir', 'https://www.torbatyar.ir') \\
|
|
.replace('http://accounting.torbatyar.ir', 'https://accounting.torbatyar.ir')
|
|
out.append(f'{{k}}={{v2}}')
|
|
seen.add(k)
|
|
continue
|
|
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_patched')
|
|
PY
|
|
echo === AFTER ===
|
|
grep -E 'NEXT_PUBLIC_|KEYCLOAK_PUBLIC_URL|FRONTEND_CALLBACK' .env || true
|
|
# recreate frontend so Next picks new NEXT_PUBLIC_* at process start
|
|
docker compose up -d --force-recreate frontend identity-access-service
|
|
sleep 10
|
|
docker compose exec -T frontend printenv | grep -E 'NEXT_PUBLIC_IDENTITY|NEXT_PUBLIC_BACKEND|NEXT_PUBLIC_KEYCLOAK|NEXT_PUBLIC_ACCOUNTING' || true
|
|
docker compose ps frontend identity-access-service
|
|
curl -sf http://127.0.0.1:8001/health || echo IDENTITY_LOCAL_FAIL
|
|
echo
|
|
curl -s -o /dev/null -w 'accounting_page=%{{http_code}}\\n' http://127.0.0.1:3000/accounting
|
|
""",
|
|
password=APP_PASS,
|
|
timeout=300,
|
|
)
|
|
if code != 0:
|
|
raise RuntimeError("fix failed")
|
|
finally:
|
|
app.close()
|
|
print("\n=== DONE ===")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|