feat(ops): scripturi start.sh (test/prod, api/worker) + verificare RAR
start.sh ruleaza api/worker/both pe mediu test sau prod, cu --send pentru trimiterea la RAR, plus status/stop. start-test.sh si start-prod.sh sunt wrappere care fixeaza mediul. tools/rar_finalizate.py listeaza prezentarile inregistrate la RAR (confirmare end-to-end ca au ajuns). .gitignore: .run/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,6 +15,9 @@ settings.xml
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
# --- start.sh: PID-uri si loguri proces local ---
|
||||||
|
.run/
|
||||||
|
|
||||||
# --- VFP: programe compilate (se regenerează din .prg) ---
|
# --- VFP: programe compilate (se regenerează din .prg) ---
|
||||||
*.fxp
|
*.fxp
|
||||||
*.FXP
|
*.FXP
|
||||||
|
|||||||
20
start-prod.sh
Executable file
20
start-prod.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# start-prod.sh — lanseaza start.sh pe mediul PROD. Forwardeaza rol + optiuni.
|
||||||
|
#
|
||||||
|
# ./start-prod.sh api # API prod
|
||||||
|
# ./start-prod.sh worker --send # worker prod (TRIMITE la RAR productie)
|
||||||
|
# ./start-prod.sh both --send # API + worker
|
||||||
|
# ./start-prod.sh status | stop
|
||||||
|
#
|
||||||
|
# In productie trimiterea trebuie ceruta EXPLICIT cu --send, ca sa nu trimiti din
|
||||||
|
# greseala. De aceea fara argumente nu pornim nimic. Recomandat: docker compose.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Specifica rolul: api | worker | both (adauga --send pentru trimitere la RAR)." >&2
|
||||||
|
echo "Ex: ./start-prod.sh both --send | vezi ./start.sh --help" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exec ./start.sh prod "$@"
|
||||||
18
start-test.sh
Executable file
18
start-test.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# start-test.sh — lanseaza start.sh pe mediul TEST. Forwardeaza rol + optiuni.
|
||||||
|
#
|
||||||
|
# ./start-test.sh # API + worker cu trimitere la RAR test (both --send)
|
||||||
|
# ./start-test.sh api # doar API
|
||||||
|
# ./start-test.sh worker --send # doar worker (trimite la RAR test)
|
||||||
|
# ./start-test.sh finalizate # ce prezentari sunt inregistrate la RAR test
|
||||||
|
# ./start-test.sh status | stop
|
||||||
|
#
|
||||||
|
# Pe test trimiterea e sigura (sandbox RAR), deci fara argumente pornim end-to-end.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
exec ./start.sh test both --send
|
||||||
|
fi
|
||||||
|
exec ./start.sh test "$@"
|
||||||
165
start.sh
Executable file
165
start.sh
Executable file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# start.sh — pornire gateway RAR AUTOPASS (api / worker) pe mediu test sau prod.
|
||||||
|
#
|
||||||
|
# Exemple:
|
||||||
|
# ./start.sh test api # API pe :8000, mediu test
|
||||||
|
# ./start.sh test worker --send # worker care TRIMITE la RAR test (creds <test> din settings.xml)
|
||||||
|
# ./start.sh test both --send # API + worker impreuna (dev end-to-end)
|
||||||
|
# ./start.sh prod api --port 8000 # API mediu prod
|
||||||
|
# ./start.sh prod worker --send # worker prod (NU foloseste creds de test)
|
||||||
|
# ./start.sh status # stare procese + /healthz
|
||||||
|
# ./start.sh stop # opreste procesele pornite cu "both"
|
||||||
|
# ./start.sh test finalizate # ce prezentari sunt inregistrate la RAR (au ajuns?)
|
||||||
|
#
|
||||||
|
# Pentru productie reala se recomanda docker compose (vezi docker-compose.yml).
|
||||||
|
# start.sh e pentru rulare directa pe VPS/LXC sau dezvoltare locala.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# --- valori implicite ---
|
||||||
|
PORT=8000
|
||||||
|
HOST=0.0.0.0
|
||||||
|
RELOAD=0
|
||||||
|
SEND=0
|
||||||
|
USE_TEST_CREDS="auto" # auto: pornit doar pe mediul test cand --send e activ
|
||||||
|
RUN_DIR=".run"
|
||||||
|
PY=python3
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
exit "${1:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- citeste .env daca exista (AUTOPASS_CREDS_KEY etc.) ---
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a; . ./.env; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- parsare argumente: env si rol = pozitionale; restul = flag-uri ---
|
||||||
|
ENVIRONMENT=""
|
||||||
|
ROLE=""
|
||||||
|
POSITIONAL=()
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--port) PORT="$2"; shift 2 ;;
|
||||||
|
--host) HOST="$2"; shift 2 ;;
|
||||||
|
--reload) RELOAD=1; shift ;;
|
||||||
|
--send) SEND=1; shift ;;
|
||||||
|
--test-creds) USE_TEST_CREDS="true"; shift ;;
|
||||||
|
--no-test-creds) USE_TEST_CREDS="false"; shift ;;
|
||||||
|
-h|--help) usage 0 ;;
|
||||||
|
-*) echo "Optiune necunoscuta: $1" >&2; usage 1 ;;
|
||||||
|
*) POSITIONAL+=("$1"); shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Comenzi fara mediu (stop/status accepta lipsa mediului).
|
||||||
|
case "${POSITIONAL[0]:-}" in
|
||||||
|
stop|status)
|
||||||
|
ROLE="${POSITIONAL[0]}" ;;
|
||||||
|
test|prod)
|
||||||
|
ENVIRONMENT="${POSITIONAL[0]}"
|
||||||
|
ROLE="${POSITIONAL[1]:-}" ;;
|
||||||
|
"")
|
||||||
|
usage 1 ;;
|
||||||
|
*)
|
||||||
|
echo "Mediu invalid: ${POSITIONAL[0]} (asteptat: test | prod | stop | status)" >&2
|
||||||
|
usage 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p "$RUN_DIR"
|
||||||
|
|
||||||
|
# --- helperi ---
|
||||||
|
ensure_creds_key() {
|
||||||
|
# API cripteaza creds RAR, worker le decripteaza: trebuie ACEEASI cheie.
|
||||||
|
# Daca ruleaza ambele din aceeasi invocare (both), o cheie efemera exportata
|
||||||
|
# acum e partajata de copii. Daca lipseste si rulezi separat, avertizeaza.
|
||||||
|
if [ -z "${AUTOPASS_CREDS_KEY:-}" ]; then
|
||||||
|
if [ "$ROLE" = "both" ]; then
|
||||||
|
AUTOPASS_CREDS_KEY="$($PY -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"
|
||||||
|
export AUTOPASS_CREDS_KEY
|
||||||
|
echo "[start] AUTOPASS_CREDS_KEY negasita -> cheie efemera generata pentru aceasta rulare."
|
||||||
|
echo "[start] (Nu supravietuieste restartului. Pune o cheie persistenta in .env pentru prod.)"
|
||||||
|
else
|
||||||
|
echo "[start] ATENTIE: AUTOPASS_CREDS_KEY nesetata. API si worker pornite SEPARAT vor folosi"
|
||||||
|
echo "[start] chei diferite -> worker-ul NU poate decripta creds-urile. Seteaz-o in .env."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
export_common() {
|
||||||
|
export AUTOPASS_RAR_ENV="$ENVIRONMENT"
|
||||||
|
# use_test_creds: auto -> true doar pe test cand trimitem
|
||||||
|
local utc="$USE_TEST_CREDS"
|
||||||
|
if [ "$utc" = "auto" ]; then
|
||||||
|
if [ "$ENVIRONMENT" = "test" ] && [ "$SEND" -eq 1 ]; then utc="true"; else utc="false"; fi
|
||||||
|
fi
|
||||||
|
export AUTOPASS_WORKER_USE_TEST_CREDS="$utc"
|
||||||
|
if [ "$SEND" -eq 1 ]; then
|
||||||
|
export AUTOPASS_WORKER_SEND_ENABLED="true"
|
||||||
|
else
|
||||||
|
export AUTOPASS_WORKER_SEND_ENABLED="${AUTOPASS_WORKER_SEND_ENABLED:-false}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_api() {
|
||||||
|
local args=(uvicorn app.main:app --host "$HOST" --port "$PORT")
|
||||||
|
[ "$RELOAD" -eq 1 ] && args+=(--reload)
|
||||||
|
echo "[start] API ($ENVIRONMENT) -> http://$HOST:$PORT (docs: /docs, dashboard: /)"
|
||||||
|
exec "$PY" -m "${args[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_worker() {
|
||||||
|
echo "[start] worker ($ENVIRONMENT) send_enabled=$AUTOPASS_WORKER_SEND_ENABLED use_test_creds=$AUTOPASS_WORKER_USE_TEST_CREDS"
|
||||||
|
[ "$AUTOPASS_WORKER_SEND_ENABLED" = "true" ] || \
|
||||||
|
echo "[start] NOTA: send dezactivat — worker proceseaza coada dar NU trimite la RAR. Adauga --send."
|
||||||
|
exec "$PY" -m app.worker
|
||||||
|
}
|
||||||
|
|
||||||
|
start_both() {
|
||||||
|
local alog="$RUN_DIR/api.log" wlog="$RUN_DIR/worker.log"
|
||||||
|
echo "[start] API + worker ($ENVIRONMENT). Loguri: $alog , $wlog. Ctrl-C opreste ambele."
|
||||||
|
"$PY" -m uvicorn app.main:app --host "$HOST" --port "$PORT" >"$alog" 2>&1 &
|
||||||
|
echo $! > "$RUN_DIR/api.pid"
|
||||||
|
"$PY" -m app.worker >"$wlog" 2>&1 &
|
||||||
|
echo $! > "$RUN_DIR/worker.pid"
|
||||||
|
trap 'echo; echo "[start] opresc..."; kill $(cat "$RUN_DIR"/*.pid 2>/dev/null) 2>/dev/null || true; exit 0' INT TERM
|
||||||
|
echo "[start] API pid $(cat "$RUN_DIR/api.pid"), worker pid $(cat "$RUN_DIR/worker.pid")"
|
||||||
|
tail -n +1 -f "$alog" "$wlog"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
local killed=0
|
||||||
|
for f in "$RUN_DIR"/api.pid "$RUN_DIR"/worker.pid; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
local pid; pid="$(cat "$f")"
|
||||||
|
if kill "$pid" 2>/dev/null; then echo "[stop] oprit pid $pid ($(basename "$f"))"; killed=1; fi
|
||||||
|
rm -f "$f"
|
||||||
|
done
|
||||||
|
[ "$killed" -eq 1 ] || echo "[stop] niciun proces urmarit (.run/*.pid)."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
for f in "$RUN_DIR"/api.pid "$RUN_DIR"/worker.pid; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
local pid; pid="$(cat "$f")"
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then echo "[status] $(basename "$f" .pid): pid $pid VIU"
|
||||||
|
else echo "[status] $(basename "$f" .pid): pid $pid MORT"; fi
|
||||||
|
done
|
||||||
|
echo "[status] /healthz:"
|
||||||
|
curl -s "http://localhost:$PORT/healthz" 2>/dev/null | "$PY" -m json.tool 2>/dev/null \
|
||||||
|
|| echo " (API nu raspunde pe :$PORT)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- dispatch ---
|
||||||
|
case "$ROLE" in
|
||||||
|
api) ensure_creds_key; export_common; start_api ;;
|
||||||
|
worker) ensure_creds_key; export_common; start_worker ;;
|
||||||
|
both) ensure_creds_key; export_common; start_both ;;
|
||||||
|
finalizate) export AUTOPASS_RAR_ENV="$ENVIRONMENT"; exec "$PY" -m tools.rar_finalizate ;;
|
||||||
|
stop) cmd_stop ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
"") echo "Lipseste rolul (api|worker|both|finalizate)" >&2; usage 1 ;;
|
||||||
|
*) echo "Rol invalid: $ROLE" >&2; usage 1 ;;
|
||||||
|
esac
|
||||||
83
tools/rar_finalizate.py
Normal file
83
tools/rar_finalizate.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Listeaza prezentarile finalizate la RAR (verificare end-to-end: au ajuns?).
|
||||||
|
|
||||||
|
Face login cu credentialele din settings.xml (blocul <test> sau <production>,
|
||||||
|
in functie de AUTOPASS_RAR_ENV) si afiseaza ce e inregistrat la RAR. Folosit ca
|
||||||
|
sa confirmi ca prezentarile trimise de worker au ajuns efectiv: compari
|
||||||
|
`id_prezentare` din coada locala (status='sent') cu `id`-urile de aici.
|
||||||
|
|
||||||
|
Utilizare:
|
||||||
|
AUTOPASS_RAR_ENV=test python3 -m tools.rar_finalizate
|
||||||
|
./start.sh test finalizate
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from app.config import ROOT, get_settings
|
||||||
|
from app.rar_client import RarClient, RarError
|
||||||
|
|
||||||
|
|
||||||
|
def _creds_for_env(env: str) -> dict | None:
|
||||||
|
"""Citeste credentialele pentru mediu (<test> / <production>) din settings.xml."""
|
||||||
|
path = ROOT / "settings.xml"
|
||||||
|
if not path.exists():
|
||||||
|
return None
|
||||||
|
block = "production" if env == "prod" else "test"
|
||||||
|
try:
|
||||||
|
root = ET.parse(path).getroot()
|
||||||
|
node = root.find(f"./{block}/credentials")
|
||||||
|
if node is None:
|
||||||
|
return None
|
||||||
|
email = (node.findtext("email") or "").strip()
|
||||||
|
password = (node.findtext("password") or "").strip()
|
||||||
|
if not email or not password or email.startswith("EMAIL_"):
|
||||||
|
return None
|
||||||
|
return {"email": email, "password": password}
|
||||||
|
except ET.ParseError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
settings = get_settings()
|
||||||
|
env = settings.rar_env
|
||||||
|
creds = _creds_for_env(env)
|
||||||
|
if not creds:
|
||||||
|
print(
|
||||||
|
f"Lipsesc credentialele <{'production' if env == 'prod' else 'test'}> in settings.xml.\n"
|
||||||
|
"Copiaza settings.xml.example -> settings.xml si completeaza-le.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
print(f"[finalizate] login RAR ({env}) ca {creds['email']} ...")
|
||||||
|
rar = RarClient(settings)
|
||||||
|
try:
|
||||||
|
token = rar.login(creds["email"], creds["password"])
|
||||||
|
items = rar.get_finalizate(token)
|
||||||
|
except RarError as exc:
|
||||||
|
print(f"[finalizate] eroare RAR: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
rar.close()
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
print("[finalizate] RAR nu a intors nicio prezentare finalizata.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"[finalizate] {len(items)} prezentari inregistrate la RAR:\n")
|
||||||
|
print(f"{'id':>8} {'VIN':<18} {'data':<12} {'odometru':>10}")
|
||||||
|
print("-" * 54)
|
||||||
|
for it in items:
|
||||||
|
idp = it.get("id", "")
|
||||||
|
vin = (it.get("vin") or it.get("serieSasiu") or "")[:18]
|
||||||
|
data = it.get("dataPrestatie") or ""
|
||||||
|
odo = it.get("odometruFinal") or it.get("odometru") or ""
|
||||||
|
print(f"{str(idp):>8} {vin:<18} {str(data):<12} {str(odo):>10}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user