Escaner gráfico para ClamAV

Aplicaciones 15 de ago. de 2025

Para el uso del antivirus ClamAV y su motor de busqueda clamscan en modo gráfico suelo recomendar usar la aplicación ClamTk, la cual funciona correctamente.

clamtk en funcionamiento


Sin embargo, me apetecía disponer de una aplicación más sencilla para el uso del antivirus.

Así que con un poco de Python y las librerías de Tkinter me he creado mi aplicación gráfica para poder usar el antivirus ClamAV y disponer de un "modo oscuro" en su interfaz.

Antivirus de SoloConLinux

¿Cómo se instala?

Si tienes configurado el repositorio de SoloConLinux, tienes la aplicación disponible para Debian 12 y Debian 13, tan sólo tienes que ejecutar:

# Actualizar e instalar
sudo apt update
sudo apt -y install antivirus-clamav

# Crear Icono en escritorio
cp /usr/share/applications/antivirus.desktop ~/Escritorio/

Puedes configurar el repositorio SoloConLinux para tu Debian 13 simplemente con las instrucciones del artículo:

Trixie en Repositorio SoloConLinux
Debian 13 Trixie en el Repositorio de SoloConLinux

La aplicación está creada para que use de forma automática su propio entorno virtual de Python3 y no haya problemas de dependencias en los equipos Debian que lo vayan a usar.

Código fuente

Para los que tengais curiosidad por el código fuente (versión 9 y última del código) os lo dejo a continuación.

Nota: Perdonad, pero no me ha dado tiempo a dejarlo en mi repositorio en modo público.

Para que os funcione teneis que tener instalado ClamAV, Python3 y customTKinter

# Instalar CustomTKinter
pip install customtkinter
#!/usr/bin/env python3
# LuisGuLo (SoloConLinux)

import customtkinter as ctk
from tkinter import filedialog
import subprocess
import threading
import os

# Inicializar apariencia
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")

# Variable global para controlar el proceso
proceso_activo = None

# Función para seleccionar ruta (inicia en HOME)
def seleccionar_ruta():
    home_dir = os.path.expanduser("~")
    ruta = filedialog.askdirectory(initialdir=home_dir)
    if ruta:
        ruta_entry.delete(0, ctk.END)
        ruta_entry.insert(0, ruta)
        actualizar_comando()

# Función para actualizar el comando
def actualizar_comando():
    ruta = ruta_entry.get()
    comando = ["clamscan"]

    if recursivo_var.get():
        comando.append("-r")
    if comprimidos_var.get():
        comando.append("--scan-archive=yes")
    if pua_var.get():
        comando.append("--detect-pua=yes")

    if max_filesize_var.get() != "Sin límite":
        comando.append(f"--max-filesize={max_filesize_var.get()}")
    if max_scansize_var.get() != "Sin límite":
        comando.append(f"--max-scansize={max_scansize_var.get()}")

    if ruta:
        comando.append(ruta)

    comando_label.configure(text="Comando: " + " ".join(comando))

# Función para ejecutar escaneo en hilo
def ejecutar_escaneo():
    progreso.start()
    resultado_text.delete("1.0", ctk.END)
    threading.Thread(target=escaneo_en_hilo).start()

