Subida a producción

This commit is contained in:
David Arranz 2025-11-30 23:44:55 +01:00
parent c349d12f9d
commit b8e250e417
15 changed files with 212 additions and 163 deletions

31
Dockerfile Normal file
View File

@ -0,0 +1,31 @@
# syntax=docker/dockerfile:1.4
# Usa una imagen base de Python
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_HOME="/app"
WORKDIR ${APP_HOME}
# Instalar librerías cliente Firebird
RUN apt-get update
RUN apt-get install libfbclient2 -y
# Copiamos solo lo necesario para instalar el paquete
COPY pyproject.toml setup.cfg README.md ./
COPY app ./app
RUN pip install --no-cache-dir .
# Copiar enviroment (se sobreescribe en compose)
#COPY enviroment/ ./enviroment
# Volumen para logs persistentes
#VOLUME ["/app/logs"]
# Entrypoint genérico
#CMD ["python", "-m", "sync_factuges_main"]
CMD ["factuges-sync", "all"]

View File

@ -3,42 +3,65 @@
# Permite: # Permite:
# factuges-sync factuges # factuges-sync factuges
# factuges-sync verifactu # factuges-sync verifactu
# factuges-sync all
#
# También por variable de entorno:
# SYNC_MODE=factuges factuges-sync # SYNC_MODE=factuges factuges-sync
import argparse import argparse
import os import os
import subprocess import subprocess
import sys import sys
from typing import Literal, Optional
Mode = Literal["factuges", "verifactu", "all"]
def main(): def run_module(module: str) -> None:
"""Lanza un módulo Python como subproceso y falla en caso de error."""
subprocess.run([sys.executable, "-m", module], check=True)
def resolve_mode(arg_mode: Optional[str]) -> Mode:
"""Resuelve el modo desde CLI o variable de entorno."""
mode = arg_mode or os.getenv("SYNC_MODE")
valid_modes: tuple[Mode, ...] = ("factuges", "verifactu", "all")
if mode not in valid_modes:
print(
"Error: debes indicar modo: 'factuges', 'verifactu' "
"o 'all' para ejecutar ambos"
)
sys.exit(1)
return mode # type: ignore[return-value]
def main() -> None:
"""Selector de modos de sincronización.""" """Selector de modos de sincronización."""
parser = argparse.ArgumentParser(description="Factuges Sync Dispatcher") parser = argparse.ArgumentParser(description="Factuges Sync Dispatcher")
parser.add_argument( parser.add_argument(
"mode", "mode",
nargs="?", nargs="?",
choices=["factuges", "verifactu"], choices=["factuges", "verifactu", "all"],
help="Modo de sincronización", help="Modo de sincronización",
) )
args = parser.parse_args() args = parser.parse_args()
mode = args.mode or os.getenv("SYNC_MODE") mode = resolve_mode(args.mode)
if mode not in ("factuges", "verifactu"): if os.getenv("ENV") == "development":
print("Error: debes indicar modo: 'factuges' o 'verifactu'")
sys.exit(1)
if os.getenv("ENV") == "developement":
print("Running in development mode (no docker)") print("Running in development mode (no docker)")
module = ( if mode == "factuges":
"app.sync_factuges_main" run_module("app.sync_factuges_main")
if mode == "factuges" elif mode == "verifactu":
else "app.sync_verifactu_main" run_module("app.sync_verifactu_main")
) else: # mode == "all"
# Primero sincroniza FactuGES, luego Verifactu.
# Ejecuta el módulo Python correspondiente # Si la primera falla, el proceso termina por el check=True.
subprocess.run([sys.executable, "-m", module], check=True) run_module("app.sync_factuges_main")
run_module("app.sync_verifactu_main")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,10 +1,11 @@
import os import os
from os.path import join, dirname from os.path import dirname, join
from typing import Any, Dict, Optional from typing import Any, Dict
from .setup_logger import logger
from dotenv import load_dotenv from dotenv import load_dotenv
from .setup_logger import logger
def _required(name: str) -> str: def _required(name: str) -> str:
""" """
@ -43,7 +44,7 @@ def load_config() -> Dict[str, Any]:
# Opcionales (con valor por defecto) # Opcionales (con valor por defecto)
"ENV": env, "ENV": env,
"LOCAL_TZ": os.getenv("LOCAL_TZ", "Europe/Madrid"), "LOCAL_TZ": os.getenv("LOCAL_TZ", "Europe/Madrid"),
"LAST_RUN_PATH": _required("LAST_RUN_PATH"), "STATE_PATH": _required("STATE_PATH"),
# FACTUGES (requeridas) # FACTUGES (requeridas)
"FACTUGES_HOST": _required("FACTUGES_HOST"), "FACTUGES_HOST": _required("FACTUGES_HOST"),

