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>
156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Recover compose conflict and finish accounting deploy on production."""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import sys
|
|
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: paramiko.SSHClient, cmd: str, *, sudo: bool = False, password: str = "", timeout: int = 600) -> tuple[int, str]:
|
|
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(f"\n>>> {'[sudo] ' if sudo else ''}{cmd[:240].replace(chr(10), ' | ')}...")
|
|
_stdin, stdout, stderr = client.exec_command(full, timeout=timeout, get_pty=True)
|
|
out = stdout.read().decode("utf-8", errors="replace")
|
|
err = stderr.read().decode("utf-8", errors="replace")
|
|
code = stdout.channel.recv_exit_status()
|
|
text = out + (("\n" + err) if err.strip() else "")
|
|
printable = text[-6000:] if len(text) > 6000 else text
|
|
try:
|
|
print(printable)
|
|
except UnicodeEncodeError:
|
|
print(printable.encode("ascii", "replace").decode("ascii"))
|
|
return code, text
|
|
|
|
|
|
def put_text_sudo(client: paramiko.SSHClient, 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"failed writing {remote}")
|
|
|
|
|
|
def main() -> int:
|
|
app = connect(APP_HOST, APP_USER, APP_PASS)
|
|
try:
|
|
# Clean docker name conflicts / stuck removals
|
|
run(
|
|
app,
|
|
f"""
|
|
set -e
|
|
cd {REMOTE_DIR}
|
|
docker ps -a --format '{{{{.ID}}}} {{{{.Names}}}}' || true
|
|
# remove orphan/conflict core containers by name pattern
|
|
for c in $(docker ps -aq --filter name=superapp_core_service); do
|
|
docker rm -f "$c" || true
|
|
done
|
|
for c in $(docker ps -aq --filter name=61dc7452ee05); do
|
|
docker rm -f "$c" || true
|
|
done
|
|
docker container prune -f || true
|
|
""",
|
|
sudo=True,
|
|
password=APP_PASS,
|
|
timeout=180,
|
|
)
|
|
|
|
# Ensure DB + env keys + start services
|
|
code, _ = run(
|
|
app,
|
|
f"""
|
|
set -e
|
|
cd {REMOTE_DIR}
|
|
# upsert accounting env if missing
|
|
grep -q '^NEXT_PUBLIC_ACCOUNTING_API_URL=' .env || echo 'NEXT_PUBLIC_ACCOUNTING_API_URL=https://accounting.torbatyar.ir' >> .env
|
|
grep -q '^ACCOUNTING_DATABASE_URL=' .env || echo 'ACCOUNTING_DATABASE_URL=postgresql+asyncpg://superapp:superapp_password@postgres:5432/accounting_db' >> .env
|
|
grep -q '^ACCOUNTING_DATABASE_URL_SYNC=' .env || echo 'ACCOUNTING_DATABASE_URL_SYNC=postgresql+psycopg://superapp:superapp_password@postgres:5432/accounting_db' >> .env
|
|
grep -q '^KEYCLOAK_SERVER_URL=' .env || echo 'KEYCLOAK_SERVER_URL=http://keycloak:8080' >> .env
|
|
# force public accounting URL
|
|
sed -i 's|^NEXT_PUBLIC_ACCOUNTING_API_URL=.*|NEXT_PUBLIC_ACCOUNTING_API_URL=https://accounting.torbatyar.ir|' .env
|
|
sed -i 's|^KEYCLOAK_SERVER_URL=.*|KEYCLOAK_SERVER_URL=http://keycloak:8080|' .env
|
|
|
|
docker compose up -d postgres
|
|
sleep 3
|
|
docker compose exec -T postgres psql -U superapp -d postgres -c "SELECT 1 FROM pg_database WHERE datname='accounting_db';" | grep -q 1 \\
|
|
|| docker compose exec -T postgres psql -U superapp -d postgres -c "CREATE DATABASE accounting_db;"
|
|
|
|
docker compose up -d --build --force-recreate accounting-service frontend
|
|
sleep 5
|
|
docker compose ps
|
|
docker compose logs --tail=60 accounting-service
|
|
curl -sf http://127.0.0.1:8002/health
|
|
echo
|
|
curl -sf http://127.0.0.1:3000/accounting >/dev/null && echo FRONTEND_ACCOUNTING_OK || echo FRONTEND_ACCOUNTING_CHECK
|
|
""",
|
|
sudo=True,
|
|
password=APP_PASS,
|
|
timeout=1800,
|
|
)
|
|
if code != 0:
|
|
raise RuntimeError("app recover 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)
|
|
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
|
|
nginx -t
|
|
systemctl reload nginx
|
|
echo NGINX_RELOADED
|
|
curl -skI https://accounting.torbatyar.ir/health | head -n 5 || true
|
|
curl -sf https://accounting.torbatyar.ir/health || curl -sk https://accounting.torbatyar.ir/health || true
|
|
""",
|
|
sudo=True,
|
|
password=NGX_PASS,
|
|
timeout=300,
|
|
)
|
|
if code != 0:
|
|
raise RuntimeError("nginx step failed")
|
|
finally:
|
|
ngx.close()
|
|
|
|
print("\n=== RECOVER DEPLOY FINISHED ===")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as exc:
|
|
print(f"FATAL: {exc}", file=sys.stderr)
|
|
raise
|