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>
135 lines
4.6 KiB
Python
135 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Deploy frontend (+ accounting) to production; preserve .env; install npm deps."""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import os
|
|
import sys
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
import paramiko
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP_HOST, APP_USER, APP_PASS = "192.168.10.162", "morteza", "Moli@5404"
|
|
REMOTE = "/home/morteza/torbatyar"
|
|
|
|
EXCLUDE_DIRS = {
|
|
".git", "node_modules", ".next", ".venv", "venv", "__pycache__",
|
|
".pytest_cache", ".mypy_cache", ".ruff_cache", ".idea", ".vscode",
|
|
}
|
|
EXCLUDE_FILES = {".DS_Store", "Thumbs.db", ".env"}
|
|
|
|
|
|
def connect():
|
|
c = paramiko.SSHClient()
|
|
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
c.connect(APP_HOST, username=APP_USER, password=APP_PASS, timeout=30, allow_agent=False, look_for_keys=False)
|
|
return c
|
|
|
|
|
|
def run(c, cmd, *, sudo=False, timeout=900):
|
|
cmd = cmd.replace("\r\n", "\n").replace("\r", "\n").strip() + "\n"
|
|
b64 = base64.b64encode(cmd.encode()).decode()
|
|
if sudo:
|
|
full = f"echo {APP_PASS!r} | sudo -S -p '' bash -c 'echo {b64} | base64 -d | bash'"
|
|
else:
|
|
full = f"bash -lc 'echo {b64} | base64 -d | bash'"
|
|
print("\n>>>", cmd[:220].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 make_tarball() -> Path:
|
|
out = ROOT / "scripts" / "_deploy_frontend_bundle.tar.gz"
|
|
print(f"Creating {out} ...")
|
|
with tarfile.open(out, "w:gz") as tar:
|
|
for dirpath, dirnames, filenames in os.walk(ROOT):
|
|
p = Path(dirpath)
|
|
dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS and not d.startswith(".cursor")]
|
|
for name in filenames:
|
|
fp = p / name
|
|
rel = fp.relative_to(ROOT)
|
|
if any(part in EXCLUDE_DIRS for part in rel.parts):
|
|
continue
|
|
if rel.name in EXCLUDE_FILES or rel.suffix in {".pyc", ".log"}:
|
|
continue
|
|
if rel.as_posix().startswith("scripts/_deploy"):
|
|
continue
|
|
tar.add(fp, arcname=str(rel).replace("\\", "/"))
|
|
print(f"Size: {out.stat().st_size / 1024 / 1024:.1f} MB")
|
|
return out
|
|
|
|
|
|
def main() -> int:
|
|
tarball = make_tarball()
|
|
c = connect()
|
|
try:
|
|
sftp = c.open_sftp()
|
|
print("Uploading...")
|
|
sftp.put(str(tarball), "/tmp/torbatyar_fe_bundle.tar.gz")
|
|
sftp.close()
|
|
|
|
code, _ = run(
|
|
c,
|
|
f"""
|
|
set -e
|
|
cd {REMOTE}
|
|
cp -a .env /tmp/torbatyar.env.bak
|
|
tar -xzf /tmp/torbatyar_fe_bundle.tar.gz -C {REMOTE}
|
|
cp /tmp/torbatyar.env.bak .env
|
|
rm -f /tmp/torbatyar_fe_bundle.tar.gz
|
|
# keep public HTTPS urls
|
|
sed -i 's|^NEXT_PUBLIC_IDENTITY_API_URL=.*|NEXT_PUBLIC_IDENTITY_API_URL=https://identity.torbatyar.ir|' .env
|
|
sed -i 's|^NEXT_PUBLIC_BACKEND_URL=.*|NEXT_PUBLIC_BACKEND_URL=https://api.torbatyar.ir|' .env
|
|
sed -i 's|^NEXT_PUBLIC_API_BASE_URL=.*|NEXT_PUBLIC_API_BASE_URL=https://api.torbatyar.ir|' .env
|
|
sed -i 's|^NEXT_PUBLIC_KEYCLOAK_URL=.*|NEXT_PUBLIC_KEYCLOAK_URL=https://auth.torbatyar.ir|' .env
|
|
sed -i 's|^NEXT_PUBLIC_ACCOUNTING_API_URL=.*|NEXT_PUBLIC_ACCOUNTING_API_URL=https://accounting.torbatyar.ir|' .env
|
|
grep -E '^NEXT_PUBLIC_' .env
|
|
""",
|
|
sudo=True,
|
|
timeout=180,
|
|
)
|
|
if code != 0:
|
|
raise RuntimeError("extract failed")
|
|
|
|
code, _ = run(
|
|
c,
|
|
f"""
|
|
set -e
|
|
cd {REMOTE}
|
|
docker compose up -d --build accounting-service frontend
|
|
# install any new npm deps into named volume
|
|
docker compose exec -T frontend npm install
|
|
docker compose restart frontend
|
|
sleep 12
|
|
docker compose ps frontend accounting-service
|
|
docker compose exec -T frontend printenv NEXT_PUBLIC_ACCOUNTING_API_URL
|
|
curl -sf http://127.0.0.1:8002/health
|
|
echo
|
|
curl -s -o /dev/null -w 'accounting=%{{http_code}}\\n' http://127.0.0.1:3000/accounting
|
|
curl -s -o /dev/null -w 'fiscal=%{{http_code}}\\n' http://127.0.0.1:3000/accounting/fiscal
|
|
""",
|
|
sudo=True,
|
|
timeout=1800,
|
|
)
|
|
if code != 0:
|
|
raise RuntimeError("compose/npm failed")
|
|
finally:
|
|
c.close()
|
|
|
|
print("\n=== DEPLOY DONE ===")
|
|
print("https://torbatyar.ir/accounting")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as exc:
|
|
print(f"FATAL: {exc}", file=sys.stderr)
|
|
raise
|