View File

@ -1,49 +1,68 @@
# app/logger.py
from __future__ import annotations
import logging import logging
import sys
import os import os
import sys
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from pathlib import Path from pathlib import Path
from typing import Optional, Union
def create_logger( def create_logger(
name: str = "factuges-sync", name: str = "factuges-sync",
*, *,
level: int = logging.INFO, level: int = logging.INFO,
log_path: str | None = None, log_path: Optional[Union[str, Path]] = None,
max_bytes: int = 5_000_000, # rotación opcional
backup_count: int = 3,
) -> logging.Logger: ) -> logging.Logger:
""" """
Crea un logger: Crea un logger consistente para FactuGES Sync.
- SIEMPRE stdout (Docker-friendly)
- SOLO EN PRODUCCIÓN añade RotatingFileHandler si log_path no es None Reglas:
- SIEMPRE envia logs a stdout (Docker-friendly).
- SOLO en producción escribe también a fichero si `log_path` está definido.
- `log_path` puede ser `str` o `Path`.
- Evita duplicar handlers.
""" """
logger = logging.getLogger(name) logger = logging.getLogger(name)
logger.setLevel(level) logger.setLevel(level)
# No duplicar handlers si ya existe el logger # Si ya está configurado, no duplicamos handlers
if logger.handlers: if logger.handlers:
return logger return logger
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
# ------------------------------------------------------------------ # ------------------------------
# 1) Consola → SIEMPRE (para docker logs) # 1) Handler de consola (siempre)
# ------------------------------------------------------------------ # ------------------------------
h_console = logging.StreamHandler(sys.stdout) h_console = logging.StreamHandler(sys.stdout)
h_console.setFormatter(formatter) h_console.setFormatter(formatter)
logger.addHandler(h_console) logger.addHandler(h_console)
# ------------------------------------------------------------------ # ------------------------------
# 2) Fichero → SOLO en prod + si se define log_path # 2) Handler de fichero (solo prod)
# ------------------------------------------------------------------ # ------------------------------
environment = os.getenv("ENV", "development") environment = os.getenv("ENV", "development").lower()
if environment == "production" and log_path:
Path(log_path).parent.mkdir(parents=True, exist_ok=True) if log_path and environment in ("production", "prod"):
p = Path(log_path)
# Aseguramos directorios
p.parent.mkdir(parents=True, exist_ok=True)
# Puedes usar FileHandler simple, pero Rotating es más seguro.
h_file = RotatingFileHandler( h_file = RotatingFileHandler(
log_path, filename=str(p),
maxBytes=5 * 1024 * 1024, maxBytes=max_bytes,
backupCount=15, backupCount=backup_count,
encoding="utf8", encoding="utf-8",
) )
h_file.setFormatter(formatter) h_file.setFormatter(formatter)
logger.addHandler(h_file) logger.addHandler(h_file)

View File

