ptrindade/linuxoptimizerpro
0
1#!/usr/bin/env python32"""3Linux System Optimizer - GUI Application v2.14==============================================5Uma aplicação gráfica para diagnosticar, limpar, atualizar e otimizar sistemas Linux.6Baseada nos comandos do documento 'comandos linux.rd'7 8Versão 2.1: Novo esquema de cores moderno9Autor: Sistema de Otimização Linux10Data: Outubro 202511"""12 13import tkinter as tk14from tkinter import ttk, scrolledtext, messagebox, simpledialog15import subprocess16import threading17import os18import sys19from datetime import datetime20 21 22class LinuxOptimizer:23 def __init__(self, root):24 self.root = root25 self.root.title("Linux System Optimizer v2.1")26 self.root.geometry("1400x900")27 self.root.minsize(1000, 700)28 29 # Verificar se está a correr como root30 self.is_root = os.geteuid() == 031 32 # Configurar estilo33 self.setup_styles()34 35 # Configurar interface principal36 self.create_main_interface()37 38 # Configurar scroll global do rato39 self.setup_global_mousewheel()40 41 # Mostrar aviso se não for root (desativado para teste)42 # if not self.is_root:43 # self.show_sudo_warning()44 45 def setup_global_mousewheel(self):46 """Configurar scroll do rato globalmente - VERSÃO MELHORADA"""47 def _on_mousewheel(event):48 # Encontrar o widget atual sob o cursor49 widget = event.widget50 attempts = 051 while widget and attempts < 10: # Limitar tentativas52 # Se encontrar um Canvas, fazer scroll nele53 if isinstance(widget, tk.Canvas):54 try:55 # Verificar se o canvas tem scroll vertical56 if widget.cget('yscrollcommand'):57 widget.yview_scroll(int(-1*(event.delta/120)), "units")58 return "break"59 except:60 pass61 # Se encontrar um Text widget com scrollbar62 elif hasattr(widget, 'yview_scroll'):63 try:64 widget.yview_scroll(int(-1*(event.delta/120)), "units")65 return "break"66 except:67 pass68 widget = widget.master69 attempts += 170 71 # Bind global do scroll mais agressivo72 self.root.bind_all("<MouseWheel>", _on_mousewheel)73 74 def font_exists(self, font_name):75 """Verificar se uma fonte específica existe no sistema"""76 try:77 import tkinter.font as tkFont78 available_fonts = tkFont.families()79 return font_name in available_fonts80 except:81 return False82 83 def setup_styles(self):84 """Configurar estilos do tkinter"""85 style = ttk.Style()86 style.theme_use('clam')87 88 # Esquema de cores moderno - Dark Theme com acentos vibrantes89 self.colors = {90 'primary': '#0f172a', # Slate 900 - Azul muito escuro91 'secondary': '#1e293b', # Slate 800 - Azul escuro médio92 'accent': '#7c3aed', # Violet 600 - Roxo vibrante93 'accent_light': '#a855f7', # Purple 500 - Roxo claro94 'success': '#059669', # Emerald 600 - Verde esmeralda95 'warning': '#ea580c', # Orange 600 - Laranja vibrante96 'danger': '#dc2626', # Red 600 - Vermelho forte97 'info': '#0284c7', # Sky 600 - Azul informativo98 'dark': '#020617', # Slate 950 - Preto suave99 'medium': '#334155', # Slate 700 - Cinza azulado100 'light': '#e2e8f0', # Slate 200 - Cinza muito claro101 'surface': '#f1f5f9', # Slate 100 - Superfície clara102 'text_light': '#f8fafc', # Slate 50 - Texto claro103 'text_dark': '#0f172a', # Slate 900 - Texto escuro104 'border': '#64748b', # Slate 500 - Bordas105 'hover_primary': '#1e40af', # Blue 700 - Hover azul106 'hover_accent': '#8b5cf6' # Violet 500 - Hover roxo107 }108 109 # Configurar estilos personalizados com novo esquema110 style.configure('Title.TLabel', 111 font=('Segoe UI', 20, 'bold'), 112 foreground=self.colors['text_light'],113 background=self.colors['primary'])114 115 style.configure('Subtitle.TLabel', 116 font=('Segoe UI', 12), 117 foreground='#cbd5e1', # Slate 300 - texto suave118 background=self.colors['primary'])119 120 # Estilo para o notebook (abas) com gradiente121 style.configure('TNotebook', 122 background=self.colors['secondary'],123 borderwidth=0,124 tabmargins=[2, 5, 2, 0])125 126 style.configure('TNotebook.Tab', 127 font=('Segoe UI', 11, 'bold'),128 padding=(25, 15),129 background=self.colors['medium'],130 foreground='#94a3b8') # Slate 400131 132 style.map('TNotebook.Tab',133 background=[('selected', self.colors['accent']),134 ('active', self.colors['accent_light'])],135 foreground=[('selected', self.colors['text_light']),136 ('active', self.colors['text_light'])])137 138 # Configurar cor de fundo da janela principal139 self.root.configure(bg=self.colors['primary'])140 141 # Função setup_global_mousewheel removida (estava duplicada)142 143 def create_main_interface(self):144 """Criar a interface principal com abas"""145 146 # Frame principal147 main_frame = tk.Frame(self.root, bg=self.colors['primary'])148 main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)149 150 # Header com gradiente visual151 header_frame = tk.Frame(main_frame, bg=self.colors['primary'], height=80)152 header_frame.pack(fill=tk.X, pady=(0, 20))153 header_frame.pack_propagate(False)154 155 # Título da aplicação156 title_label = ttk.Label(header_frame, text="🐧 Linux System Optimizer v2.1", 157 style='Title.TLabel')158 title_label.pack(pady=(15, 5))159 160 # Status do utilizador161 status_text = "🔴 A correr como utilizador normal (alguns comandos requerem sudo)" if not self.is_root else "🟢 A correr como root - acesso completo"162 status_label = ttk.Label(header_frame, text=status_text, style='Subtitle.TLabel')163 status_label.pack(pady=(0, 10))164 165 # Container principal com PanedWindow para redimensionamento166 content_frame = tk.Frame(main_frame, bg=self.colors['primary'])167 content_frame.pack(fill=tk.BOTH, expand=True)168 169 # PanedWindow para permitir redimensionar entre abas e output170 self.paned_window = tk.PanedWindow(content_frame, 171 orient=tk.VERTICAL,172 bg=self.colors['primary'],173 sashwidth=8,174 sashrelief='raised')175 self.paned_window.pack(fill=tk.BOTH, expand=True)176 177 # Frame superior para as abas178 tabs_frame = tk.Frame(self.paned_window, bg=self.colors['primary'])179 self.paned_window.add(tabs_frame, minsize=300)180 181 # Criar notebook para abas182 self.notebook = ttk.Notebook(tabs_frame)183 self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)184 185 # Criar abas186 self.create_diagnostic_tab()187 self.create_cleanup_tab()188 self.create_update_tab()189 self.create_optimization_tab()190 191 # Frame inferior para o output (redimensionável)192 output_container = tk.Frame(self.paned_window, bg=self.colors['primary'])193 self.paned_window.add(output_container, minsize=200)194 195 # Área de output196 self.create_output_area(output_container)197 198 def create_scrollable_tab(self, notebook, tab_name, icon):199 """Criar uma aba com scroll funcional"""200 # Frame principal da aba com cor atualizada201 main_tab_frame = tk.Frame(notebook, bg=self.colors['surface'])202 notebook.add(main_tab_frame, text=f"{icon} {tab_name}")203 204 # Canvas e scrollbar com cores atualizadas205 canvas = tk.Canvas(main_tab_frame, bg=self.colors['surface'], 206 highlightthickness=0, borderwidth=0)207 scrollbar = ttk.Scrollbar(main_tab_frame, orient="vertical", command=canvas.yview)208 scrollable_frame = tk.Frame(canvas, bg=self.colors['surface'])209 210 # Configurar scroll211 scrollable_frame.bind(212 "<Configure>",213 lambda e: canvas.configure(scrollregion=canvas.bbox("all"))214 )215 216 canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")217 canvas.configure(yscrollcommand=scrollbar.set)218 219 # O scroll global já está configurado - não precisamos duplicar aqui220 221 # Pack elementos222 canvas.pack(side="left", fill="both", expand=True, padx=(10, 0), pady=10)223 scrollbar.pack(side="right", fill="y", pady=10, padx=(0, 10))224 225 return scrollable_frame226 227 def create_diagnostic_tab(self):228 """Criar aba de Diagnóstico"""229 scrollable_frame = self.create_scrollable_tab(self.notebook, "Diagnóstico", "🔍")230 231 # Seção: Informações do Sistema232 self.create_section(scrollable_frame, "💻 Informações do Sistema", [233 ("Ver informações da CPU", "lscpu", False),234 ("Ver informações de memória", "free -h", False),235 ("Ver informações de hardware", "sudo lshw -short", True),236 ("Ver temperatura (se disponível)", "sensors", False),237 ])238 239 # Seção: Uso de Disco240 self.create_section(scrollable_frame, "💾 Análise de Disco", [241 ("Ver espaço em disco", "df -h", False),242 ("Ver dispositivos de bloco", "lsblk", False),243 ("Analisar diretórios grandes (raiz)", "sudo du -h / 2>/dev/null | sort -rh | head -10", True),244 ("Analisar diretório home", "du -h ~/ | sort -rh | head -20", False),245 ("Encontrar ficheiros grandes (+100MB)", "find / -type f -size +100M 2>/dev/null | head -20", False),246 ])247 248 # Seção: Processos e Performance249 self.create_section(scrollable_frame, "⚡ Processos e Performance", [250 ("Processos que mais consomem CPU", "ps aux --sort=-%cpu | head -11", False),251 ("Processos que mais consomem Memória", "ps aux --sort=-%mem | head -11", False),252 ("Abrir htop (monitor interativo)", "htop", False),253 ("Ver tempo de boot", "systemd-analyze", False),254 ("Serviços mais lentos no boot", "systemd-analyze blame", False),255 ])256 257 # Seção: Rede e Sistema258 self.create_section(scrollable_frame, "🌐 Rede e Sistema", [259 ("Ver conexões de rede ativas", "ss -tuln", False),260 ("Ver informações de rede", "ip addr show", False),261 ("Ver serviços em execução", "systemctl list-units --type=service --state=running", False),262 ("Ver serviços falhados", "systemctl --failed", False),263 ])264 265 def create_cleanup_tab(self):266 """Criar aba de Limpeza"""267 scrollable_frame = self.create_scrollable_tab(self.notebook, "Limpeza", "🧹")268 269 # Seção: Cache APT270 self.create_section(scrollable_frame, "📦 Cache de Pacotes APT", [271 ("Ver tamanho do cache APT", "du -sh /var/cache/apt/archives/", False),272 ("Limpeza conservadora (autoclean)", "sudo apt autoclean", True),273 ("Limpeza completa (clean)", "sudo apt clean", True),274 ("Remover pacotes órfãos", "sudo apt autoremove --purge", True),275 ])276 277 # Seção: Logs do Sistema278 self.create_section(scrollable_frame, "📄 Logs do Sistema", [279 ("Ver tamanho dos logs", "sudo du -h /var/log/ | sort -rh | head -10", True),280 ("Limpar logs (manter 3 dias)", "sudo journalctl --vacuum-time=3d", True),281 ("Limitar logs (máx 100MB)", "sudo journalctl --vacuum-size=100M", True),282 ("Esvaziar syslog", "sudo truncate -s 0 /var/log/syslog", True),283 ])284 285 # Seção: Ficheiros Temporários286 self.create_section(scrollable_frame, "🗂️ Ficheiros Temporários", [287 ("Ver tamanho do /tmp", "du -sh /tmp/", False),288 ("Limpar cache do utilizador", "rm -rf ~/.cache/*", False),289 ("Limpar thumbnails", "rm -rf ~/.thumbnails/*", False),290 ("Limpar lixeira", "rm -rf ~/.local/share/Trash/*", False),291 ("Limpar /tmp (cuidado!)", "sudo rm -rf /tmp/*", True),292 ])293 294 # Seção: Snaps295 self.create_section(scrollable_frame, "📱 Gestão de Snaps", [296 ("Listar snaps instalados", "snap list", False),297 ("Ver política de retenção", "snap get system refresh.retain", False),298 ("Limpar snaps (manual)", "# Use: sudo snap remove <nome> --revision <num>", False),299 ])300 301 def create_update_tab(self):302 """Criar aba de Atualizações"""303 scrollable_frame = self.create_scrollable_tab(self.notebook, "Atualizações", "🔄")304 305 # Seção: Atualizações de Sistema306 self.create_section(scrollable_frame, "🔧 Sistema APT", [307 ("Atualizar lista de pacotes", "sudo apt update", True),308 ("Fazer upgrade de pacotes", "sudo apt upgrade", True),309 ("Fazer dist-upgrade", "sudo apt dist-upgrade", True),310 ("Ver pacotes que podem ser atualizados", "apt list --upgradable", False),311 ])312 313 # Seção: Snaps314 self.create_section(scrollable_frame, "📱 Snaps", [315 ("Atualizar todos os snaps", "sudo snap refresh", True),316 ("Ver atualizações disponíveis", "snap refresh --list", False),317 ])318 319 # Seção: Verificação de Integridade320 self.create_section(scrollable_frame, "🔍 Verificação de Integridade", [321 ("Verificar integridade dos pacotes", "sudo dpkg --configure -a", True),322 ("Verificar dependências", "sudo apt-get check", True),323 ("Corrigir dependências quebradas", "sudo apt --fix-broken install", True),324 ])325 326 # Seção: Kernels327 self.create_section(scrollable_frame, "⚙️ Gestão de Kernels", [328 ("Ver kernels instalados", "dpkg -l | grep linux-image", False),329 ("Remover kernels antigos", "sudo apt autoremove --purge", True),330 ])331 332 def create_optimization_tab(self):333 """Criar aba de Otimização"""334 scrollable_frame = self.create_scrollable_tab(self.notebook, "Otimização", "⚡")335 336 # Seção: Análise de Performance337 self.create_section(scrollable_frame, "📊 Análise de Performance", [338 ("Análise crítica do boot", "systemd-analyze critical-chain", False),339 ("Monitorizar I/O de disco", "sudo iotop", True),340 ("Estatísticas de I/O", "iostat -x 1 5", False),341 ])342 343 # Seção: Gestão de Serviços344 self.create_section(scrollable_frame, "🔧 Gestão de Serviços", [345 ("Ver serviços habilitados", "systemctl list-unit-files --type=service --state=enabled", False),346 ("Desativar serviço", "# Use: sudo systemctl disable --now <serviço>", False),347 ("Mascarar serviço", "# Use: sudo systemctl mask <serviço>", False),348 ])349 350 # Seção: Otimizações do Sistema351 self.create_section(scrollable_frame, "⚙️ Sistema", [352 ("Atualizar base de dados locate", "sudo updatedb", True),353 ("Atualizar cache de man pages", "sudo mandb", True),354 ("Ver uso de swap", "swapon --show", False),355 ("Verificar saúde do SSD", "sudo smartctl -a /dev/sda", True),356 ])357 358 # Seção: Aplicações de Arranque359 self.create_section(scrollable_frame, "🚀 Arranque", [360 ("Ver aplicações de arranque", "ls ~/.config/autostart/", False),361 ("Editar aplicações de arranque", "# Use um editor para ~/.config/autostart/", False),362 ])363 364 def create_section(self, parent, title, commands):365 """Criar uma seção com título e comandos - CÓPIA EXATA DO TESTE FUNCIONAL"""366 # Frame da seção - SIMPLIFICAR367 section_frame = tk.LabelFrame(parent, text=f" {title} ", padx=20, pady=15,368 bg=self.colors['light'], fg=self.colors['text_dark'],369 font=('Segoe UI', 12, 'bold'))370 section_frame.pack(fill=tk.X, padx=20, pady=20)371 372 # Frame único para todos os botões373 buttons_frame = tk.Frame(section_frame, bg=self.colors['light'])374 buttons_frame.pack(fill=tk.X, pady=5)375 376 for desc, cmd, needs_sudo in commands:377 # Cores378 if needs_sudo:379 color = self.colors['danger']380 else:381 color = self.colors['accent']382 383 # Criar botão384 btn = tk.Button(buttons_frame, 385 text=desc,386 bg=color, fg='white',387 font=('Arial', 11, 'bold'),388 padx=10, pady=5,389 command=lambda c=cmd, s=needs_sudo: self.run_command(c, s))390 391 # Pack horizontal com espaçamento392 btn.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.X, expand=True)393 394 def create_output_area(self, parent):395 """Criar área de output redimensionável"""396 # Frame do output com estilo moderno397 output_frame = tk.LabelFrame(parent, 398 text=" 📋 Resultado dos Comandos (Redimensionável) ",399 bg=self.colors['light'],400 fg=self.colors['text_dark'],401 font=('Segoe UI', 11, 'bold'),402 padx=10, pady=10,403 borderwidth=2,404 relief='solid')405 output_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)406 407 # Frame para o texto e scrollbar personalizada408 text_frame = tk.Frame(output_frame, bg=self.colors['light'])409 text_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))410 411 # Terminal com novo esquema de cores412 self.output_text = scrolledtext.ScrolledText(text_frame, 413 bg=self.colors['dark'], 414 fg='#06ffa5', # Verde neon mais moderno415 font=('JetBrains Mono', 10) if self.font_exists('JetBrains Mono') else ('Consolas', 10),416 insertbackground='#06ffa5',417 selectbackground=self.colors['accent'],418 selectforeground=self.colors['text_light'],419 borderwidth=0,420 highlightthickness=2,421 highlightcolor=self.colors['accent'],422 highlightbackground=self.colors['border'],423 padx=15, pady=12,424 wrap=tk.WORD)425 self.output_text.pack(fill=tk.BOTH, expand=True)426 427 # Botões de controlo com design moderno428 control_frame = tk.Frame(output_frame, bg=self.colors['light'])429 control_frame.pack(fill=tk.X)430 431 # Botões com novo design432 clear_btn = tk.Button(control_frame, 433 text="🗑️ Limpar Output",434 font=('Segoe UI', 10, 'bold'),435 bg=self.colors['warning'],436 fg=self.colors['text_light'],437 activebackground='#c2410c', # Orange 700438 activeforeground=self.colors['text_light'],439 borderwidth=0,440 relief='flat',441 padx=25, pady=10,442 cursor='hand2',443 command=self.clear_output)444 clear_btn.pack(side=tk.LEFT, padx=(0, 15))445 446 # Botão guardar com cor info447 save_btn = tk.Button(control_frame, 448 text="💾 Guardar Log",449 font=('Segoe UI', 10, 'bold'),450 bg=self.colors['info'],451 fg=self.colors['text_light'],452 activebackground='#0369a1', # Sky 700453 activeforeground=self.colors['text_light'],454 borderwidth=0,455 relief='flat',456 padx=25, pady=10,457 cursor='hand2',458 command=self.save_log)459 save_btn.pack(side=tk.LEFT)460 461 # Efeitos hover melhorados462 def create_hover_effect(button, hover_color, normal_color):463 def on_enter(event):464 button.configure(bg=hover_color)465 def on_leave(event):466 button.configure(bg=normal_color)467 button.bind("<Enter>", on_enter)468 button.bind("<Leave>", on_leave)469 470 create_hover_effect(clear_btn, '#c2410c', self.colors['warning']) # Orange 700471 create_hover_effect(save_btn, '#0369a1', self.colors['info']) # Sky 700472 473 def run_command(self, command, needs_sudo):474 """Executar comando em thread separada"""475 if needs_sudo and not self.is_root:476 # Pedir confirmação para comandos sudo477 if not messagebox.askyesno("Confirmação", 478 f"Este comando requer privilégios de administrador:\n\n{command}\n\nContinuar?"):479 return480 481 # Executar em thread separada482 thread = threading.Thread(target=self._execute_command, args=(command,))483 thread.daemon = True484 thread.start()485 486 def _execute_command(self, command):487 """Executar comando e mostrar resultado"""488 timestamp = datetime.now().strftime("%H:%M:%S")489 self.append_output(f"\n[{timestamp}] Executando: {command}\n" + "="*60 + "\n")490 491 try:492 # Executar comando493 if command.startswith('#'):494 # Comentário, não executar495 self.append_output("ℹ️ Este é um comando informativo/exemplo.\n")496 return497 498 if command.startswith('sudo') and not self.is_root:499 # Tentar executar com pkexec se disponível500 alt_command = command.replace('sudo ', 'pkexec ')501 try:502 result = subprocess.run(alt_command, shell=True, capture_output=True, 503 text=True, timeout=30)504 except:505 # Fallback para sudo normal506 result = subprocess.run(command, shell=True, capture_output=True, 507 text=True, timeout=30)508 else:509 result = subprocess.run(command, shell=True, capture_output=True, 510 text=True, timeout=30)511 512 # Mostrar resultado513 if result.stdout:514 self.append_output(result.stdout)515 if result.stderr:516 self.append_output(f"⚠️ Avisos/Erros:\n{result.stderr}")517 518 if result.returncode == 0:519 self.append_output("\n✅ Comando executado com sucesso!\n")520 else:521 self.append_output(f"\n❌ Comando falhou com código {result.returncode}\n")522 523 except subprocess.TimeoutExpired:524 self.append_output("\n⏱️ Comando excedeu tempo limite (30s)\n")525 except Exception as e:526 self.append_output(f"\n❌ Erro ao executar comando: {str(e)}\n")527 528 self.append_output("\n" + "="*60 + "\n")529 530 def append_output(self, text):531 """Adicionar texto ao output de forma thread-safe"""532 def update():533 self.output_text.insert(tk.END, text)534 self.output_text.see(tk.END)535 536 self.root.after(0, update)537 538 def clear_output(self):539 """Limpar área de output"""540 self.output_text.delete(1.0, tk.END)541 542 def save_log(self):543 """Guardar log em ficheiro"""544 content = self.output_text.get(1.0, tk.END)545 if not content.strip():546 messagebox.showwarning("Aviso", "Não há conteúdo para guardar.")547 return548 549 filename = f"linux_optimizer_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"550 try:551 with open(filename, 'w') as f:552 f.write(content)553 messagebox.showinfo("Sucesso", f"Log guardado como {filename}")554 except Exception as e:555 messagebox.showerror("Erro", f"Erro ao guardar log: {str(e)}")556 557 def show_sudo_warning(self):558 """Mostrar aviso sobre permissões"""559 messagebox.showinfo("Informação", 560 "A aplicação está a correr como utilizador normal.\n\n"561 "Alguns comandos (marcados a vermelho) requerem privilégios de administrador.\n\n"562 "Para melhor experiência, execute com:\nsudo python3 linux_optimizer.py")563 564 565def main():566 """Função principal"""567 root = tk.Tk()568 app = LinuxOptimizer(root)569 root.mainloop()570 571 572if __name__ == "__main__":573 main()