178 lines
8.1 KiB
Python
178 lines
8.1 KiB
Python
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()
|