@ -1,11 +1,12 @@
import sys import sys
from app.config import get_package_version
from datetime import datetime from datetime import datetime
from pathlib import Path
from dateutil import tz from dateutil import tz
from app.config import create_logger, load_config, log_config
from app.db import get_mysql_connection, get_factuges_connection, sync_invoices_factuges from app.config import create_logger, get_package_version, load_config, log_config
from app.utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion from app.db import get_factuges_connection, get_mysql_connection, sync_invoices_factuges
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
def main(): def main():
@ -14,10 +15,14 @@ def main():
version = get_package_version() version = get_package_version()
local_tz = tz.gettz(config['LOCAL_TZ']) local_tz = tz.gettz(config['LOCAL_TZ'])
state_path = Path(config["STATE_PATH"])
# Logging # Logging
log_dir = state_path / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
logger = create_logger( logger = create_logger(
name="factuges-sync", name="factuges-sync",
log_path="/app/logs/sync_factuges.log", # Solo lo genera en producción log_path=log_dir / "sync_factuges.log", # Solo lo genera en producción
) )
logger.info("============================================================") logger.info("============================================================")
@ -28,11 +33,12 @@ def main():
log_config(config) log_config(config)
state_file = state_path / "factuges_last.ini"
conn_factuges = None conn_factuges = None
conn_mysql = None conn_mysql = None
try: try:
# Obtener la fecha de la última ejecución del programa # Obtener la fecha de la última ejecución del programa
last_execution_date_utc = obtener_fecha_ultima_ejecucion(config['LAST_RUN_PATH']) last_execution_date_utc = obtener_fecha_ultima_ejecucion(state_file)
last_execution_date_local_tz = last_execution_date_utc.astimezone( last_execution_date_local_tz = last_execution_date_utc.astimezone(
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S") tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
@ -47,7 +53,7 @@ def main():
# Sincronizamos # Sincronizamos
logger.info( logger.info(
f">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web") ">>>>>>>>>>> INI Sync invoices FactuGES escritorio to FactuGES web")
sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz) sync_invoices_factuges(conn_factuges, conn_mysql, last_execution_date_local_tz)
# Confirmar los cambios # Confirmar los cambios
@ -55,9 +61,9 @@ def main():
conn_factuges.commit() conn_factuges.commit()
conn_factuges.close() conn_factuges.close()
conn_mysql.close() conn_mysql.close()
logger.info(f">>>>>>>>>>> FIN Sync invoices FactuGES escritorio to FactuGES web") logger.info(">>>>>>>>>>> FIN Sync invoices FactuGES escritorio to FactuGES web")
actualizar_fecha_ultima_ejecucion(config['LAST_RUN_PATH']) actualizar_fecha_ultima_ejecucion(state_file)
# Enviar email # Enviar email
# send_orders_mail(inserted_orders) # send_orders_mail(inserted_orders)

View File

@ -1,11 +1,12 @@
import sys import sys
from app.config import get_package_version
from datetime import datetime from datetime import datetime
from pathlib import Path
from dateutil import tz from dateutil import tz
from app.config import create_logger, load_config, log_config
from app.db import get_mysql_connection, get_factuges_connection, sync_invoices_verifactu from app.config import create_logger, get_package_version, load_config, log_config
from app.utils import obtener_fecha_ultima_ejecucion, actualizar_fecha_ultima_ejecucion from app.db import get_mysql_connection, sync_invoices_verifactu
from app.utils import actualizar_fecha_ultima_ejecucion, obtener_fecha_ultima_ejecucion
def main(): def main():
@ -15,10 +16,14 @@ def main():
version = get_package_version() version = get_package_version()
local_tz = tz.gettz(config['LOCAL_TZ']) local_tz = tz.gettz(config['LOCAL_TZ'])
state_path = Path(config["STATE_PATH"])
# Logging # Logging
log_dir = state_path / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
logger = create_logger( logger = create_logger(
name="factuges-sync", name="factuges-sync",
log_path="/app/logs/sync_verifactu.log", # Solo lo genera en producción log_path=log_dir / "sync_verifactu.log", # Solo lo genera en producción
) )
logger.info("============================================================") logger.info("============================================================")
@ -29,11 +34,12 @@ def main():
log_config(config) log_config(config)
state_file = Path(config["STATE_PATH"]) / "verifactu_last.ini"
conn_factuges = None conn_factuges = None
conn_mysql = None conn_mysql = None
try: try:
# Obtener la fecha de la última ejecución del programa # Obtener la fecha de la última ejecución del programa
last_execution_date_utc = obtener_fecha_ultima_ejecucion() last_execution_date_utc = obtener_fecha_ultima_ejecucion(state_file)
last_execution_date_local_tz = last_execution_date_utc.astimezone( last_execution_date_local_tz = last_execution_date_utc.astimezone(
tz=local_tz).strftime("%Y-%m-%d %H:%M:%S") tz=local_tz).strftime("%Y-%m-%d %H:%M:%S")
@ -47,13 +53,13 @@ def main():
# Sync Verifactu # Sync Verifactu
logger.info( logger.info(
f">>>>>>>>>> INI Sync facturas emitidas to Verifactu") ">>>>>>>>>> INI Sync facturas emitidas to Verifactu")
sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz) sync_invoices_verifactu(conn_mysql, last_execution_date_local_tz)
conn_mysql.commit() conn_mysql.commit()
conn_mysql.close() conn_mysql.close()
logger.info(f">>>>>>>>>> FIN Sync facturas emitidas to Verifactu") logger.info(">>>>>>>>>> FIN Sync facturas emitidas to Verifactu")
actualizar_fecha_ultima_ejecucion() actualizar_fecha_ultima_ejecucion(state_file)
# Enviar email # Enviar email
# send_orders_mail(inserted_orders) # send_orders_mail(inserted_orders)

