#!/usr/bin/env bash # Build de la 0 ERP/CRM DiDi - one-shot installer # Versiune 2.0, iulie 2026 # # Cerinte: docker, docker compose v2. Reteaua Docker `didi-network` trebuie sa existe # (o creeaza scriptul daca lipseste). # # NOTA: toata logica de setup ruleaza IN container, prin `bench execute` pe functiile # din app-ul didi_custom. Scripturile din scripts/setup/ sunt LEGACY (prima versiune, # rulate pe host) si NU mai sunt folosite de acest installer. set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$DIR" log() { printf '\033[1;36m[%(%H:%M:%S)T]\033[0m %s\n' -1 "$*"; } err() { printf '\033[1;31m[%(%H:%M:%S)T] ERROR:\033[0m %s\n' -1 "$*" >&2; exit 1; } # ── Verificari ────────────────────────────────────────────── command -v docker >/dev/null || err "docker nu e instalat" docker compose version >/dev/null || err "docker compose v2 nu e instalat" [ -f .env ] || err ".env nu exista. Copiaza din .env.example si configureaza." source .env SITE_NAME="${SITE_NAME:-didi-erp}" ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin}" MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:?seteaza MYSQL_ROOT_PASSWORD in .env}" ERPNEXT_PORT="${ERPNEXT_PORT:-8080}" if ! docker network inspect didi-network >/dev/null 2>&1; then log "Creez reteaua didi-network..." docker network create didi-network fi # Helper: ruleaza o functie Python din bench, IN container (cu venv-ul frappe). # NU folosi `python3` pe host sau in container pentru logica frappe — esueaza tacut. bexec() { log " bench execute $1" docker exec didi-erpnext bash -c \ "cd /home/frappe/frappe-bench && bench --site $SITE_NAME execute $1" \ || log " ⚠️ $1 a esuat — verifica manual" } # Helper: insert/set-value tolerant la 'exista deja' bexec_kw() { docker exec didi-erpnext bash -c \ "cd /home/frappe/frappe-bench && bench --site $SITE_NAME execute $1 --kwargs '$2'" \ >/dev/null 2>&1 || true } # ── Pas 1. Pornire containere ─────────────────────────────── log "Pas 1/10. Pornire containere..." docker compose up -d log " Astept MariaDB sa fie healthy..." for i in $(seq 1 30); do status=$(docker inspect --format='{{.State.Health.Status}}' didi-mariadb 2>/dev/null || echo "starting") [ "$status" = "healthy" ] && break sleep 2 done [ "$status" = "healthy" ] || err "MariaDB nu a devenit healthy dupa 60s" log " OK" # ── Pas 2. Astepta ERPNext gunicorn ──────────────────────── log "Pas 2/10. Astept ERPNext gunicorn..." for i in $(seq 1 30); do if docker exec didi-erpnext curl -sf http://127.0.0.1:8000 >/dev/null 2>&1; then log " OK" break fi sleep 3 done # ── Pas 3. Config Redis ───────────────────────────────────── log "Pas 3/10. Configurare Redis in common_site_config..." docker exec didi-erpnext bash -c " cd /home/frappe/frappe-bench && bench set-config -g redis_cache 'redis://redis-cache:6379' && bench set-config -g redis_queue 'redis://redis-queue:6379' && bench set-config -g redis_socketio 'redis://redis-queue:6379' " >/dev/null # ── Pas 4. Creare site daca nu exista ────────────────────── log "Pas 4/10. Verificare/creare site $SITE_NAME..." if docker exec didi-erpnext test -f "/home/frappe/frappe-bench/sites/${SITE_NAME}/site_config.json"; then log " Site $SITE_NAME exista deja" else log " Creez site $SITE_NAME (dureaza ~3 min)..." docker exec didi-erpnext bash -c " cd /home/frappe/frappe-bench && bench new-site $SITE_NAME \ --mariadb-root-password '$MYSQL_ROOT_PASSWORD' \ --admin-password '$ADMIN_PASSWORD' \ --db-host mariadb \ --install-app erpnext " fi # ── Pas 5. Fix MariaDB user @ wildcard ────────────────────── log "Pas 5/10. Fix DB user pentru orice host..." DB_NAME=$(docker exec didi-erpnext python3 -c " import json with open('/home/frappe/frappe-bench/sites/${SITE_NAME}/site_config.json') as f: print(json.load(f)['db_name']) ") DB_PWD=$(docker exec didi-erpnext python3 -c " import json with open('/home/frappe/frappe-bench/sites/${SITE_NAME}/site_config.json') as f: print(json.load(f)['db_password']) ") docker exec didi-mariadb mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" -e " CREATE USER IF NOT EXISTS '$DB_NAME'@'%' IDENTIFIED BY '$DB_PWD'; GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_NAME'@'%'; FLUSH PRIVILEGES; " 2>/dev/null # ── Pas 6. Patch portal_user + install didi_custom ───────── log "Pas 6/10. Patch portal_users + install didi_custom..." docker exec didi-erpnext sed -i \ 's|for portal_user in self.portal_users:|for portal_user in (getattr(self, "portal_users", None) or []):|' \ /home/frappe/frappe-bench/apps/erpnext/erpnext/selling/doctype/customer/customer.py 2>/dev/null || true docker exec didi-erpnext bash -c " cd /home/frappe/frappe-bench && printf 'frappe\nerpnext\ndidi_custom\n' > sites/apps.txt && env/bin/pip install -e apps/didi_custom 2>&1 | tail -2 && bench --site $SITE_NAME install-app didi_custom 2>&1 | tail -2 || true " >/dev/null # Fix installed_apps registry docker exec didi-mariadb mariadb -uroot -p"$MYSQL_ROOT_PASSWORD" "$DB_NAME" -e " UPDATE tabDefaultValue SET defvalue='[\"frappe\",\"erpnext\",\"didi_custom\"]' WHERE defkey='installed_apps'; " 2>/dev/null # ── Pas 7. Companie + plan de conturi Romania ────────────── log "Pas 7/10. Companie TOP CLOSSERS SRL + plan de conturi RO..." # Warehouse Type "Transit" e cerut la crearea companiei si lipseste in instalarea curata bexec_kw frappe.client.insert '{"doc":{"doctype":"Warehouse Type","name":"Transit"}}' bexec didi_custom.make_company_ro3.execute bexec didi_custom.setup_ro_coa.execute # An fiscal curent — lipseste complet dupa instalarea curata; facturile pica fara el YEAR=$(date +%Y) log " An fiscal $YEAR..." bexec_kw frappe.client.insert "{\"doc\":{\"doctype\":\"Fiscal Year\",\"year\":\"$YEAR\",\"year_start_date\":\"$YEAR-01-01\",\"year_end_date\":\"$YEAR-12-31\"}}" # Price list implicit in RON bexec_kw frappe.client.set_value '{"doctype":"Price List","name":"Standard Selling","fieldname":"currency","value":"RON"}' # ── Pas 8. Doctypes, campuri custom, continut CMS ────────── log "Pas 8/10. Doctypes + campuri custom + CMS..." bexec didi_custom.setup_doctypes.execute bexec didi_custom.integrations.migrations.setup_custom_fields.execute bexec didi_custom.fix_sa.execute bexec didi_custom.populate_content.execute # ── Pas 9. Integrare website, print format, email, workspace ─ log "Pas 9/10. Items DIDI + user API + print format + email + workspace..." bexec didi_custom.setup_website_integration.execute bexec didi_custom.setup_phase2_final.setup_print_format bexec didi_custom.setup_website_integration.fix_print_format bexec didi_custom.setup_email.execute bexec didi_custom.setup_workspaces.execute bexec didi_custom.setup_shortcuts.execute # ── Pas 10. Build + restart ──────────────────────────────── log "Pas 10/10. Build assets + restart..." docker exec didi-erpnext bash -c 'cd /home/frappe/frappe-bench && bench build --force' 2>&1 | tail -2 docker exec didi-redis-cache redis-cli FLUSHALL >/dev/null docker exec didi-redis-queue redis-cli FLUSHALL >/dev/null docker restart didi-erpnext >/dev/null sleep 8 # ── Verificare finala ─────────────────────────────────────── HTTP=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${ERPNEXT_PORT}") log "=============================================" if [ "$HTTP" = "200" ] || [ "$HTTP" = "301" ] || [ "$HTTP" = "302" ]; then log " ERPNext UP: http://localhost:${ERPNEXT_PORT}" log " Login: Administrator / ${ADMIN_PASSWORD}" log " Site: ${SITE_NAME}" else log " HTTP ${HTTP} - verifica log-urile cu: docker compose logs erpnext" fi log "=============================================" log "" log "Credentialele API pentru website (user website_api@didi-erp.local) se genereaza" log "la Pas 9 — vezi output-ul lui setup_website_integration.execute." log "Pentru date de test (smoke testing): bash seed-data.sh"