almeira-web/deploy_dripzone_vps.py

180 lines
8.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import paramiko
import tarfile
import os
import sys
import io
# Force UTF-8 stdout on Windows
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
HOST = "161.97.93.156"
USER = "carlos"
PASSWORD = "Innova1978+*?"
LOCAL_DIR = r"c:\Users\Carlos\dev\almeira-web\dripzone-app"
ARCHIVE_NAME = r"c:\Users\Carlos\dev\almeira-web\dripzone_deploy.tar.gz"
REMOTE_DIR = "/home/carlos/apps/dripzone-app"
REMOTE_TAR = "/home/carlos/apps/dripzone_deploy.tar.gz"
def create_tar():
print("[1/4] Empaquetando dripzone-app para despliegue de produccion...")
with tarfile.open(ARCHIVE_NAME, "w:gz") as tar:
for root, dirs, files in os.walk(LOCAL_DIR):
# Excluir node_modules, .next, android (para mantener paquete ligero)
dirs[:] = [d for d in dirs if d not in ['node_modules', '.next', 'android', '.git']]
for file in files:
if file.endswith('.apk'):
continue
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, LOCAL_DIR)
tar.add(full_path, arcname=rel_path)
size_mb = os.path.getsize(ARCHIVE_NAME) / (1024 * 1024)
print(f"[OK] Archivo generado: {ARCHIVE_NAME} ({size_mb:.2f} MB)")
def deploy():
create_tar()
print(f"[2/4] Conectando a VPS {HOST} via SSH / SFTP...")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connected = False
for attempt in range(1, 4):
try:
print(f" Intento {attempt} de conexion a {HOST}...")
client.connect(hostname=HOST, username=USER, password=PASSWORD, timeout=60, banner_timeout=60, auth_timeout=60)
connected = True
break
except Exception as conn_err:
print(f" Aviso: fallo en intento {attempt}: {conn_err}")
import time
time.sleep(2)
if not connected:
raise Exception(f"No se pudo conectar a {HOST} tras 3 intentos")
sftp = client.open_sftp()
print("[3/4] Subiendo archivo comprimido y APK al servidor...")
sftp.put(ARCHIVE_NAME, REMOTE_TAR)
apk_local = r"c:\Users\Carlos\dev\almeira-web\dripzone-app\public\DripZone.apk"
if os.path.exists(apk_local):
local_size = os.path.getsize(apk_local)
remote_apk = f"{REMOTE_DIR}/public/DripZone.apk"
should_upload = True
try:
r_stat = sftp.stat(remote_apk)
if r_stat.st_size == local_size:
print(f" -> DripZone.apk ({local_size/(1024*1024):.1f} MB) ya se encuentra actualizado en el VPS. Omitiendo subida redundante.")
should_upload = False
except:
pass
if should_upload:
print(f" -> Subiendo DripZone.apk ({local_size/(1024*1024):.1f} MB) a public/...")
try:
sftp.mkdir(f"{REMOTE_DIR}/public")
except:
pass
try:
sftp.put(apk_local, remote_apk)
print(" [OK] DripZone.apk subido al VPS.")
except Exception as e:
print(f" [Aviso] Error en subida SFTP de APK: {e}. Continuando con el despliegue...")
sftp.close()
print("[OK] Archivos base subidos exitosamente.")
commands = f"""
mkdir -p {REMOTE_DIR}
tar -xzf {REMOTE_TAR} -C {REMOTE_DIR}
rm -f {REMOTE_TAR}
echo '{PASSWORD}' | sudo -S chown -R carlos:carlos /home/carlos/.docker || true
chmod -R 777 /home/carlos/.docker || true
cd {REMOTE_DIR}
docker compose -f docker-compose.prod.yml down --remove-orphans || true
DOCKER_BUILDKIT=1 docker compose -f docker-compose.prod.yml up -d --build
sleep 3
docker exec -u 0 dripzone-production chown -R nextjs:nodejs /app/data /app/public || true
docker exec -u 0 dripzone-production chmod -R 777 /app/data /app/public || true
docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/public/DripZone.apk || true
docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/DripZone.apk || true
docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/public/dripzone.apk || true
docker cp {REMOTE_DIR}/public/DripZone.apk dripzone-production:/app/public/app-dripzone.apk || true
docker exec dripzone-production chmod 644 /app/public/DripZone.apk /app/DripZone.apk /app/public/dripzone.apk /app/public/app-dripzone.apk || true
docker ps | grep dripzone
"""
print("[4/4] Ejecutando compilacion y levantando contenedor en Produccion...")
stdin, stdout, stderr = client.exec_command(commands)
while not stdout.channel.exit_status_ready():
line = stdout.readline()
if line:
print(line.strip())
for line in stdout.readlines():
print(line.strip())
err = stderr.read().decode('utf-8', errors='ignore')
if err:
print("[Logs/Avisos]:", err)
status = stdout.channel.recv_exit_status()
print(f"[EXIT STATUS]: {status}")
client.close()
if status == 0:
send_apk_to_whatsapp()
def send_apk_to_whatsapp():
import urllib.request
import json
print("[5/5] Enviando APK y reporte de actualizacion al WhatsApp de Carlos (+57 316 745 5877)...")
url = "https://evolution.almeira.com.co/message/sendMedia/AutomataAlmeira"
headers = {
"apikey": "EE49029EEB85-4568-8F81-C9FDFC6E2943",
"Content-Type": "application/json"
}
caption = (
"🚀 *¡PUBLICACIÓN AUTOMÁTICA EN VIVO & CATÁLOGO WHATSAPP SINCRONIZADO!* 👟📲\n"
"━━━━━━━━━━━━━━━━━━━━━━\n"
"✨ *Sistema Autónomo de Captura y Notificación 100% Activo:*\n\n"
"1️⃣ 🌐 *Webhook Evolution API Reconfigurado y Conectado*: El webhook de la instancia `AutomataAlmeira` quedó apuntando directamente a `https://drip-zone.online/api/webhook/whatsapp` procesando eventos `MESSAGES_UPSERT`, `MESSAGES_UPDATE` y `SEND_MESSAGE` en tiempo real.\n\n"
"2️⃣ ⚡ *Publicación Incondicional de Mensajes*: Se eliminaron filtros restrictivos. Todo mensaje con foto de calzado entrante en grupos mayoristas se procesa, se parsea con IA Gemini y se publica de inmediato en el catálogo de la tienda web y app móvil.\n\n"
"3️⃣ 📢 *Notificación Instantánea con Foto y Costos a tu WhatsApp*: Cada vez que un producto se publica, el sistema te envía automáticamente un mensaje con la *fotografía en alta definición*, el *valor de venta al público*, el *costo de proveedor*, la *ganancia neta estimada*, las *tallas disponibles* y el enlace directo a la web y panel admin.\n\n"
"4️⃣ 🛍️ *Sincronización con Catálogo de WhatsApp (Meta Commerce)*: Cada producto nuevo o modificado queda inmediatamente disponible en el feed XML de Meta Commerce (`/api/catalog/feed.xml`) y sincronizado con el Catálogo de WhatsApp Business.\n\n"
"5️⃣ 📲 *APK Android Actualizado*: Compilado y listo para usar en tu celular.\n\n"
"👉 *Tienda Web:* https://drip-zone.online\n"
"👉 *Panel de Productos:* https://drip-zone.online/admin/productos\n"
"📥 *Descarga directa del APK:* https://drip-zone.online/DripZone.apk"
)
payload = {
"number": "573167455877",
"mediatype": "document",
"mimetype": "application/vnd.android.package-archive",
"caption": caption,
"media": "https://drip-zone.online/DripZone.apk",
"fileName": "DripZone.apk"
}
try:
req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers)
with urllib.request.urlopen(req, timeout=180) as resp:
print(f"[OK] APK despachada con exito al WhatsApp de Carlos (HTTP {resp.status})")
except Exception as e:
print(f"[Aviso] No se pudo enviar archivo multimedia por Evolution: {e}. Enviando mensaje de texto...")
text_url = "https://evolution.almeira.com.co/message/sendText/AutomataAlmeira"
text_payload = {
"number": "573167455877",
"text": (
f"{caption}\n\n"
"📥 *Descarga e instala el APK actualizado aquí:*\n"
"👉 https://drip-zone.online/DripZone.apk"
)
}
try:
req2 = urllib.request.Request(text_url, data=json.dumps(text_payload).encode('utf-8'), headers=headers)
with urllib.request.urlopen(req2, timeout=20) as resp2:
print(f"[OK] Notificacion de texto enviada por WhatsApp (HTTP {resp2.status})")
except Exception as e2:
print(f"[Error] No se pudo enviar texto por WhatsApp: {e2}")
if __name__ == "__main__":
deploy()