View File

@ -2,74 +2,68 @@ from __future__ import annotations
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
import os
from typing import Optional from typing import Optional
# Fecha por defecto si nunca se ha ejecutado
DEFAULT_PATH = Path("last_execution.txt") DEFAULT_FALLBACK = datetime(2025, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
DEFAULT_FALLBACK = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc) FMT = "%Y-%m-%d %H:%M:%S" # Formato persistido
FMT = "%Y-%m-%d %H:%M:%S"
def obtener_fecha_ultima_ejecucion( def obtener_fecha_ultima_ejecucion(
path: str = DEFAULT_PATH, path: str | Path,
*, *,
fallback: Optional[datetime] = None, fallback: Optional[datetime] = None,
) -> datetime: ) -> datetime:
""" """
Lee la última fecha de ejecución desde `path` y la devuelve como aware (UTC). Lee la última fecha de ejecución almacenada en `path`.
Retorna always-aware UTC.
- Si el fichero no existe o el contenido es inválido, devuelve `fallback`. - Si el archivo no existe fallback
- Si `fallback` es None, usa DEFAULT_FALLBACK (2024-01-01T00:00:00Z). - Si está vacío o el formato es inválido fallback
fallback predeterminado: DEFAULT_FALLBACK (2025-01-01)
""" """
# Comentario: fallback explícito para evitar lógica duplicada en llamadas
effective_fallback = fallback or DEFAULT_FALLBACK effective_fallback = fallback or DEFAULT_FALLBACK
p = Path(path)
if path is None:
return effective_fallback
# 2. Convertimos str -> Path
path = Path(path)
try: try:
text = path.read_text(encoding="utf-8").strip() text = p.read_text(encoding="utf-8").strip()
if not text: if not text:
# Comentario: fichero vacío -> usamos fallback
return effective_fallback return effective_fallback
dt_naive = datetime.strptime(text, FMT) dt_naive = datetime.strptime(text, FMT)
return dt_naive.replace(tzinfo=timezone.utc) return dt_naive.replace(tzinfo=timezone.utc)
except FileNotFoundError: except FileNotFoundError:
return effective_fallback return effective_fallback
except ValueError: except ValueError:
# Comentario: formato inválido en el archivo -> fallback
return effective_fallback return effective_fallback
def actualizar_fecha_ultima_ejecucion( def actualizar_fecha_ultima_ejecucion(
path: str = DEFAULT_PATH, path: str | Path,
*, *,
momento: Optional[datetime] = None, momento: Optional[datetime] = None,
) -> None: ) -> None:
""" """
Escribe en `path` la fecha/hora (UTC) en formato YYYY-MM-DD HH:MM:SS. Guarda `momento` (UTC) en `path`, creando directorios si hace falta.
Si `momento` es None, usa ahora en UTC.
Crea directorios intermedios si no existen. - Si `momento` es None ahora en UTC
- Si `momento` viene naive se asume UTC
- Si trae tz se convierte a UTC
""" """
p = Path(path)
if momento is None: if momento is None:
momento = datetime.now(timezone.utc) momento = datetime.now(timezone.utc)
else: else:
# Normalizamos a UTC si viene con tz; si es naive, asumimos UTC
if momento.tzinfo is None: if momento.tzinfo is None:
momento = momento.replace(tzinfo=timezone.utc) momento = momento.replace(tzinfo=timezone.utc)
else: else:
momento = momento.astimezone(timezone.utc) momento = momento.astimezone(timezone.utc)
# Asegurar carpeta si `path` incluye directorios p.parent.mkdir(parents=True, exist_ok=True)
folder = os.path.dirname(os.path.abspath(path))
if folder and not os.path.exists(folder):
os.makedirs(folder, exist_ok=True)
with open(path, "w", encoding="utf8") as f: p.write_text(momento.strftime(FMT), encoding="utf-8")
f.write(momento.strftime(FMT))

