almeira-web/deploy_dripzone_vps.py

178 lines
8.1 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 dripzone-production chown -R nextjs:nodejs /app/data || 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 = (
"👑 *¡ACCESO DE ADMINISTRADOR RESTAURADO & APK COMPILADO!* 🚀👟\n"
"━━━━━━━━━━━━━━━━━━━━━━\n"
"✨ *Ajustes implementados y desplegados en Producción:*\n\n"
"1️⃣ 🛡️ *Botón de Admin Permanente en la Web*: Restaurado de forma visible y accesible en la barra superior de navegación de https://drip-zone.online. Ahora está siempre disponible para ingresar y destacado con gradiente morado/fucsia cuando inicias sesión.\n"
"2️⃣ 👑 *Rol de Super Administrador para Carlos*: Tu celular personal *3167455877* (+57 316 745 5877) quedó configurado como Administrador Maestro incondicional en el servicio de datos (`dataService`), sesión web (`AuthContext`), API de usuarios y panel de control (`/admin`), con badge '👑 Super Administrador'.\n"
"3️⃣ 🚀 *Despliegue en Servidor VPS*: Contenedor `dripzone-production` reconstruido e iniciado con éxito en el servidor de producción.\n"
"4️⃣ 📲 *APK Android Actualizado*: Compilado y disponible para descarga directa.\n\n"
"👉 *Panel de Administración:* https://drip-zone.online/admin\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()