def escaneo_en_hilo():
    global proceso_activo
    ruta = ruta_entry.get()
    if not ruta:
        resultado_text.insert(ctk.END, " Por favor selecciona una ruta.\n")
        progreso.stop()
        return

    comando = comando_label.cget("text").replace("Comando: ", "").split()
    try:
        proceso_activo = subprocess.Popen(comando, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        stdout, stderr = proceso_activo.communicate()
        resultado_text.insert(ctk.END, stdout)
        if stderr:
            resultado_text.insert(ctk.END, "\n Errores:\n" + stderr)
    except Exception as e:
        resultado_text.insert(ctk.END, f" Error al ejecutar: {e}")
    finally:
        progreso.stop()
        proceso_activo = None

# Función para detener escaneo
def detener_escaneo():
    global proceso_activo
    if proceso_activo and proceso_activo.poll() is None:
        proceso_activo.terminate()
        resultado_text.insert(ctk.END, "\n Escaneo detenido por el usuario.\n")
        progreso.stop()
        proceso_activo = None

# Crear ventana
ventana = ctk.CTk()
ventana.title("🛡️ Escaneo con ClamAV")
ventana.geometry("900x750")

# Título visual
ctk.CTkLabel(
    ventana,
    text="🛡️ Escáner Antivirus con ClamAV",
    font=ctk.CTkFont(size=24, weight="bold"),
    text_color="white"
).grid(row=0, column=0, columnspan=3, padx=10, pady=(20, 10))

# Ruta
ctk.CTkLabel(ventana, text="Ruta a escanear:").grid(row=1, column=0, padx=10, pady=10, sticky="w")
ruta_entry = ctk.CTkEntry(ventana, width=500)
ruta_entry.grid(row=1, column=1, padx=10, pady=10)
ctk.CTkButton(ventana, text="Seleccionar", command=seleccionar_ruta).grid(row=1, column=2, padx=10, pady=10)

# Opciones
recursivo_var = ctk.BooleanVar()
comprimidos_var = ctk.BooleanVar()
pua_var = ctk.BooleanVar()

ctk.CTkCheckBox(ventana, text="Búsqueda recursiva", variable=recursivo_var, command=actualizar_comando).grid(row=2, column=0, columnspan=3, padx=10, pady=5, sticky="w")
ctk.CTkCheckBox(ventana, text="Escanear comprimidos", variable=comprimidos_var, command=actualizar_comando).grid(row=3, column=0, columnspan=3, padx=10, pady=5, sticky="w")
ctk.CTkCheckBox(ventana, text="Localizar PUA", variable=pua_var, command=actualizar_comando).grid(row=4, column=0, columnspan=3, padx=10, pady=5, sticky="w")

# Tamaños
ctk.CTkLabel(ventana, text="Max filesize:").grid(row=5, column=0, padx=10, pady=10, sticky="w")
max_filesize_var = ctk.StringVar(value="Sin límite")
ctk.CTkOptionMenu(ventana, variable=max_filesize_var, values=["Sin límite", "50M", "100M", "500M"], command=lambda _: actualizar_comando()).grid(row=5, column=1, padx=10, pady=10)

ctk.CTkLabel(ventana, text="Max scansize:").grid(row=6, column=0, padx=10, pady=10, sticky="w")
max_scansize_var = ctk.StringVar(value="Sin límite")
ctk.CTkOptionMenu(ventana, variable=max_scansize_var, values=["Sin límite", "200M", "500M", "2G"], command=lambda _: actualizar_comando()).grid(row=6, column=1, padx=10, pady=10)

# Comando generado
comando_label = ctk.CTkLabel(ventana, text="Comando: clamscan", text_color="lightblue", wraplength=800, anchor="w", justify="left")
comando_label.grid(row=7, column=0, columnspan=3, padx=10, pady=10)

# Botones
ctk.CTkButton(ventana, text="Ejecutar escaneo", command=ejecutar_escaneo).grid(row=8, column=0, padx=10, pady=10)
ctk.CTkButton(ventana, text="Detener escaneo", command=detener_escaneo).grid(row=8, column=1, padx=10, pady=10)

# Barra de progreso
progreso = ctk.CTkProgressBar(ventana, mode="indeterminate", width=600)
progreso.grid(row=9, column=0, columnspan=3, padx=10, pady=10)

# Informe con barra de desplazamiento
ctk.CTkLabel(ventana, text="📋 Informe escaneo:").grid(row=10, column=0, columnspan=3, padx=10, pady=10, sticky="w")

informe_frame = ctk.CTkFrame(ventana)
informe_frame.grid(row=11, column=0, columnspan=3, padx=10, pady=10)

resultado_text = ctk.CTkTextbox(informe_frame, width=820, height=300)
resultado_text.grid(row=0, column=0, sticky="nsew")

scrollbar = ctk.CTkScrollbar(informe_frame, orientation="vertical", command=resultado_text.yview)
scrollbar.grid(row=0, column=1, sticky="ns")

resultado_text.configure(yscrollcommand=scrollbar.set)

# Ejecutar ventana
ventana.mainloop()

Espero que os sea útil

Etiquetas

Luis GuLo

🐧 SysAdmin GNU/Linux - 🐳 Docker - 🖥️ Bash Scripting - 🐪 Perl - 🐬 MySQL - 👥 Formador de TI - 👥 Formador de SysAdmin's - 💢 Ansible - ☁️ Cloud Computing - ❤️ Debian GNU/Linux