View File

@ -1,6 +1,6 @@
ENV = development ENV = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt STATE_PATH = ./
#LOG_PATH = ./app.log #LOG_PATH = ./app.log
#DESARROLLO ACANA #DESARROLLO ACANA

View File

@ -1,6 +1,6 @@
ENV = development ENV = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt STATE_PATH = ./
#LOG_PATH = ./app.log #LOG_PATH = ./app.log
#DESARROLLO ACANA #DESARROLLO ACANA

View File

@ -1,6 +1,6 @@
ENV = development ENV = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt STATE_PATH = ./
#LOG_PATH = ./app.log #LOG_PATH = ./app.log
#DESARROLLO #DESARROLLO

View File

@ -1,6 +1,7 @@
ENV = development ENV = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = ./app.last_run.txt STATE_PATH = ./
#LOG_PATH = ./app.log #LOG_PATH = ./app.log
#DESARROLLO ACANA #DESARROLLO ACANA

View File

@ -1,7 +1,7 @@
# SYNC # SYNC
ENV = development ENV = development
LOCAL_TZ = Europe/Madrid LOCAL_TZ = Europe/Madrid
LAST_RUN_PATH = /usr/share/factuges-app/last_run_factuges.ini STATE_PATH = /app/state
FACTUGES_HOST = acana.mywire.org FACTUGES_HOST = acana.mywire.org
FACTUGES_PORT = 63050 FACTUGES_PORT = 63050

View File

