160 lines
6.6 KiB
Python
160 lines
6.6 KiB
Python
"""Parseo de argumentos de línea de comandos y configuración de la aplicación."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import getpass
|
|
import os
|
|
import sys
|
|
from dataclasses import dataclass
|
|
|
|
DEFAULT_PORT = 993
|
|
DEFAULT_TIMEOUT = 30
|
|
DEFAULT_RETRIES = 3
|
|
DEFAULT_STATE_FILE = "migration-state.json"
|
|
DEFAULT_LOG_DIR = "logs"
|
|
DEFAULT_HEADER_BATCH_SIZE = 500
|
|
|
|
EXIT_PARAM_ERROR = 2
|
|
|
|
|
|
@dataclass
|
|
class ServerConfig:
|
|
label: str
|
|
host: str
|
|
port: int
|
|
user: str
|
|
password: str
|
|
domain: str | None = None
|
|
ip: str | None = None
|
|
verify_tls: bool = True
|
|
use_ssl: bool = True
|
|
|
|
@property
|
|
def connect_host(self) -> str:
|
|
return self.ip or self.host
|
|
|
|
@property
|
|
def tls_hostname(self) -> str:
|
|
return self.host
|
|
|
|
|
|
@dataclass
|
|
class AppConfig:
|
|
server_a: ServerConfig
|
|
server_b: ServerConfig
|
|
timeout: int
|
|
retries: int
|
|
state_file: str
|
|
log_dir: str
|
|
header_batch_size: int
|
|
verbose: bool
|
|
|
|
|
|
def _resolve_password(cli_value: str | None, env_var: str, prompt_label: str) -> str:
|
|
if cli_value:
|
|
return cli_value
|
|
env_value = os.environ.get(env_var)
|
|
if env_value:
|
|
return env_value
|
|
try:
|
|
return getpass.getpass(f"{prompt_label}: ")
|
|
except (EOFError, KeyboardInterrupt):
|
|
print("\nNo se proporcionó contraseña.", file=sys.stderr)
|
|
sys.exit(EXIT_PARAM_ERROR)
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(
|
|
prog="imap_migrate.py",
|
|
description="Migra o respalda el contenido completo de una cuenta IMAP entre dos servidores.",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Ejemplo:
|
|
python3 imap_migrate.py \\
|
|
--aserver imap.servidor-a.com --aport 993 \\
|
|
--auser usuario@dominio.com --apassword 'passwordA' \\
|
|
--bserver imap.servidor-b.com --bport 993 \\
|
|
--buser usuario@dominio.com --bpassword 'passwordB'
|
|
|
|
Si no se proporcionan las contraseñas, se solicitarán de forma interactiva
|
|
(o pueden definirse en las variables de entorno IMAP_A_PASSWORD / IMAP_B_PASSWORD).
|
|
|
|
Ejecutar el programa varias veces sobre el mismo --state-file continúa una
|
|
migración incremental sin volver a copiar mensajes ya migrados.
|
|
""",
|
|
)
|
|
|
|
group_a = parser.add_argument_group("Servidor origen (A)")
|
|
group_a.add_argument("--aserver", required=True, help="Host del servidor IMAP origen")
|
|
group_a.add_argument("--aport", type=int, default=DEFAULT_PORT, help=f"Puerto del servidor origen (por defecto {DEFAULT_PORT})")
|
|
group_a.add_argument("--auser", required=True, help="Usuario/cuenta en el servidor origen")
|
|
group_a.add_argument("--apassword", default=None, help="Password del servidor origen (si se omite, se solicita de forma interactiva)")
|
|
group_a.add_argument("--adomain", default=None, help="Dominio lógico de la cuenta origen (informativo / uso futuro multi-cuenta)")
|
|
group_a.add_argument("--aip", default=None, help="IP a la que conectar en el servidor origen, usando --aserver como hostname TLS")
|
|
group_a.add_argument("--a-no-verify-tls", action="store_true", help="Deshabilita verificación de certificado TLS en el servidor origen (inseguro)")
|
|
group_a.add_argument("--a-no-ssl", action="store_true", help="Conecta sin SSL/TLS al servidor origen (inseguro, no recomendado)")
|
|
|
|
group_b = parser.add_argument_group("Servidor destino (B)")
|
|
group_b.add_argument("--bserver", required=True, help="Host del servidor IMAP destino")
|
|
group_b.add_argument("--bport", type=int, default=DEFAULT_PORT, help=f"Puerto del servidor destino (por defecto {DEFAULT_PORT})")
|
|
group_b.add_argument("--buser", required=True, help="Usuario/cuenta en el servidor destino")
|
|
group_b.add_argument("--bpassword", default=None, help="Password del servidor destino (si se omite, se solicita de forma interactiva)")
|
|
group_b.add_argument("--bdomain", default=None, help="Dominio lógico de la cuenta destino (informativo / uso futuro multi-cuenta)")
|
|
group_b.add_argument("--bip", default=None, help="IP a la que conectar en el servidor destino, usando --bserver como hostname TLS")
|
|
group_b.add_argument("--b-no-verify-tls", action="store_true", help="Deshabilita verificación de certificado TLS en el servidor destino (inseguro)")
|
|
group_b.add_argument("--b-no-ssl", action="store_true", help="Conecta sin SSL/TLS al servidor destino (inseguro, no recomendado)")
|
|
|
|
parser.add_argument("--no-verify-tls", action="store_true", help="Deshabilita verificación de certificado TLS en AMBOS servidores (inseguro)")
|
|
|
|
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help=f"Timeout de conexión en segundos (por defecto {DEFAULT_TIMEOUT})")
|
|
parser.add_argument("--retries", type=int, default=DEFAULT_RETRIES, help=f"Número de reintentos ante fallos de conexión (por defecto {DEFAULT_RETRIES})")
|
|
parser.add_argument("--state-file", default=DEFAULT_STATE_FILE, help=f"Archivo de estado de la migración (por defecto {DEFAULT_STATE_FILE})")
|
|
parser.add_argument("--log-dir", default=DEFAULT_LOG_DIR, help=f"Directorio de logs (por defecto {DEFAULT_LOG_DIR})")
|
|
parser.add_argument("--header-batch-size", type=int, default=DEFAULT_HEADER_BATCH_SIZE, help="Cantidad de mensajes a analizar por lote (por defecto %(default)s)")
|
|
parser.add_argument("--verbose", action="store_true", help="Muestra en consola los mismos detalles que se registran en el log")
|
|
|
|
return parser
|
|
|
|
|
|
def parse_args(argv: list[str] | None = None) -> AppConfig:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
a_password = _resolve_password(args.apassword, "IMAP_A_PASSWORD", "Password servidor A")
|
|
b_password = _resolve_password(args.bpassword, "IMAP_B_PASSWORD", "Password servidor B")
|
|
|
|
server_a = ServerConfig(
|
|
label="A",
|
|
host=args.aserver,
|
|
port=args.aport,
|
|
user=args.auser,
|
|
password=a_password,
|
|
domain=args.adomain,
|
|
ip=args.aip,
|
|
verify_tls=not (args.no_verify_tls or args.a_no_verify_tls),
|
|
use_ssl=not args.a_no_ssl,
|
|
)
|
|
server_b = ServerConfig(
|
|
label="B",
|
|
host=args.bserver,
|
|
port=args.bport,
|
|
user=args.buser,
|
|
password=b_password,
|
|
domain=args.bdomain,
|
|
ip=args.bip,
|
|
verify_tls=not (args.no_verify_tls or args.b_no_verify_tls),
|
|
use_ssl=not args.b_no_ssl,
|
|
)
|
|
|
|
return AppConfig(
|
|
server_a=server_a,
|
|
server_b=server_b,
|
|
timeout=args.timeout,
|
|
retries=args.retries,
|
|
state_file=args.state_file,
|
|
log_dir=args.log_dir,
|
|
header_batch_size=args.header_batch_size,
|
|
verbose=args.verbose,
|
|
)
|