@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
SCRIPT_VERSION="1.1.0" SCRIPT_VERSION="2.0.0"
# ================================================ # ================================================
# FACTUGES SYNC - Docker Build Script # FACTUGES SYNC - Docker Build Script (Simplificado)
# ----------------------------------------------- # -----------------------------------------------
# Uso: # Uso:
# ./build.sh <company> [--load] # ./build.sh <company> [--load]
@ -12,7 +12,7 @@ SCRIPT_VERSION="1.1.0"
# ---------- 1. Validación ---------- # ---------- 1. Validación ----------
if [[ $# -eq 0 || "$1" == --* ]]; then if [[ $# -eq 0 || "$1" == --* ]]; then
echo "ERROR: Falta el parámetro <company>" echo "ERROR: Falta el parámetro <company>"
echo "Uso: ./build.sh <company> [--load]" echo "Uso: ./build.sh <company> [--load]"
exit 1 exit 1
fi fi
@ -31,64 +31,45 @@ mkdir -p "$OUT_DIR"
# ---------- 3. Info ---------- # ---------- 3. Info ----------
DATE=$(date +'%Y%m%d-%H%M%S') DATE=$(date +'%Y%m%d-%H%M%S')
ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") ISO_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
USER_NAME=$(whoami)
IMAGE_VERSION=$(sed -n 's/^version[[:space:]]*=[[:space:]]*\(.*\)$/\1/p' "$PROJECT_DIR/setup.cfg" | head -n1) IMAGE_VERSION=$(sed -n 's/^version[[:space:]]*=[[:space:]]*\(.*\)$/\1/p' \
"$PROJECT_DIR/setup.cfg" | head -n1)
IMAGE_VERSION="${IMAGE_VERSION:-0.0.0}" IMAGE_VERSION="${IMAGE_VERSION:-0.0.0}"
IMAGE_NAME="factuges-sync"
TAG_VERSION="${IMAGE_NAME}:${COMPANY}-${IMAGE_VERSION}"
TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest"
echo "" echo ""
echo "-------------------------------------------------------" echo "-------------------------------------------------------"
echo " FACTUGES SYNC Build Script v${SCRIPT_VERSION}" echo " FACTUGES SYNC Build Script v${SCRIPT_VERSION}"
echo " Compañía: ${COMPANY}" echo " Compañía: ${COMPANY}"
echo " Versión: ${IMAGE_VERSION}" echo " Versión: ${IMAGE_VERSION}"
echo " Fecha: ${DATE}" echo " Fecha: ${DATE}"
echo "-------------------------------------------------------" echo "-------------------------------------------------------"
echo "" echo ""
# ---------- 4. Función para generar 1 build ---------- # ---------- 4. Build único ----------
build_image() { echo "📦 Construyendo imagen Docker..."
local MODE="$1" # factuges | verifactu
local IMAGE_NAME="factuges-sync-${MODE}" docker build --no-cache \
-t "${TAG_VERSION}" \
-t "${TAG_LATEST}" \
--build-arg COMPANY="${COMPANY}" \
-f "${PROJECT_DIR}/Dockerfile" "${PROJECT_DIR}"
local TAG_VERSION="${IMAGE_NAME}:${COMPANY}-${IMAGE_VERSION}" echo "✅ Imagen construida: ${TAG_VERSION}"
local TAG_LATEST="${IMAGE_NAME}:${COMPANY}-latest"
echo "📦 Construyendo imagen Docker (${MODE})..." # ---------- 5. Save tar ----------
TAR_V="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-v${IMAGE_VERSION}-${DATE}.tar"
TAR_LATEST="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-latest.tar"
docker build --no-cache \ docker save -o "${TAR_V}" "${TAG_VERSION}" "${TAG_LATEST}"
-t "${TAG_VERSION}" \ cp -f "${TAR_V}" "${TAR_LATEST}"
-t "${TAG_LATEST}" \
--build-arg COMPANY="${COMPANY}" \
-f "${PROJECT_DIR}/Dockerfile.${MODE}" "${PROJECT_DIR}"
echo "✅ Imagen construida: ${TAG_VERSION}" echo "📦 Imagen guardada:"
echo " - ${TAR_V}"
local TAR_V="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-v${IMAGE_VERSION}-${DATE}.tar" echo " - ${TAR_LATEST}"
local TAR_LATEST="${OUT_DIR}/${IMAGE_NAME}-${COMPANY}-latest.tar"
docker save -o "${TAR_V}" "${TAG_VERSION}" "${TAG_LATEST}"
cp -f "${TAR_V}" "${TAR_LATEST}"
echo "📦 Imagen guardada:"
echo " - ${TAR_V}"
echo " - ${TAR_LATEST}"
# Exportamos variables a nivel global para el LOAD opcional
echo "${TAR_V}"
echo "${TAR_LATEST}"
echo "${TAR_V}|${TAR_LATEST}"
}
# ---------- 5. Ejecutar build para ambos modos ----------
BUILD_OUT_FACTUGES=$(build_image "factuges")
FACTUGES_TAR_V=$(echo "$BUILD_OUT_FACTUGES" | cut -d '|' -f1)
FACTUGES_TAR_LATEST=$(echo "$BUILD_OUT_FACTUGES" | cut -d '|' -f2)
BUILD_OUT_VERIFACTU=$(build_image "verifactu")
VERIFACTU_TAR_V=$(echo "$BUILD_OUT_VERIFACTU" | cut -d '|' -f1)
VERIFACTU_TAR_LATEST=$(echo "$BUILD_OUT_VERIFACTU" | cut -d '|' -f2)
# ---------- 6. Manifest ---------- # ---------- 6. Manifest ----------
MANIFEST_FILE="${OUT_DIR}/manifest-${IMAGE_VERSION}-${DATE}.json" MANIFEST_FILE="${OUT_DIR}/manifest-${IMAGE_VERSION}-${DATE}.json"
@ -98,13 +79,9 @@ cat > "${MANIFEST_FILE}" <<EOF
"version": "${IMAGE_VERSION}", "version": "${IMAGE_VERSION}",
"build_time": "${ISO_DATE}", "build_time": "${ISO_DATE}",
"docker_images": { "docker_images": {
"factuges": { "sync": {
"versioned": "$(basename "${FACTUGES_TAR_V}")", "versioned": "$(basename "${TAR_V}")",
"latest": "$(basename "${FACTUGES_TAR_LATEST}")" "latest": "$(basename "${TAR_LATEST}")"
},
"verifactu": {
"versioned": "$(basename "${VERIFACTU_TAR_V}")",
"latest": "$(basename "${VERIFACTU_TAR_LATEST}")"
} }
} }
} }
@ -118,32 +95,23 @@ echo ""
if [[ "$LOAD" == true ]]; then if [[ "$LOAD" == true ]]; then
echo "📥 Subiendo imágenes al servidor..." echo "📥 Subiendo imágenes al servidor..."
# Subimos solo los .tar
scp -P 49152 "${OUT_DIR}"/*.tar \ scp -P 49152 "${OUT_DIR}"/*.tar \
rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/sync/ rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/sync/
scp -P 49152 deploy-cron.sh \
rodax@vps-2.rodax-software.com:/opt/factuges/${COMPANY}/
echo "📥 Cargando imágenes en Docker remoto..." echo "📥 Cargando imágenes en Docker remoto..."
ssh -p 49152 rodax@vps-2.rodax-software.com <<EOF ssh -p 49152 rodax@vps-2.rodax-software.com <<EOF
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${FACTUGES_TAR_V}") docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${TAR_V}")
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${FACTUGES_TAR_LATEST}") docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${TAR_LATEST}")
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${VERIFACTU_TAR_V}")
docker load -i /opt/factuges/${COMPANY}/sync/$(basename "${VERIFACTU_TAR_LATEST}")
EOF EOF
echo "✔ Todas las imágenes cargadas en producción" echo "✔ Imágenes cargadas"
fi fi
# ---------- 8. Resumen ---------- # ---------- 8. Resumen ----------
echo "" echo ""
echo "-------------------------------------------------------" echo "-------------------------------------------------------"
echo "🎯 BUILD COMPLETADO PARA '${COMPANY}'" echo "🎯 BUILD COMPLETADO PARA '${COMPANY}'"
echo " - factuges"
echo " - verifactu"
[[ "$LOAD" == true ]] && echo "✔ Load OK"
echo "🧩 Script version: ${SCRIPT_VERSION}" echo "🧩 Script version: ${SCRIPT_VERSION}"
echo "-------------------------------------------------------" echo "-------------------------------------------------------"
echo "" echo ""

View File

@ -6,7 +6,7 @@ services:
environment: environment:
ENV: "production" ENV: "production"
LOCAL_TZ: "Europe/Madrid" LOCAL_TZ: "Europe/Madrid"
LAST_RUN_PATH: "${LAST_RUN_PATH}" STATE_PATH: "${STATE_PATH}"
FACTUGES_HOST: "${FACTUGES_HOST}" FACTUGES_HOST: "${FACTUGES_HOST}"
FACTUGES_PORT: "${FACTUGES_PORT}" FACTUGES_PORT: "${FACTUGES_PORT}"

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = factuges-sync name = factuges-sync
version = 0.0.21 version = 0.0.25
description = ETL job to sync data from legacy DB to MariaDB description = ETL job to sync data from legacy DB to MariaDB
author = Rodax Software author = Rodax Software
author_email = info@rodax-software.com author_email = info@rodax-software.com