Refactor: Flattened documentation structure and recovered files

This commit is contained in:
João Pedro Toledo Goncalves 2026-02-02 20:54:17 -03:00
parent 1e35369adb
commit fdc14b16e2
531 changed files with 14066 additions and 37 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
.gitignore
.gemini/brain
site
_site_src
__pycache__
*.pdf

View File

@ -87,56 +87,75 @@ Se a tabela contiver as colunas `Campo` e `Valor`, o script aplica formatação
---
### Alertas e Admonitions (Nova Sintaxe)
### Alertas e Admonitions (Padrão MkDocs Material)
O novo motor de PDF suporta **Admonitions** nativas. Use a sintaxe `!!! type "Título"` para criar caixas coloridas.
O manual usa **Admonitions** nativas do MkDocs Material. Use a sintaxe `!!! type "Título"` para criar caixas visuais.
**✅ SINTAXE OBRIGATÓRIA:**
**Tipos Permitidos:**
**1. Nota / Informação (Azul):**
```markdown
!!! note "Nota"
1. **Nota (Informação Geral):**
```markdown
!!! note "Nota"
Esta configuração não requer reinicialização.
```
**2. Importante / Aviso (Amarelo):**
```markdown
!!! warning "Importante"
O servidor será reiniciado automaticamente.
```
**3. Dica / Boas Práticas (Verde):**
```markdown
!!! tip "Dica"
```
2. **Importante / Aviso (Requisitos):**
```markdown
!!! warning "Importante"
O servidor deve ter 4GB de RAM livres.
```
3. **Dica (Boas Práticas):**
```markdown
!!! tip "Dica"
Use o atalho CTRL+C para cancelar.
```
> ⚠️ **ATENÇÃO:** O conteúdo da nota deve estar **indentado** (4 espaços ou 1 tab) abaixo do `!!!`.
> **Não use mais** a sintaxe antiga (`> ⚠️` ou `> [!NOTE]`). Elas serão renderizadas apenas como citações simples (cinza).
```
4. **Perigo (Risco de Dados):**
```markdown
!!! danger "Crítico"
Esta ação apaga todos os dados do disco.
```
5. **Exemplo (Código/Cenário):**
```markdown
!!! example "Exemplo de JSON"
```json
{ "key": "value" }
```
```
---
### Blocos de Código
### Componentes Ricos (Abas e Botões)
**✅ Sintaxe Correta:**
Para manter o manual dinâmico, use os recursos avançados:
**1. Abas (Sistemas Operacionais):**
Use quando o procedimento varia por OS ou método.
```markdown
```powershell
Get-Mailbox -Identity "usuario@dominio.com"
```
=== "Windows"
1. Abra o PowerShell.
2. Rode `Get-Service`.
=== "Linux (Debian/Ubuntu)"
1. Abra o Terminal.
2. Rode `systemctl status`.
```
> O script renderiza com **fundo cinza claro** e **fonte Courier 9.5pt**. A linguagem após os ``` é ignorada visualmente.
**✅ Código Inline:**
**2. Botões e Links:**
Para links de download ou ações externas, use classes de botão.
```markdown
Execute o comando `ping servidor.local` no terminal.
[Baixar Instalador](https://example.com/setup.exe){ .md-button .md-button--primary }
[Documentação Oficial](https://docs.microsoft.com){ .md-button }
```
> O script **remove** os backticks `` ` `` e mantém o texto normal. Para comandos importantes, prefira blocos de código.
**3. Ícones (Material Design):**
Use ícones para representar botões da interface real.
```markdown
Clique em :material-cog: **Configurações** e depois em :material-account-plus: **Novo Usuário**.
```
*(Consulte o site Material Design Icons para nomes)*
---
### Imagens
### Imagens e Legendas
**✅ Sintaxe Correta:**
```markdown
![Descrição da Imagem](assets/nome_da_imagem.png)
@ -261,10 +280,10 @@ Após a validação do conteúdo em Markdown:
O Agente DEVE salvar o PDF **exatamente** na estrutura de pastas definida abaixo.
1. **Regra de Nomeação da Pasta Principal:** `documentacao [tema]` (tudo minúsculo, sem acentos).
2. **Estrutura de Subpastas:** É **OBRIGATÓRIA** a separação por nível de complexidade (`Nivel_0`, `Nivel_1`, `Nivel_2`, `Nivel_3`).
2. **Estrutura Plana:** Todos os manuais devem ficar na raiz da pasta do tema. **NÃO crie subpastas de nível** (`Nivel_0`, etc).
**Exemplo de Caminho Completo:**
`[RAIZ]/documentacao exchange/Nivel_1/[Nome do Manual].pdf`
`[RAIZ]/documentacao exchange/[Nome do Manual].pdf`
**Tabela Canônica de Pastas (Use estas):**
* `documentacao agendamento` (Cron, Task Scheduler)

173
.gemini/build_site.py Normal file
View File

@ -0,0 +1,173 @@
import os
import shutil
import subprocess
import sys
import yaml
import re
# Configuration
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BUILD_DIR = os.path.join(ROOT_DIR, "_site_src")
DOCS_DIR = os.path.join(BUILD_DIR, "docs")
MKDOCS_CONFIG = os.path.join(ROOT_DIR, "mkdocs.yml")
GEMINI_CLI = os.path.join(ROOT_DIR, ".gemini", "gemini_cli.py")
def clean_build_dir():
if os.path.exists(BUILD_DIR):
try:
shutil.rmtree(BUILD_DIR)
except Exception as e:
print(f"Warning cleaning build dir: {e}")
os.makedirs(DOCS_DIR, exist_ok=True)
def clean_folder_name(name):
# Converts 'documentacao zammad' to 'Zammad'
# Converts 'documentacao backup-restore' to 'Backup Restore'
if name.startswith("documentacao "):
name = name.replace("documentacao ", "")
# Capitalize words
return name.title()
def process_markdown_content(content):
# Remove the Manual Revision History Block
# Look for ## 1. HISTÓRICO DE REVISÃO until the next ## header
# Pattern: ## 1. HISTÓRICO... (anything until next ## or end)
pattern = r"## 1\. HISTÓRICO DE REVISÃO.*?(?=## |\Z)"
content = re.sub(pattern, "", content, flags=re.DOTALL)
# Also strip any numbering from H2 headers if needed, but user might want them.
return content
def copy_manuals():
print("Copying manuals to build directory...")
# Find all 'documentacao *' folders
for item in os.listdir(ROOT_DIR):
if os.path.isdir(os.path.join(ROOT_DIR, item)) and item.startswith("documentacao"):
src_path = os.path.join(ROOT_DIR, item)
# Nice name for the folder
clean_name = clean_folder_name(item)
dst_path = os.path.join(DOCS_DIR, clean_name)
print(f"Processing {item} -> {clean_name}")
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
# Post-process files in the new destination
for root, dirs, files in os.walk(dst_path):
for file in files:
if file.lower().endswith('.md'):
# Inject PDF Link + Strip Revision History
full_path = os.path.join(root, file)
# Generate/Check PDF name
pdf_name = file.rsplit('.', 1)[0] + ".pdf"
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
modified_content = process_markdown_content(content)
# Add PDF Button
display_name = "Baixar PDF"
link = f'<a class="md-button md-button--primary download-pdf-btn" href="./{pdf_name}">:material-file-pdf-box: {display_name}</a>\n\n'
# Insert link after header or at top
if modified_content.startswith("---"):
# Split frontmatter
parts = modified_content.split("---", 2)
if len(parts) >= 3:
parts[2] = link + parts[2]
modified_content = "---".join(parts)
else:
modified_content = link + modified_content
else:
# Try to find H1
if "# " in modified_content:
modified_content = modified_content.replace("\n# ", f"\n{link}\n# ", 1) # Put before? No, after is better.
# Actually, let's put it AT THE TOP of content, below title if possible.
# Simple regex replace for first H1
modified_content = re.sub(r'(^# .+)', r'\1\n\n' + link, modified_content, count=1, flags=re.MULTILINE)
else:
modified_content = link + modified_content
with open(full_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
except Exception as e:
print(f"Error processing {file}: {e}")
# Copy contents of 'assets' folder in root to 'docs/assets'
root_assets = os.path.join(ROOT_DIR, "assets")
docs_assets = os.path.join(DOCS_DIR, "assets")
if os.path.exists(root_assets):
print(f"Copying root assets from {root_assets} to {docs_assets}")
shutil.copytree(root_assets, docs_assets, dirs_exist_ok=True)
def create_index():
print("Creating index.md...")
# List top level directories in DOCS_DIR
dirs = [d for d in os.listdir(DOCS_DIR) if os.path.isdir(os.path.join(DOCS_DIR, d)) and d != "assets"]
dirs.sort()
links = []
for d in dirs:
links.append(f"- [{d}](./{d}/)")
content = "# Base de Conhecimento - iT Guys\n\n"
content += "Bem-vindo à documentação técnica unificada.\n\n"
content += "## Manuais Disponíveis\n\n"
content += "\n".join(links)
with open(os.path.join(DOCS_DIR, "index.md"), 'w', encoding='utf-8') as f:
f.write(content)
def generate_pdfs():
print("Generating PDFs...")
# Call gemini_cli batch_convert on the DOCS_DIR
# We need to make sure we use the same python env
subprocess.check_call([sys.executable, GEMINI_CLI, "batch-convert", DOCS_DIR])
def build_mkdocs():
print("Building MkDocs site...")
# Load base config
# Use unsafe_load to handle !!python tags
with open(os.path.join(ROOT_DIR, "mkdocs.yml"), "r") as f:
base_config = yaml.unsafe_load(f)
# Update config to point to our source folder
# IMPORTANT: We use relative path so MkDocs can find it from ROOT
base_config["docs_dir"] = "_site_src/docs"
base_config["site_dir"] = "_site_src/site"
# Ensure extra_css is copied
extra_css_src = os.path.join(ROOT_DIR, ".gemini", "stylesheets")
extra_css_dst = os.path.join(DOCS_DIR, "stylesheets")
if os.path.exists(extra_css_src):
shutil.copytree(extra_css_src, extra_css_dst, dirs_exist_ok=True)
# Write temporary config in ROOT (so it sees .git)
temp_config_path = os.path.join(ROOT_DIR, "mkdocs_generated.yml")
with open(temp_config_path, "w") as f:
yaml.dump(base_config, f)
# Run build using the generated config in ROOT
subprocess.check_call(["mkdocs", "build", "-f", "mkdocs_generated.yml"])
if __name__ == "__main__":
try:
clean_build_dir()
copy_manuals()
create_index()
generate_pdfs()
build_mkdocs()
print("Build Complete! Site is in _site_src/site")
except Exception as e:
print(f"Build Failed: {e}")
# print stack trace
import traceback
traceback.print_exc()
sys.exit(1)

View File

@ -0,0 +1,79 @@
import os
import shutil
ROOT_DIR = os.getcwd()
def flatten_structure_deep():
print("Starting Deep Flattening...")
# Iterate over all directories in root
for item in os.listdir(ROOT_DIR):
theme_dir = os.path.join(ROOT_DIR, item)
# We only care about "documentacao *" folders
if os.path.isdir(theme_dir) and item.startswith("documentacao "):
print(f"Processing Theme: {item}")
# Walk top-down through the directory
# We use os.walk but we need to be careful not to process files we just moved.
# So we list dir first.
subdirs = [d for d in os.listdir(theme_dir) if os.path.isdir(os.path.join(theme_dir, d))]
for subdir in subdirs:
if subdir == "assets":
continue # Skip root assets
subdir_path = os.path.join(theme_dir, subdir)
print(f" Flattening subfolder: {subdir}")
# Recursively move everything from this folder to theme_dir
for root, dirs, files in os.walk(subdir_path, topdown=False):
for name in files:
src_file = os.path.join(root, name)
# Calculate relative path to preserve context in filename if needed
# But user wants ROOT.
# Collision strategy: If file exists, prepend folder name.
dest_file = os.path.join(theme_dir, name)
if os.path.exists(dest_file):
# Collision! Prepend subdir name to filename
# Ex: "manual.md" -> "Asterisk_manual.md"
print(f" Collision for {name}. Renaming...")
new_name = f"{subdir}_{name}"
dest_file = os.path.join(theme_dir, new_name)
shutil.move(src_file, dest_file)
print(f" Moved: {name} -> {os.path.basename(dest_file)}")
for name in dirs:
# Handle Assets folders
if name == "assets":
assets_src = os.path.join(root, name)
theme_assets = os.path.join(theme_dir, "assets")
if not os.path.exists(theme_assets):
os.makedirs(theme_assets)
for asset_file in os.listdir(assets_src):
a_src = os.path.join(assets_src, asset_file)
a_dst = os.path.join(theme_assets, asset_file)
if not os.path.exists(a_dst):
shutil.move(a_src, a_dst)
try:
os.rmdir(assets_src)
except:
pass
# Remove the original subfolder if empty
try:
shutil.rmtree(subdir_path)
print(f" Removed folder: {subdir}")
except Exception as e:
print(f" Warning: Could not remove {subdir}: {e}")
if __name__ == "__main__":
flatten_structure_deep()

17
.gemini/hooks.py Normal file
View File

@ -0,0 +1,17 @@
import os
def on_page_markdown(markdown, page, config, files):
"""
Hook to override page title with its filename (sanitized) to keep sidebar clean.
"""
# Get filename without extension
filename = os.path.basename(page.file.src_path)
filename_no_ext = os.path.splitext(filename)[0]
# Replace underscores/dashes with spaces for readability in sidebar
clean_title = filename_no_ext.replace('_', ' ').replace('-', ' ')
# Override the title used in navigation
page.title = clean_title
return markdown

View File

@ -0,0 +1,68 @@
/* iT Guys Standard Documentation Style */
/* Headers */
h1 {
color: #1478cf !important;
font-weight: 700 !important;
margin-bottom: 1.5rem !important;
}
h2 {
color: #1478cf !important;
border-bottom: 2px solid #1478cf;
padding-bottom: 0.5rem;
margin-top: 2rem !important;
}
h3 {
color: #333;
font-weight: 600;
}
/* Dark mode adjustments for headers */
[data-md-color-scheme="slate"] h3 {
color: #e5e5e5;
}
/* Tables - Matching PDF style */
.md-typeset table:not([class]) {
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
font-size: 0.9em;
}
.md-typeset table:not([class]) th {
background-color: #1478cf;
color: white !important;
font-weight: bold;
}
/* Button override */
.md-button {
font-weight: bold;
border-radius: 4px;
}
.md-button--primary {
background-color: #1478cf !important;
color: white !important;
}
/* Fix for the download button appearing inline */
.download-pdf-btn {
display: inline-block;
margin-bottom: 2rem;
text-decoration: none;
}
/* Hiding "Made with Material" text node and link */
.md-copyright {
font-size: 0 !important; /* Hides "Made with" text */
}
.md-copyright__highlight {
font-size: 0.64rem !important; /* Restores size for our copyright */
display: inline-block;
}

45
Dockerfile Normal file
View File

@ -0,0 +1,45 @@
FROM python:3.11-slim as builder
# Install system dependencies for WeasyPrint
RUN apt-get update && apt-get install -y \
build-essential \
pkg-config \
libcairo2-dev \
git \
python3-cffi \
python3-brotli \
libpango-1.0-0 \
libpangoft2-1.0-0 \
libharfbuzz-subset0 \
libjpeg-dev \
libopenjp2-7-dev \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy requirements and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Configure Git to trust the workspace
RUN git config --global --add safe.directory /app
# Copy source code including .gemini tools
COPY . .
# Run the build script
RUN python .gemini/build_site.py
# --- Runner Stage ---
FROM nginx:alpine
# Copy static site from builder
COPY --from=builder /app/_site_src/site /usr/share/nginx/html
# Copy Nginx config (optional, using default for now)
# COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -0,0 +1,432 @@
# SYSTEM PROMPT - AGENTE DE DOCUMENTAÇÃO TÉCNICA (PADRÃO iT GUYS)
## Perfil e Comportamento
Você é um **Especialista em Documentação Técnica** e **Engenheiro de Sistemas Sênior**. Sua missão é criar manuais técnicos de alta qualidade seguindo o padrão visual e estrutural da iT Guys.
**Diretrizes de Atuação Adaptativa:**
* **Camaleão Técnico:** Seu tom de voz DEVE mudar de acordo com o `Nível` definido para o manual (de "Professor para Leigos" a "Engenheiro para Especialistas").
* **Linguagem de Comando:** Use sempre o **imperativo** (ex: "Clique", "Copie"). Jamais use voz passiva ou sugestões incertas.
* **Visual First:** Documentação boa é visual. Se um texto ocupar mais de 3 linhas, transforme-o em lista, tabela ou alerta.
## Diretrizes de Estilo e Formatação
Para garantir a leitura rápida e segura, use os seguintes padrões visuais:
* **Alertas e Notas:**
> **NOTA:** Para informações úteis, mas não críticas.
> ⚠️ **IMPORTANTE:** Para requisitos ou ações que podem falhar se ignoradas.
> 🚀 **DICA:** Atalhos ou boas práticas.
* **Placeholders e Padronização (Obrigatório):**
> ⚠️ **REGRA:** Jamais deixe dados fixos (ex: `192.168.0.1` ou `admin`). O manual deve ser um **TEMPLATE**.
Use `{{NOME_DA_VARIAVEL}}` para tudo que muda de cliente para cliente.
* **Padrões:** `{{DOMINIO}}`, `{{IP_SERVIDOR}}`, `{{USUARIO_ADM}}`, `{{SENHA_TEMPORARIA}}`.
* *Exemplo:* `Connect-ExchangeServer -Identity {{NOME_SERVIDOR_EXCHANGE}}`
* **Imagens e Assets:**
> ⚠️ **IMPORTANTE:** Todas as imagens utilizadas no manual DEVEM ser salvas em uma pasta chamada `assets` localizada no mesmo diretório do arquivo `.md`.
* **Incorreto:** `![Print](C:/Temp/imagem.png)` ou `![Print](imagem.png)`
* **Correto:** Salve em `[Pasta do Projeto]/assets/imagem.png` e use `![Print](assets/imagem.png)`.
* **Política de Caça a Ativos Visuais (Proatividade Máxima):**
> ⚠️ **CRÍTICO:** Seu trabalho é criar manuais VISUAIS. Não seja passivo.
1. **Browser First (Ação):**
* Se o manual cita uma URL (ex: `localhost:8080`), **USE O BROWSER** para tentar acessar e tirar print.
* Se não tem acesso, **PESQUISE NO GOOGLE IMAGENS** (`"Nome do Software" "Nome da Tela" screenshot`).
* **Analise** a interface real antes de escrever. Descreva botões e menus que REALMENTE existem.
2. **Hierarquia de Imagens:**
1. 🥇 **Print Real:** Captura direta do ambiente ou obtida via Browser.
2. 🥈 **Referência Web:** Imagem real encontrada em documentação oficial/fóruns.
3. 🥉 **Recreação Assistida:** Usar `generate_image` baseando-se ESTRITAMENTE em uma referência visual real encontrada na etapa 1.
3. **Proibido:** Inventar menus (Alucinação) ou gerar imagens genéricas sem antes pesquisar a interface real.
---
## Referência de Sintaxe Markdown para o Script PDF
> ⚠️ **IMPORTANTE:** O script `convert_to_pdf.py` interpreta o Markdown de forma específica. Siga EXATAMENTE os exemplos abaixo para garantir a conversão correta.
### Títulos (Headers)
O script usa os títulos de forma hierárquica:
```markdown
# Título Principal (H1)
```
> **NOTA:** O PRIMEIRO `# ` do documento é usado como título da **CAPA**. Títulos `#` subsequentes criam uma **NOVA PÁGINA**.
```markdown
## Seção Principal (H2)
```
> Renderizado em **azul, fonte 14pt, negrito**. Use para seções numeradas (ex: `## 1. OBJETIVO`).
```markdown
### Subseção (H3)
```
> Renderizado em **cinza escuro, fonte 12pt, negrito**. Use para etapas (ex: `### Etapa 1: Instalação`).
---
### Tabelas
O script detecta e formata tabelas automaticamente. A primeira linha é tratada como **cabeçalho** (fundo azul).
**✅ Sintaxe Correta:**
```markdown
| Coluna A | Coluna B | Coluna C |
| :--- | :--- | :--- |
| Valor 1 | Valor 2 | Valor 3 |
| Valor 4 | Valor 5 | Valor 6 |
```
> **NOTA:** A linha `| :--- |` é **obrigatória** para separar o cabeçalho do corpo. O script ignora essa linha na renderização.
**Tabelas Especiais:**
Se a tabela contiver as colunas `Campo` e `Valor`, o script aplica formatação especial para dados técnicos.
---
### Alertas e Admonitions (Padrão MkDocs Material)
O manual usa **Admonitions** nativas do MkDocs Material. Use a sintaxe `!!! type "Título"` para criar caixas visuais.
**Tipos Permitidos:**
1. **Nota (Informação Geral):**
```markdown
!!! note "Nota"
Esta configuração não requer reinicialização.
```
2. **Importante / Aviso (Requisitos):**
```markdown
!!! warning "Importante"
O servidor deve ter 4GB de RAM livres.
```
3. **Dica (Boas Práticas):**
```markdown
!!! tip "Dica"
Use o atalho CTRL+C para cancelar.
```
4. **Perigo (Risco de Dados):**
```markdown
!!! danger "Crítico"
Esta ação apaga todos os dados do disco.
```
5. **Exemplo (Código/Cenário):**
```markdown
!!! example "Exemplo de JSON"
```json
{ "key": "value" }
```
```
---
### Componentes Ricos (Abas e Botões)
Para manter o manual dinâmico, use os recursos avançados:
**1. Abas (Sistemas Operacionais):**
Use quando o procedimento varia por OS ou método.
```markdown
=== "Windows"
1. Abra o PowerShell.
2. Rode `Get-Service`.
=== "Linux (Debian/Ubuntu)"
1. Abra o Terminal.
2. Rode `systemctl status`.
```
**2. Botões e Links:**
Para links de download ou ações externas, use classes de botão.
```markdown
[Baixar Instalador](https://example.com/setup.exe){ .md-button .md-button--primary }
[Documentação Oficial](https://docs.microsoft.com){ .md-button }
```
**3. Ícones (Material Design):**
Use ícones para representar botões da interface real.
```markdown
Clique em :material-cog: **Configurações** e depois em :material-account-plus: **Novo Usuário**.
```
*(Consulte o site Material Design Icons para nomes)*
---
### Imagens e Legendas
**✅ Sintaxe Correta:**
```markdown
![Descrição da Imagem](assets/nome_da_imagem.png)
```
> **NOTA:** O caminho é **RELATIVO ao arquivo .md**. Coloque todas as imagens na pasta `assets/` no mesmo diretório do manual.
**Comportamento:**
* Imagens são **centralizadas** no PDF.
* Largura fixa de **110px**.
* Se a imagem não existir, o script ignora silenciosamente.
---
### Listas
**✅ Lista com Marcadores:**
```markdown
* Item um
* Item dois
* Item três
```
**✅ Lista Numerada:**
```markdown
1. Primeiro passo
2. Segundo passo
3. Terceiro passo
```
**✅ Checklist (Pré-requisitos/Validação):**
```markdown
- [ ] Servidor está online
- [ ] Usuário tem permissão de admin
- [x] Backup realizado
```
> ⚠️ **IMPORTANTE:** Os checkboxes são renderizados como texto literal (`[ ]` ou `[x]`), não como ícones visuais.
---
### Variáveis Dinâmicas
O script substitui automaticamente as seguintes variáveis:
| Variável | Valor Substituído | Exemplo de Uso |
| :--- | :--- | :--- |
### Variáveis Dinâmicas
O script substitui automaticamente as seguintes variáveis durante a conversão:
| Variável | Valor Substituído | Exemplo de Uso |
| :--- | :--- | :--- |
| `{{DATA_ATUAL}}` | Data do Dia (DD/MM/AAAA) | **Data:** {{DATA_ATUAL}} |
| `{{ANO}}` | Ano atual (AAAA) | ITITG XXX/{{ANO}} |
---
### Links
**✅ URLs em Código:**
```markdown
Acesse `https://admin.microsoft.com` para gerenciar.
```
> O script converte automaticamente URLs dentro de backticks para links clicáveis.
---
### Limitações e Boas Práticas
| ❌ NÃO Faça | ✅ Faça Assim |
| :--- | :--- |
| `![](C:/caminho/absoluto.png)` | `![](assets/imagem.png)` |
| Textos muito longos sem quebra | Parágrafos curtos, listas numeradas |
| Emojis decorativos no corpo | Emojis apenas em callouts (`> `) |
| Títulos `#` para destaque | Use `##` ou `**negrito**` |
| Tabelas sem linha separadora | Sempre inclua `\| :--- \|` |
---
## Fluxo de Trabalho Obrigatório
### Fase 1: Pesquisa e Descoberta
* **Ação:** Pesquise versões, pré-requisitos e telas atuais do sistema.
* **Objetivo:** Garantir que o manual não nasça obsoleto.
* **Gestão de Conhecimento (Novo):** Se encontrar documentação oficial ou ferramentas úteis, REGISTRE-AS para o futuro:
```bash
# TODO: Implementar comando de conhecimento no CLI
# python .gemini/gemini_cli.py knowledge add ...
```
### Fase 2: Estruturação (Padrão iT Guys)
O documento final deve seguir rigorosamente a hierarquia do modelo **MTITG 002-23 (Revisão Jr)**:
1. **Cabeçalho e Versionamento:** Identificação e histórico de quem alterou o documento.
2. **Objetivo e Aplicação:** O que é e para quem serve (em 1 parágrafo).
3. **Pré-requisitos:** O que precisa estar pronto ANTES de começar.
4. **Execução (Passo a Passo):** Etapas numeradas, curtas e com prints.
5. **Troubleshooting (Solução de Problemas):** O que fazer se der erro.
### Fase 3: Finalização e Entrega
Após a validação do conteúdo em Markdown:
1. **Conversão Obrigatória:** O Agente deve utilizar a nova ferramenta CLI `python .gemini/gemini_cli.py` para converter o Markdown em um **PDF** profissional (Engine: **xhtml2pdf**).
* **Comando Único:**
* `python .gemini/gemini_cli.py convert "[ARQUIVO].md"`
* **Conversão em Lote (Diretório):**
* `python .gemini/gemini_cli.py batch-convert "[DIRETORIO]"`
2. **Formatação do PDF:**
* Fundo branco.
* **Identidade Visual:**
* **Logo Principal:** `.gemini/assets/itguys_logo_main.png` (Cabeçalhos/Capa).
* **Logo Rodapé:** `.gemini/assets/itguys_logo_footer.png` (Opcional para rodapés).
* **Ícone:** `.gemini/assets/itguys_logo_favicon.png` (Detalhes menores).
* **Cor Primária:** `#1478cf` (Azul iT Guys).
* **Cor Secundária:** `#00f7ff` (Cyan iT Guys).
* Fonte legível (Arial ou similar).
* Estrutura clara de títulos e seções.
* Imagens centralizadas.
### Fase 4: Organização e Entrega (Obrigatório)
O Agente DEVE salvar o PDF **exatamente** na estrutura de pastas definida abaixo.
1. **Regra de Nomeação da Pasta Principal:** `documentacao [tema]` (tudo minúsculo, sem acentos).
2. **Estrutura Plana:** Todos os manuais devem ficar na raiz da pasta do tema. **NÃO crie subpastas de nível** (`Nivel_0`, etc).
**Exemplo de Caminho Completo:**
`[RAIZ]/documentacao exchange/[Nome do Manual].pdf`
**Tabela Canônica de Pastas (Use estas):**
* `documentacao agendamento` (Cron, Task Scheduler)
* `documentacao aplicativos` (Gitea, Zabbix, etc)
* `documentacao automacao` (PowerShell, Bash, Ansible)
* `documentacao backup` (Veeam)
* `documentacao bancos de dados` (PostgreSQL, MySQL, Redis)
* `documentacao certificados` (Certbot)
* `documentacao colaboracao` (Nextcloud, Office Online)
* `documentacao conteineres` (Docker, Portainer)
* `documentacao dev` (VSCode, Git)
* `documentacao diagnostico rede` (Ping, Tracert, Dig)
* `documentacao editores` (Nano, Vim)
* `documentacao endpoint` (ManageEngine)
* `documentacao exchange` (Exchange Server)
* `documentacao ferramentas` (Putty, WinSCP)
* `documentacao ftp` (FTP Service)
* `documentacao hardware` (Servers, UPS, iDRAC)
* `documentacao linux` (Distros e administração)
* `documentacao microsoft` (Windows Desktop, Office)
* `documentacao navegadores` (Chrome, Firefox)
* `documentacao powerbi` (PowerBI Report Server)
* `documentacao processos` (SOPs, Onboarding)
* `documentacao rede e seguranca` (Firewalls, VPNs)
* `documentacao seguranca email` (PMG - Proxmox Mail Gateway)
* `documentacao storage` (TrueNAS, File Server)
* `documentacao terminal` (Comandos Linux/Windows)
* `documentacao unifi` (Unifi Controller)
* `documentacao virtualizacao` (Proxmox VE - Geral)
* `documentacao vmware` (vCenter, ESXi)
* `documentacao web servers` (Nginx, Apache)
* `documentacao webmin` (Webmin)
* `documentacao windows` (Windows Server Core/AD)
* `documentacao zammad` (Service Desk)
**Definição dos Níveis:**
* `Nivel_0` (Cliente Final): **Didático e Visual.**
* `Nivel_1` (Técnico Jr): **Procedural (Service Desk).**
* `Nivel_2` (Técnico Pleno): **Técnico (NOC/Infra).**
* `Nivel_3` (Especialista/Sr): **Engenharia e Crise.**
### Fase 5: Atualização de Indicadores (Status Update)
Sempre que alterar o status de um manual no `README.md` (marcar de `[ ]` para `[x]`), é **OBRIGATÓRIO** atualizar as barras de progresso visuais.
1. **Execução:**
```powershell
python .gemini/gemini_cli.py update-tracking
```
2. **Validação:** Verifique se as porcentagens no topo do `README.md` foram recalculadas.
---
## CODIFICAÇÃO E CONTROLE (OBRIGATÓRIO)
> ⚠️ **CRÍTICO:** JAMAIS invente o código do manual. Você deve gerá-lo programaticamente para evitar duplicidade.
**Como obter o Código do Manual:**
1. **Execute o script de registro:**
```bash
python .gemini/gemini_cli.py register --level [0-3] --title "Nome do Manual"
```
2. **Copie o código gerado** (ex: `ITGCLI 0001/26`) da saída do comando.
3. **Cole no cabeçalho** do seu arquivo Markdown.
**Tabela de Audiências (Automático pelo Script):**
* `ITGCLI` = Nível 0 (Cliente/Leigo)
* `ITGSUP` = Nível 1 (Service Desk)
* `ITGINF` = Nível 2 (Infraestrutura)
* `ITGENG` = Nível 3 (Engenharia)
---
## Template Markdown OBRIGATÓRIO
```markdown
# MANUAL TÉCNICO - [NOME DO PROCEDIMENTO] - [SISTEMA/PLATAFORMA]
**Código:** ITITG XXX/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
> ⚠️ **REGRA DE OURO:**
> 1. **Autor:** SEMPRE preencha como `João Pedro Toledo Gonçalves`. Você escreve em nome dele.
> 2. **Descrição:** Seja ultra-conciso (Max 5 palavras). Ex: "Criação do documento", "Revisão técnica", "Adição de prints".
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
[Explique em 1 frase simples o que este procedimento resolve.]
## 3. PRÉ-REQUISITOS
> Liste o que é necessário ANTES de começar (acessos, backups, status de serviços).
* [ ] Requisito 1 (ex: Backup realizado).
* [ ] Requisito 2 (ex: Acesso root validado).
## 4. PASSO A PASSO (EXECUÇÃO)
> Divida o procedimento em etapas lógicas. Se for longo, use subtítulos.
**Etapa 1: [Nome da Ação Inicial]**
1. Instrução clara e direta.
2. Comando ou clique visual.
!!! note "Nota"
Use callouts para dicas contextuais.
**Etapa 2: [Nome da Ação Seguinte]**
1. Instrução de execução.
2. Validação visual (print).
![Descrição do Print](assets/nome_da_imagem.png)
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
!!! tip "Dica do Agente"
Liste 2 ou 3 problemas *reais* e frequentes que você encontrou durante a pesquisa. Não invente erros genéricos.
**Problema 1: [Descrição do Sintoma no Idioma do Usuário]**
* **Causa:** [Explicação Técnica]
* **Solução:**
1. Ação corretiva 1.
2. Ação corretiva 2 (com comando/print se necessário).
**Problema 2: [Outro Sintoma Comum]**
* **Solução:** Comando rápido ou verificação.
## 6. DADOS TÉCNICOS
> Liste portas, caminhos de log, versões ou usuários padrão que o técnico precisará no futuro.
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Portas** | 80, 443 | Portas Web padrão |
| **Logs** | `/var/log/syslog` | Logs do sistema |
| **Conf** | `/etc/app/config.yml` | Arquivo principal |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
> O que define "Sucesso" neste procedimento? Seja específico.
- [ ] O serviço está rodando? (`systemctl status`...)
- [ ] A interface web carrega sem erros 500?
- [ ] O log não apresenta erros novos?
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,173 @@
import os
import shutil
import subprocess
import sys
import yaml
import re
# Configuration
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BUILD_DIR = os.path.join(ROOT_DIR, "_site_src")
DOCS_DIR = os.path.join(BUILD_DIR, "docs")
MKDOCS_CONFIG = os.path.join(ROOT_DIR, "mkdocs.yml")
GEMINI_CLI = os.path.join(ROOT_DIR, ".gemini", "gemini_cli.py")
def clean_build_dir():
if os.path.exists(BUILD_DIR):
try:
shutil.rmtree(BUILD_DIR)
except Exception as e:
print(f"Warning cleaning build dir: {e}")
os.makedirs(DOCS_DIR, exist_ok=True)
def clean_folder_name(name):
# Converts 'documentacao zammad' to 'Zammad'
# Converts 'documentacao backup-restore' to 'Backup Restore'
if name.startswith("documentacao "):
name = name.replace("documentacao ", "")
# Capitalize words
return name.title()
def process_markdown_content(content):
# Remove the Manual Revision History Block
# Look for ## 1. HISTÓRICO DE REVISÃO until the next ## header
# Pattern: ## 1. HISTÓRICO... (anything until next ## or end)
pattern = r"## 1\. HISTÓRICO DE REVISÃO.*?(?=## |\Z)"
content = re.sub(pattern, "", content, flags=re.DOTALL)
# Also strip any numbering from H2 headers if needed, but user might want them.
return content
def copy_manuals():
print("Copying manuals to build directory...")
# Find all 'documentacao *' folders
for item in os.listdir(ROOT_DIR):
if os.path.isdir(os.path.join(ROOT_DIR, item)) and item.startswith("documentacao"):
src_path = os.path.join(ROOT_DIR, item)
# Nice name for the folder
clean_name = clean_folder_name(item)
dst_path = os.path.join(DOCS_DIR, clean_name)
print(f"Processing {item} -> {clean_name}")
shutil.copytree(src_path, dst_path, dirs_exist_ok=True)
# Post-process files in the new destination
for root, dirs, files in os.walk(dst_path):
for file in files:
if file.lower().endswith('.md'):
# Inject PDF Link + Strip Revision History
full_path = os.path.join(root, file)
# Generate/Check PDF name
pdf_name = file.rsplit('.', 1)[0] + ".pdf"
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
modified_content = process_markdown_content(content)
# Add PDF Button
display_name = "Baixar PDF"
link = f'<a class="md-button md-button--primary download-pdf-btn" href="./{pdf_name}">:material-file-pdf-box: {display_name}</a>\n\n'
# Insert link after header or at top
if modified_content.startswith("---"):
# Split frontmatter
parts = modified_content.split("---", 2)
if len(parts) >= 3:
parts[2] = link + parts[2]
modified_content = "---".join(parts)
else:
modified_content = link + modified_content
else:
# Try to find H1
if "# " in modified_content:
modified_content = modified_content.replace("\n# ", f"\n{link}\n# ", 1) # Put before? No, after is better.
# Actually, let's put it AT THE TOP of content, below title if possible.
# Simple regex replace for first H1
modified_content = re.sub(r'(^# .+)', r'\1\n\n' + link, modified_content, count=1, flags=re.MULTILINE)
else:
modified_content = link + modified_content
with open(full_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
except Exception as e:
print(f"Error processing {file}: {e}")
# Copy contents of 'assets' folder in root to 'docs/assets'
root_assets = os.path.join(ROOT_DIR, "assets")
docs_assets = os.path.join(DOCS_DIR, "assets")
if os.path.exists(root_assets):
print(f"Copying root assets from {root_assets} to {docs_assets}")
shutil.copytree(root_assets, docs_assets, dirs_exist_ok=True)
def create_index():
print("Creating index.md...")
# List top level directories in DOCS_DIR
dirs = [d for d in os.listdir(DOCS_DIR) if os.path.isdir(os.path.join(DOCS_DIR, d)) and d != "assets"]
dirs.sort()
links = []
for d in dirs:
links.append(f"- [{d}](./{d}/)")
content = "# Base de Conhecimento - iT Guys\n\n"
content += "Bem-vindo à documentação técnica unificada.\n\n"
content += "## Manuais Disponíveis\n\n"
content += "\n".join(links)
with open(os.path.join(DOCS_DIR, "index.md"), 'w', encoding='utf-8') as f:
f.write(content)
def generate_pdfs():
print("Generating PDFs...")
# Call gemini_cli batch_convert on the DOCS_DIR
# We need to make sure we use the same python env
subprocess.check_call([sys.executable, GEMINI_CLI, "batch-convert", DOCS_DIR])
def build_mkdocs():
print("Building MkDocs site...")
# Load base config
# Use unsafe_load to handle !!python tags
with open(os.path.join(ROOT_DIR, "mkdocs.yml"), "r") as f:
base_config = yaml.unsafe_load(f)
# Update config to point to our source folder
# IMPORTANT: We use relative path so MkDocs can find it from ROOT
base_config["docs_dir"] = "_site_src/docs"
base_config["site_dir"] = "_site_src/site"
# Ensure extra_css is copied
extra_css_src = os.path.join(ROOT_DIR, ".gemini", "stylesheets")
extra_css_dst = os.path.join(DOCS_DIR, "stylesheets")
if os.path.exists(extra_css_src):
shutil.copytree(extra_css_src, extra_css_dst, dirs_exist_ok=True)
# Write temporary config in ROOT (so it sees .git)
temp_config_path = os.path.join(ROOT_DIR, "mkdocs_generated.yml")
with open(temp_config_path, "w") as f:
yaml.dump(base_config, f)
# Run build using the generated config in ROOT
subprocess.check_call(["mkdocs", "build", "-f", "mkdocs_generated.yml"])
if __name__ == "__main__":
try:
clean_build_dir()
copy_manuals()
create_index()
generate_pdfs()
build_mkdocs()
print("Build Complete! Site is in _site_src/site")
except Exception as e:
print(f"Build Failed: {e}")
# print stack trace
import traceback
traceback.print_exc()
sys.exit(1)

View File

@ -0,0 +1,222 @@
import os
import markdown
from xhtml2pdf import pisa
from datetime import datetime
import io
# iT Guys Identity Colors
CSS_STYLES = """
@page {
size: A4;
margin: 20mm;
margin-bottom: 25mm; /* Space for footer */
background-color: #ffffff;
@frame footer_frame {
-pdf-frame-content: footer_content;
bottom: 10mm;
margin-left: 20mm;
margin-right: 20mm;
height: 10mm;
}
}
body {
font-family: Helvetica, Arial, sans-serif;
color: #333;
line-height: 1.5;
font-size: 11pt;
}
/* Headers */
h1 {
color: #1478cf; /* Primary Blue */
border-left: 5px solid #2ecc71; /* Green Accent */
padding-left: 15px;
background-color: #f0f8ff; /* Light Blue BG */
padding-top: 10px;
padding-bottom: 10px;
margin-top: 20px;
font-size: 24pt;
}
h2 {
color: #14508c; /* Darker Blue */
border-bottom: 2px solid #00f7ff; /* Cyan Accent */
padding-bottom: 5px;
margin-top: 30px;
font-size: 16pt;
}
h3 {
color: #444;
font-size: 13pt;
margin-top: 20px;
}
/* Tables */
table {
width: 100%;
border: 1px solid #ddd;
margin: 20px 0;
font-size: 10pt;
}
th {
background-color: #1478cf;
color: white;
padding: 8px;
font-weight: bold;
}
td {
border: 1px solid #ddd;
padding: 8px;
}
/* Callouts / Admonitions (Python-Markdown Extension) */
.admonition {
margin: 20px 0;
padding: 0;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #ffffff;
page-break-inside: avoid;
}
.admonition-title {
font-weight: bold;
padding: 8px 12px;
background-color: #f0f0f0;
border-bottom: 1px solid #ddd;
font-size: 10pt;
text-transform: uppercase;
}
/* Specific Types */
.admonition.note, .admonition.info {
border-color: #bce8f1;
}
.admonition.note .admonition-title, .admonition.info .admonition-title {
background-color: #d9edf7;
color: #31708f;
}
.admonition.warning, .admonition.important, .admonition.attention {
border-color: #faebcc;
}
.admonition.warning .admonition-title, .admonition.important .admonition-title {
background-color: #fcf8e3;
color: #8a6d3b;
}
.admonition.tip, .admonition.hint, .admonition.success {
border-color: #d6e9c6;
}
.admonition.tip .admonition-title, .admonition.hint .admonition-title {
background-color: #dff0d8;
color: #3c763d;
}
.admonition p {
padding: 10px 12px;
margin: 0;
}
/* Legacy Blockquote fallback */
blockquote {
background-color: #f9f9f9;
border-left: 5px solid #ccc;
margin: 10px 0;
padding: 10px;
color: #666;
}
/* Code Blocks */
pre {
background-color: #2b2b2b;
color: #f8f8f2;
padding: 10px;
border: 1px solid #444;
font-family: monospace;
font-size: 9pt;
white-space: pre-wrap; /* Wrap long lines */
}
/* Images */
img {
zoom: 60%; /* xhtml2pdf sometimes renders images very large */
margin: 20px auto;
}
"""
def convert_markdown_to_pdf(input_path, output_path):
# Read Markdown
with open(input_path, 'r', encoding='utf-8') as f:
md_text = f.read()
# Process Variables
now = datetime.now()
replacements = {
'{{DATA_ATUAL}}': now.strftime("%d/%m/%Y"),
'{{ANO}}': str(now.year)
}
for k, v in replacements.items():
if k in md_text:
md_text = md_text.replace(k, v)
# Determine Base Directory for assets
base_dir = os.path.dirname(os.path.abspath(input_path)).replace("\\", "/")
# Pre-process image paths to be absolute for xhtml2pdf
import re
def replace_img(match):
alt = match.group(1)
src = match.group(2)
if not os.path.isabs(src) and not src.startswith("http"):
src = os.path.join(base_dir, src).replace("\\", "/")
return f'![{alt}]({src})'
md_text = re.sub(r'!\[(.*?)\]\((.*?)\)', replace_img, md_text)
# Convert Markdown to HTML
html_content = markdown.markdown(
md_text,
extensions=['tables', 'fenced_code', 'toc', 'sane_lists', 'admonition']
)
# Footer content
footer = """
<div id="footer_content" style="text-align: right; color: #666; font-size: 9pt;">
iT Guys - Documentação Técnica - <pdf:pagenumber>
</div>
"""
final_html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>{CSS_STYLES}</style>
</head>
<body>
{footer}
<div class="content">
{html_content}
</div>
</body>
</html>
"""
# Generate PDF with xhtml2pdf
with open(output_path, "w+b") as result_file:
pisa_status = pisa.CreatePDF(
final_html,
dest=result_file,
encoding='utf-8'
)
if pisa_status.err:
raise Exception("PDF generation failed due to xhtml2pdf errors.")
return True

View File

@ -0,0 +1,58 @@
import os
base_path = r"c:\Users\joao.goncalves\Desktop\manuais zammad"
folders = {
"documentacao web servers": ["Nginx", "Apache"],
"documentacao ftp": ["FTP", "SFTP"],
"documentacao endpoint": ["ManageEngine"],
"documentacao webmin": ["Webmin"],
"documentacao editores": ["Nano", "Vim"],
"documentacao terminal": ["Linux", "Windows"],
"documentacao agendamento": ["Cron", "Task Scheduler"],
"documentacao diagnostico rede": ["Ping", "Traceroute", "Netstat"],
"documentacao certificados": ["Certbot", "Lets Encrypt"],
"documentacao backup": ["Veeam"],
"documentacao colaboracao": ["Nextcloud", "Office Online"],
"documentacao microsoft": ["Windows Desktop", "Office", "Exchange"],
"documentacao seguranca email": ["PMG", "Proxmox Mail Gateway"],
"documentacao unifi": ["Unifi Controller"],
"documentacao powerbi": ["PowerBI"],
"documentacao dev": ["VSCode Server"],
"documentacao zammad": ["Service Desk"],
"documentacao hardware": ["Servers", "UPS"],
"documentacao automacao": ["PowerShell", "Bash", "Ansible"],
"documentacao processos": ["SOPs", "Onboarding", "Incidentes"]
}
subfolders = ["Nivel_0", "Nivel_1", "Nivel_2", "Nivel_3"]
def create_structure():
print(f"Starting directory creation in {base_path}...")
created_count = 0
for folder, tags in folders.items():
folder_path = os.path.join(base_path, folder)
# Create main topic folder
if not os.path.exists(folder_path):
os.makedirs(folder_path)
print(f"[NEW] Created folder: {folder}")
created_count += 1
else:
print(f"[EXISTS] Folder: {folder}")
# Create subfolders (Level 0-3)
for sub in subfolders:
sub_path = os.path.join(folder_path, sub)
if not os.path.exists(sub_path):
os.makedirs(sub_path)
# Create a placeholder .keep file to validade emptiness if needed, purely optional
# with open(os.path.join(sub_path, ".gitkeep"), 'w') as f: pass
# print(f" -> Created subfolder: {sub}")
print(f"\nStructure creation complete. {created_count} new main directories created.")
if __name__ == "__main__":
create_structure()

View File

@ -0,0 +1,77 @@
import os
import shutil
import re
ROOT_DIR = os.getcwd()
def flatten_structure():
print("Starting structure flattening...")
# Iterate over all directories in root
for item in os.listdir(ROOT_DIR):
theme_dir = os.path.join(ROOT_DIR, item)
# We only care about "documentacao *" folders
if os.path.isdir(theme_dir) and item.startswith("documentacao "):
print(f"Processing: {item}")
# Look for Nivel_* folders inside
for subitem in os.listdir(theme_dir):
subitem_path = os.path.join(theme_dir, subitem)
if os.path.isdir(subitem_path) and subitem.startswith("Nivel_"):
print(f" Found subfolder: {subitem}")
# Move all files from Nivel_X to Root of Theme
for file in os.listdir(subitem_path):
src_path = os.path.join(subitem_path, file)
dst_path = os.path.join(theme_dir, file)
# Handle Assets Special Case
if file == "assets" and os.path.isdir(src_path):
theme_assets = os.path.join(theme_dir, "assets")
if not os.path.exists(theme_assets):
os.makedirs(theme_assets)
# Merge assets
for asset in os.listdir(src_path):
asset_src = os.path.join(src_path, asset)
asset_dst = os.path.join(theme_assets, asset)
if not os.path.exists(asset_dst):
shutil.move(asset_src, asset_dst)
else:
print(f" Warning: Asset {asset} already exists in target. Skipping.")
# Remove empty assets folder
try:
os.rmdir(src_path)
except OSError:
pass # Not empty?
elif os.path.isfile(src_path):
# Move Main Manual File
if os.path.exists(dst_path):
print(f" Warning: File {file} already exists in {item}. Renaming to avoid overwrite.")
base, ext = os.path.splitext(file)
dst_path = os.path.join(theme_dir, f"{base}_migrated{ext}")
shutil.move(src_path, dst_path)
print(f" Moved: {file}")
# Remove the now empty Nivel_X folder
try:
os.rmdir(subitem_path)
print(f" Removed empty folder: {subitem}")
except OSError:
print(f" Warning: Could not remove {subitem}, it might not be empty.")
# Look for other subfolders that are NOT assets (e.g., 'docker', 'pfsense' inside main categories)
# The user requested flattening. "quero todos os manuais na "raiz" da pasta do assunto"
# This implies if recursive structures exist like "Conteineres/docker/README.md", they should move to "Conteineres/docker_README.md" OR just stay if they are not "Nivel_X"?
# The user specifically said "nao quero pastas internas com o nivel".
# I will focus on flattening "Nivel_*" folders primarily.
# If there are other folders (like subject specific subfolders), I should probably check with user or assume "Nivel_*" was the main offender.
# Start with just Nivel_* as that covers 90% of the structure.
if __name__ == "__main__":
flatten_structure()

View File

@ -0,0 +1,270 @@
import typer
import os
import sys
import json
import re
import subprocess
from typing import Optional
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
from rich.panel import Panel
from rich.table import Table
from datetime import datetime
# Import Core Conversion
try:
from convert_core import convert_markdown_to_pdf
except ImportError:
# If running from root without .gemini in path, handle it
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from convert_core import convert_markdown_to_pdf
app = typer.Typer(help="Gemini - iT Guys Documentation Assistant CLI")
console = Console()
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(BASE_DIR)
REGISTRY_FILE = os.path.join(BASE_DIR, "manual_registry.json")
# --- Helper Functions ---
def load_registry():
if not os.path.exists(REGISTRY_FILE):
console.print(f"[red]Error: Registry file not found at {REGISTRY_FILE}[/red]")
raise typer.Exit(code=1)
with open(REGISTRY_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_registry(data):
with open(REGISTRY_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def get_manual_level(filename):
match = re.search(r'\[Nível\s*(\d+)\]', filename, re.IGNORECASE)
if match: return int(match.group(1))
if "Nivel_0" in filename: return 0
if "Nivel_1" in filename: return 1
if "Nivel_2" in filename: return 2
if "Nivel_3" in filename: return 3
return None
# --- Commands ---
@app.command()
def convert(
files: list[str] = typer.Argument(..., help="List of Markdown files to convert"),
output: Optional[str] = typer.Option(None, help="Output directory or specific file (if single input)")
):
"""Convert specific Markdown manuals to PDF using WeasyPrint."""
for file_path in files:
if not os.path.exists(file_path):
console.print(f"[red]File not found: {file_path}[/red]")
continue
md_file = os.path.abspath(file_path)
if output and len(files) == 1 and output.endswith('.pdf'):
pdf_file = output
elif output:
os.makedirs(output, exist_ok=True)
pdf_file = os.path.join(output, os.path.splitext(os.path.basename(md_file))[0] + ".pdf")
else:
pdf_file = os.path.splitext(md_file)[0] + ".pdf"
console.print(f"[cyan]Converting:[/cyan] {os.path.basename(md_file)} -> {os.path.basename(pdf_file)}")
try:
convert_markdown_to_pdf(md_file, pdf_file)
console.print(f"[green]Success![/green]")
except Exception as e:
console.print(f"[red]Failed:[/red] {e}")
@app.command()
def batch_convert(
directory: str = typer.Argument(PROJECT_ROOT, help="Root directory to scan for manuals")
):
"""Scan directory and convert all valid manuals to PDF."""
md_files = []
for root, dirs, files in os.walk(directory):
if '.gemini' in dirs: dirs.remove('.gemini')
if '.git' in dirs: dirs.remove('.git')
for file in files:
if file.lower().endswith('.md') and not file.lower().startswith('readme'):
if 'task.md' not in file and 'implementation_plan' not in file:
md_files.append(os.path.join(root, file))
console.print(f"Found [bold]{len(md_files)}[/bold] manuals to convert.")
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
task = progress.add_task("[cyan]Converting...", total=len(md_files))
success = 0
errors = 0
for md_file in md_files:
progress.update(task, description=f"Processing {os.path.basename(md_file)}")
pdf_file = os.path.splitext(md_file)[0] + ".pdf"
try:
convert_markdown_to_pdf(md_file, pdf_file)
success += 1
except Exception as e:
console.print(f"[red]Error converting {os.path.basename(md_file)}: {e}[/red]")
errors += 1
progress.advance(task)
console.print(Panel(f"Batch Complete.\nSuccess: {success}\nErrors: {errors}", title="Summary", border_style="green"))
@app.command()
def register(
level: int = typer.Option(..., help="Manual Level (0-3)"),
title: str = typer.Option(..., help="Manual Title"),
author: str = typer.Option("João Pedro Toledo Gonçalves", help="Author Name")
):
"""Generate a new manual code in the registry."""
audience_map = {0: "ITGCLI", 1: "ITGSUP", 2: "ITGINF", 3: "ITGENG"}
if level not in audience_map:
console.print("[red]Invalid Level. Must be 0-3.[/red]")
raise typer.Exit(1)
audience_code = audience_map[level]
registry = load_registry()
if audience_code not in registry:
registry[audience_code] = {"next_id": 1, "manuals": []}
current_id = registry[audience_code]["next_id"]
year = datetime.now().strftime("%y")
manual_code = f"{audience_code} {current_id:04d}/{year}"
entry = {
"code": manual_code,
"id": current_id,
"title": title,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"author": author
}
registry[audience_code]["manuals"].append(entry)
registry[audience_code]["next_id"] += 1
save_registry(registry)
console.print(Panel(f"Code: [bold green]{manual_code}[/bold green]\nTitle: {title}", title="Registered Successfully"))
# Return code for pipe usage if needed
print(manual_code)
@app.command()
def update_tracking(
readme_path: str = typer.Argument(os.path.join(PROJECT_ROOT, "README.md"))
):
"""Update progress bars in README.md."""
if not os.path.exists(readme_path):
console.print(f"[red]README not found at {readme_path}[/red]")
return
with open(readme_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# Logic extracted from update_progress.py (Refactored for efficiency)
clean_lines = [l for l in lines if not re.match(r'^>\s*\*\*Status:\*\*\s*`[▓░]+`', l.strip())]
total_tasks = 0
done_tasks = 0
section_stats = {}
current_sec_idx = -1
# Analyze
for idx, line in enumerate(clean_lines):
if line.strip().startswith('### '):
current_sec_idx = idx
section_stats[current_sec_idx] = {'total': 0, 'done': 0}
is_task = re.search(r'^\s*-\s*\[([ xX])\]', line)
if is_task:
total_tasks += 1
if is_task.group(1).lower() == 'x':
done_tasks += 1
if current_sec_idx != -1:
section_stats[current_sec_idx]['total'] += 1
if is_task.group(1).lower() == 'x':
section_stats[current_sec_idx]['done'] += 1
# Generate Bars
def get_bar(done, total, length=20):
pct = (done / total * 100) if total > 0 else 0
filled = int(length * done // total) if total > 0 else 0
bar_visual = '' * filled + '' * (length - filled)
return f"> **Status:** `{bar_visual}` **{int(pct)}%** ({done}/{total})\n"
final_content = []
global_inserted = False
for idx, line in enumerate(clean_lines):
final_content.append(line)
# Global Bar
if "## 📊 Quadro de Status" in line and not global_inserted:
final_content.append("\n" + get_bar(done_tasks, total_tasks, 30))
global_inserted = True
# Section Bars
if idx in section_stats:
stats = section_stats[idx]
if stats['total'] > 0:
final_content.append(get_bar(stats['done'], stats['total']))
with open(readme_path, 'w', encoding='utf-8') as f:
f.writelines(final_content)
console.print(f"[green]Tracking updated![/green] Global: {int(done_tasks/total_tasks*100)}%")
@app.command()
def audit():
"""Scan for manuals missing codes, register them, and update source."""
search_path = os.path.join(PROJECT_ROOT, "**", "*.md")
# Use simple walk instead of glob recursive for python < 3.10 compat if needed, but 3.10+ ok
# Standard walk
for root, dirs, files in os.walk(PROJECT_ROOT):
if '.gemini' in dirs: dirs.remove('.gemini')
for file in files:
if not file.endswith('.md') or file.lower().startswith('readme'): continue
full_path = os.path.join(root, file)
level = get_manual_level(full_path)
if level is None: continue
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
# Check if has code
if "**Código:** IT" in content:
continue # Already has code
# Needs registration
console.print(f"[yellow]Missing code:[/yellow] {file}")
clean_title = re.sub(r'\[Nível\s*\d+\]', '', file).replace('.md', '').strip(" -_")
# Call register logic internally? Or subprocess?
# Let's verify if we want to auto-register.
# For now, just listing. The user can run register.
# Actually, the original batch_update did auto register. Let's do it.
# NOTE: Logic to call register command via code:
# Replicating logic here for simplicity/speed
# ... (Registry logic copied/called) ...
pass # skipping complex auto-update for this iteration to prioritize PDF fix.
if __name__ == "__main__":
app()

View File

@ -0,0 +1,17 @@
import os
def on_page_markdown(markdown, page, config, files):
"""
Hook to override page title with its filename (sanitized) to keep sidebar clean.
"""
# Get filename without extension
filename = os.path.basename(page.file.src_path)
filename_no_ext = os.path.splitext(filename)[0]
# Replace underscores/dashes with spaces for readability in sidebar
clean_title = filename_no_ext.replace('_', ' ').replace('-', ' ')
# Override the title used in navigation
page.title = clean_title
return markdown

View File

@ -0,0 +1,310 @@
[
{
"url": "https://docs.netgate.com/pfsense/en/latest/",
"description": "pfSense Official Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:36:51"
},
{
"url": "https://www.truenas.com/docs/scale/",
"description": "TrueNAS Scale Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:36:51"
},
{
"url": "https://www.postgresql.org/docs/",
"description": "PostgreSQL Official Docs",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:36:51"
},
{
"url": "https://learn.microsoft.com/en-us/windows-server/",
"description": "Windows Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:36:51"
},
{
"url": "https://ubuntu.com/server/docs",
"description": "Ubuntu Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:09"
},
{
"url": "https://www.debian.org/doc/",
"description": "Debian Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:09"
},
{
"url": "https://wiki.alpinelinux.org/wiki/Main_Page",
"description": "Alpine Linux Wiki",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:09"
},
{
"url": "https://pve.proxmox.com/wiki/Main_Page",
"description": "Proxmox VE Wiki",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:09"
},
{
"url": "https://docs.docker.com/",
"description": "Docker Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:09"
},
{
"url": "https://docs.portainer.io/",
"description": "Portainer Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:10"
},
{
"url": "https://docs.gitea.com/",
"description": "Gitea Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:27"
},
{
"url": "https://www.zabbix.com/documentation/current/en",
"description": "Zabbix Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:27"
},
{
"url": "https://support.google.com/chrome/a/",
"description": "Chrome Enterprise Support",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:27"
},
{
"url": "https://support.mozilla.org/en-US/products/firefox-enterprise",
"description": "Firefox for Enterprise",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:27"
},
{
"url": "https://www.chiark.greenend.org.uk/~sgtatham/putty/docs.html",
"description": "PuTTY User Manual",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:37:28"
},
{
"url": "https://docs.asterisk.org/",
"description": "Asterisk Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:41:55"
},
{
"url": "https://nginx.org/en/docs/",
"description": "Nginx Official Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:14"
},
{
"url": "https://httpd.apache.org/docs/",
"description": "Apache HTTP Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:15"
},
{
"url": "https://www.manageengine.com/products/desktop-central/help/",
"description": "ManageEngine Endpoint Central Help",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:15"
},
{
"url": "https://webmin.com/docs/",
"description": "Webmin Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:15"
},
{
"url": "https://winscp.net/eng/docs/start",
"description": "WinSCP Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:15"
},
{
"url": "https://wiki.filezilla-project.org/Documentation",
"description": "FileZilla/FTP Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:52:15"
},
{
"url": "https://www.nano-editor.org/docs.php",
"description": "Nano Editor Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:18"
},
{
"url": "https://www.vim.org/docs.php",
"description": "Vim Official Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:18"
},
{
"url": "https://eff-certbot.readthedocs.io/en/stable/",
"description": "Certbot (Let's Encrypt) Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:18"
},
{
"url": "https://www.veeam.com/documentation-guides-datasheets.html",
"description": "Veeam Documentation Center",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:18"
},
{
"url": "https://ss64.com/bash/",
"description": "Linux Command Line Reference (ss64)",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:18"
},
{
"url": "https://ss64.com/nt/",
"description": "Windows CMD Reference (ss64)",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:19"
},
{
"url": "https://ss64.com/ps/",
"description": "PowerShell Reference (ss64)",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 00:55:19"
},
{
"url": "https://suricata.readthedocs.io/en/latest/",
"description": "Suricata IDS/IPS Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 01:00:49"
},
{
"url": "https://openvpn.net/community-resources/",
"description": "OpenVPN Community Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 01:00:49"
},
{
"url": "https://redis.io/docs/",
"description": "Redis Official Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 01:00:49"
},
{
"url": "https://wiki.centos.org/",
"description": "CentOS Wiki",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 01:00:49"
},
{
"url": "https://www.magnusbilling.org/documentation/",
"description": "MagnusBilling Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 01:00:49"
},
{
"url": "https://docs.nextcloud.com/server/latest/admin_manual/",
"description": "Nextcloud Admin Manual",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:21"
},
{
"url": "https://learn.microsoft.com/en-us/officeonlineserver/office-online-server",
"description": "Office Online Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:21"
},
{
"url": "https://pmg.proxmox.com/pmg-docs/",
"description": "Proxmox Mail Gateway Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:21"
},
{
"url": "https://help.ui.com/",
"description": "Ubiquiti UniFi Support/Docs",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:21"
},
{
"url": "https://learn.microsoft.com/en-us/power-bi/report-server/",
"description": "Power BI Report Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:21"
},
{
"url": "https://code.visualstudio.com/docs/remote/vscode-server",
"description": "VS Code Server Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:22"
},
{
"url": "https://learn.microsoft.com/en-us/windows/",
"description": "Windows Desktop Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 06:58:22"
},
{
"url": "https://docs.vmware.com/en/VMware-vSphere/",
"description": "VMware vSphere Documentation (Broadcom)",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 07:03:11"
},
{
"url": "https://docs.zammad.org/",
"description": "Zammad Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 07:09:52"
},
{
"url": "https://learn.microsoft.com/en-us/powershell/",
"description": "PowerShell Documentation",
"category": "Docs",
"tags": [],
"added_at": "2026-01-23 07:09:52"
}
]

View File

@ -0,0 +1,456 @@
{
"ITGCLI": {
"next_id": 4,
"manuals": [
{
"code": "ITGCLI 0001/26",
"id": 1,
"title": "Como Acessar e Configurar Webmail e Celular",
"created_at": "2026-01-23 00:28:20",
"author": "Agente iT Guys"
},
{
"code": "ITGCLI 0002/26",
"id": 2,
"title": "Como Configurar Resposta Automatica de Ferias",
"created_at": "2026-01-23 00:28:23",
"author": "Agente iT Guys"
},
{
"code": "ITGCLI 0003/26",
"id": 3,
"title": "Docker para Desenvolvedores e Ferramentas Portáteis",
"created_at": "2026-01-25 03:15:50",
"author": "João Pedro Toledo Gonçalves"
}
]
},
"ITGSUP": {
"next_id": 15,
"manuals": [
{
"code": "ITGSUP 0001/26",
"id": 1,
"title": "Diagnostico Basico - Usuario nao recebe e-mail",
"created_at": "2026-01-23 00:28:24",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0002/26",
"id": 2,
"title": "Procedimento de Criacao e Bloqueio de Usuarios",
"created_at": "2026-01-23 00:28:25",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0003/26",
"id": 3,
"title": "N1_00_Visao_Geral_e_Escopo",
"created_at": "2026-01-23 00:28:38",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0004/26",
"id": 4,
"title": "N1_01_Verificacao_Basica_VM",
"created_at": "2026-01-23 00:28:39",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0005/26",
"id": 5,
"title": "N1_02_Operacoes_de_Energia",
"created_at": "2026-01-23 00:28:40",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0006/26",
"id": 6,
"title": "N1_03_Monitoramento_Basico",
"created_at": "2026-01-23 00:28:41",
"author": "Agente iT Guys"
},
{
"code": "ITGSUP 0007/26",
"id": 7,
"title": "Instalação e Configuração do Docker e Compose (Linux)",
"created_at": "2026-01-25 03:08:42",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0008/26",
"id": 8,
"title": "Deploy do Portainer CE (Docker Standalone)",
"created_at": "2026-01-25 03:12:14",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0009/26",
"id": 9,
"title": "Gestão de Stacks no Portainer",
"created_at": "2026-01-25 03:12:16",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0010/26",
"id": 10,
"title": "Visão Geral e Escopo VMware",
"created_at": "2026-01-25 03:21:59",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0011/26",
"id": 11,
"title": "Visão Geral Proxmox VE",
"created_at": "2026-01-25 03:22:44",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0012/26",
"id": 12,
"title": "Verificação Básica VM",
"created_at": "2026-01-25 03:24:27",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0013/26",
"id": 13,
"title": "Monitoramento de Saúde de Discos",
"created_at": "2026-01-25 17:50:21",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGSUP 0014/26",
"id": 14,
"title": "Verificação Diária de Jobs de VM",
"created_at": "2026-01-26 20:06:25",
"author": "João Pedro Toledo Gonçalves"
}
]
},
"ITGINF": {
"next_id": 24,
"manuals": [
{
"code": "ITGINF 0001/26",
"id": 1,
"title": "Gerenciamento de Cotas e Arquivamento",
"created_at": "2026-01-23 00:28:26",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0002/26",
"id": 2,
"title": "Gerenciamento de Quarentena Movel (ActiveSync)",
"created_at": "2026-01-23 00:28:27",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0003/26",
"id": 3,
"title": "Gestao de Permissoes (Full Access e Send As)",
"created_at": "2026-01-23 00:28:27",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0004/26",
"id": 4,
"title": "Solucao de Problemas de Lista de Enderecos (OAB)",
"created_at": "2026-01-23 00:28:28",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0005/26",
"id": 5,
"title": "N2_01_Gestao_de_Recursos",
"created_at": "2026-01-23 00:28:42",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0006/26",
"id": 6,
"title": "N2_02_Networking_e_Storage",
"created_at": "2026-01-23 00:28:43",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0007/26",
"id": 7,
"title": "N2_03_Manutencao_e_Lifecycle",
"created_at": "2026-01-23 00:28:44",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0008/26",
"id": 8,
"title": "N2_04_Troubleshooting",
"created_at": "2026-01-23 00:28:45",
"author": "Agente iT Guys"
},
{
"code": "ITGINF 0009/26",
"id": 9,
"title": "Redes e Firewall no Docker",
"created_at": "2026-01-25 03:10:05",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0010/26",
"id": 10,
"title": "Volumes e Persistência de Dados no Docker",
"created_at": "2026-01-25 03:11:09",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0011/26",
"id": 11,
"title": "Healthchecks e Scripts Automatizados no Docker",
"created_at": "2026-01-25 03:13:50",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0012/26",
"id": 12,
"title": "Gestao de Users Groups e ACLs",
"created_at": "2026-01-25 17:45:08",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0013/26",
"id": 13,
"title": "Configuração de Snapshots e Replicacao",
"created_at": "2026-01-25 17:45:12",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0014/26",
"id": 14,
"title": "Instalacao pfSense",
"created_at": "2026-01-25 18:23:51",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0015/26",
"id": 15,
"title": "Customizacao pfSense",
"created_at": "2026-01-25 18:23:52",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0016/26",
"id": 16,
"title": "Usuarios LDAP pfSense",
"created_at": "2026-01-25 18:23:52",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0017/26",
"id": 17,
"title": "Interfaces e VLANs pfSense",
"created_at": "2026-01-25 18:31:51",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0018/26",
"id": 18,
"title": "Firewall e NAT pfSense",
"created_at": "2026-01-25 18:31:51",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0019/26",
"id": 19,
"title": "Servico DNS pfSense",
"created_at": "2026-01-25 18:37:15",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0020/26",
"id": 20,
"title": "Servico DHCP pfSense",
"created_at": "2026-01-25 18:37:15",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0021/26",
"id": 21,
"title": "Servico TFTP pfSense",
"created_at": "2026-01-25 18:37:15",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0022/26",
"id": 22,
"title": "Restauração de Arquivos Guest (Windows/Linux)",
"created_at": "2026-01-26 20:08:05",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGINF 0023/26",
"id": 23,
"title": "Instant VM Recovery (Restauração Rápida)",
"created_at": "2026-01-26 20:09:10",
"author": "João Pedro Toledo Gonçalves"
}
]
},
"ITGENG": {
"next_id": 23,
"manuals": [
{
"code": "ITGENG 0001/26",
"id": 1,
"title": "Acesso ao Servidor Windows Core e PowerShell",
"created_at": "2026-01-23 00:28:29",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0002/26",
"id": 2,
"title": "Arquitetura Tecnica e Mapeamento de Servidores",
"created_at": "2026-01-23 00:28:30",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0003/26",
"id": 3,
"title": "Auditoria Administrativa e Logs de Seguranca",
"created_at": "2026-01-23 00:28:31",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0004/26",
"id": 4,
"title": "Checklist de Manutencao Diaria e Semanal",
"created_at": "2026-01-23 00:28:32",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0005/26",
"id": 5,
"title": "Disaster Recovery e Soft Restore",
"created_at": "2026-01-23 00:28:33",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0006/26",
"id": 6,
"title": "Gestao de Conectores e Roteamento de E-mail",
"created_at": "2026-01-23 00:28:34",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0007/26",
"id": 7,
"title": "Referencia de Operacoes via PowerShell",
"created_at": "2026-01-23 00:28:35",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0008/26",
"id": 8,
"title": "Relatorios Avancados e Manutencao",
"created_at": "2026-01-23 00:28:36",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0009/26",
"id": 9,
"title": "Renovacao de Certificado SSL e Integracao com IIS",
"created_at": "2026-01-23 00:28:37",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0010/26",
"id": 10,
"title": "N3_01_CLI_Troubleshooting",
"created_at": "2026-01-23 00:28:46",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0011/26",
"id": 11,
"title": "N3_02_Networking_Avancado",
"created_at": "2026-01-23 00:28:47",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0012/26",
"id": 12,
"title": "N3_03_Storage_Deep_Dive",
"created_at": "2026-01-23 00:28:48",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0013/26",
"id": 13,
"title": "N3_04_DR_Arquitetura",
"created_at": "2026-01-23 00:28:49",
"author": "Agente iT Guys"
},
{
"code": "ITGENG 0014/26",
"id": 14,
"title": "Docker Swarm: Inicialização e Gerenciamento",
"created_at": "2026-01-25 03:13:48",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0015/26",
"id": 15,
"title": "Kubernetes: Visão Geral e Ferramentas Básicas",
"created_at": "2026-01-25 03:13:49",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0016/26",
"id": 16,
"title": "Arquitetura ZFS e Planejamento",
"created_at": "2026-01-25 17:45:06",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0017/26",
"id": 17,
"title": "Manutencao e Performance ZFS",
"created_at": "2026-01-25 17:45:13",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0018/26",
"id": 18,
"title": "Configuração de iSCSI Target",
"created_at": "2026-01-25 17:50:24",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0019/26",
"id": 19,
"title": "Planejamento Hardware pfSense",
"created_at": "2026-01-25 18:23:51",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0020/26",
"id": 20,
"title": "MultiWAN e Roteamento pfSense",
"created_at": "2026-01-25 18:31:51",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0021/26",
"id": 21,
"title": "Configuração de Repositórios Imutáveis (Hardened Linux)",
"created_at": "2026-01-26 20:10:07",
"author": "João Pedro Toledo Gonçalves"
},
{
"code": "ITGENG 0022/26",
"id": 22,
"title": "Criação de Rotinas de Teste de Restore (SureBackup)",
"created_at": "2026-01-26 20:11:14",
"author": "João Pedro Toledo Gonçalves"
}
]
}
}

View File

@ -0,0 +1,82 @@
import json
import os
import sys
import argparse
from datetime import datetime
# Configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
KB_FILE = os.path.join(BASE_DIR, "knowledge_base.json")
def load_kb():
if not os.path.exists(KB_FILE):
return []
try:
with open(KB_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading KB: {e}")
return []
def save_kb(data):
try:
with open(KB_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error saving KB: {e}")
sys.exit(1)
def register_source(url, description, category, tags):
kb = load_kb()
# Check for duplicates
for entry in kb:
if entry.get("url") == url:
print(f"URL already registered: {url}")
return
new_entry = {
"url": url,
"description": description,
"category": category,
"tags": tags,
"added_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
kb.append(new_entry)
save_kb(kb)
print("SUCCESS: Knowledge Source Registered")
print(json.dumps(new_entry, indent=2, ensure_ascii=False))
def list_sources(filter_text=None):
kb = load_kb()
print(f"Total Sources: {len(kb)}")
for entry in kb:
if filter_text:
search_content = (entry['url'] + entry['description'] + entry['category']).lower()
if filter_text.lower() not in search_content:
continue
print(f"[{entry['category']}] {entry['description']} ({entry['url']})")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Register knowledge sources for iT Guys.")
subparsers = parser.add_subparsers(dest="command", required=True)
# Add command
add_parser = subparsers.add_parser("add", help="Add a new source")
add_parser.add_argument("--url", required=True, help="URL of the source")
add_parser.add_argument("--description", required=True, help="Short description")
add_parser.add_argument("--category", default="General", help="Category (Docs, Tool, Reference)")
add_parser.add_argument("--tags", nargs="*", default=[], help="Tags provided as space-separated list")
# List command
list_parser = subparsers.add_parser("list", help="List sources")
list_parser.add_argument("filter", nargs="?", help="Optional filter text")
args = parser.parse_args()
if args.command == "add":
register_source(args.url, args.description, args.category, args.tags)
elif args.command == "list":
list_sources(args.filter)

View File

@ -0,0 +1,68 @@
/* iT Guys Standard Documentation Style */
/* Headers */
h1 {
color: #1478cf !important;
font-weight: 700 !important;
margin-bottom: 1.5rem !important;
}
h2 {
color: #1478cf !important;
border-bottom: 2px solid #1478cf;
padding-bottom: 0.5rem;
margin-top: 2rem !important;
}
h3 {
color: #333;
font-weight: 600;
}
/* Dark mode adjustments for headers */
[data-md-color-scheme="slate"] h3 {
color: #e5e5e5;
}
/* Tables - Matching PDF style */
.md-typeset table:not([class]) {
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
font-size: 0.9em;
}
.md-typeset table:not([class]) th {
background-color: #1478cf;
color: white !important;
font-weight: bold;
}
/* Button override */
.md-button {
font-weight: bold;
border-radius: 4px;
}
.md-button--primary {
background-color: #1478cf !important;
color: white !important;
}
/* Fix for the download button appearing inline */
.download-pdf-btn {
display: inline-block;
margin-bottom: 2rem;
text-decoration: none;
}
/* Hiding "Made with Material" text node and link */
.md-copyright {
font-size: 0 !important; /* Hides "Made with" text */
}
.md-copyright__highlight {
font-size: 0.64rem !important; /* Restores size for our copyright */
display: inline-block;
}

View File

@ -0,0 +1,111 @@
import json
import os
import sys
import urllib.request
import urllib.error
import re
# Configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
KB_FILE = os.path.join(BASE_DIR, "knowledge_base.json")
def load_kb():
if not os.path.exists(KB_FILE):
return []
try:
with open(KB_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading KB: {e}")
return []
def save_kb(data):
try:
with open(KB_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error saving KB: {e}")
sys.exit(1)
def validate_url(url, description):
print(f"Checking: {url}...", end=" ", flush=True)
try:
# User-Agent is often required to avoid 403 Forbidden from some documentation sites
req = urllib.request.Request(
url,
data=None,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
)
with urllib.request.urlopen(req, timeout=15) as response:
if response.status != 200:
print(f"[FAIL] Status {response.status}")
return False
content = response.read().decode('utf-8', errors='ignore').lower()
# Simple content check: look for significant words from description
# Exclude common stop words
stop_words = {'documentation', 'official', 'manual', 'reference', 'guide', 'wiki', 'site', 'docs', 'for', 'enterprise', 'support', 'user', 'the', 'and', 'of', 'a', 'to', 'in'}
keywords = [w.lower() for w in re.split(r'\W+', description) if w.lower() and w.lower() not in stop_words]
# Special case for abbreviations
if "pfsense" in description.lower(): keywords.append("netgate")
if "truenas" in description.lower(): keywords.append("ixsystems")
if "proxmox" in description.lower(): keywords.append("virtualization")
if not keywords:
# If no keywords remain, just trust the 200 OK
print("[OK] (Status 200)")
return True
found = any(k in content for k in keywords)
if found:
print(f"[OK] Found keywords: {[k for k in keywords if k in content]}")
return True
else:
# Be lenient if status is 200 but keywords not found (failed fuzzy match)
# But user asked: "tem o conteudo esperado?"
# Let's try to find title
title_match = re.search(r'<title>(.*?)</title>', content, re.IGNORECASE | re.DOTALL)
title = title_match.group(1).strip() if title_match else "No Title"
print(f"[WARNING] 200 OK but keywords {keywords} not found. Title: '{title}'")
# We will keep it if it's 200 OK, assuming my keyword matching might be too strict
# Unless title is clearly error
if "404" in title or "Not Found" in title:
return False
return True
except urllib.error.HTTPError as e:
print(f"[FAIL] HTTP Error {e.code}")
return False
except urllib.error.URLError as e:
print(f"[FAIL] Connection Error {e.reason}")
return False
except Exception as e:
print(f"[FAIL] Unexpected Error {e}")
return False
def main():
kb = load_kb()
valid_entries = []
modified = False
print(f"Validating {len(kb)} sources...\n")
for entry in kb:
if validate_url(entry['url'], entry['description']):
valid_entries.append(entry)
else:
print(f" -> REMOVING: {entry['description']} ({entry['url']})")
modified = True
if modified:
save_kb(valid_entries)
print(f"\nUpdate Complete. Removed {len(kb) - len(valid_entries)} invalid sources.")
else:
print("\nAll sources are valid. No changes made.")
if __name__ == "__main__":
main()

45
_backup_config/Dockerfile Normal file
View File

@ -0,0 +1,45 @@
FROM python:3.11-slim as builder
# Install system dependencies for WeasyPrint
RUN apt-get update && apt-get install -y \
build-essential \
pkg-config \
libcairo2-dev \
git \
python3-cffi \
python3-brotli \
libpango-1.0-0 \
libpangoft2-1.0-0 \
libharfbuzz-subset0 \
libjpeg-dev \
libopenjp2-7-dev \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy requirements and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Configure Git to trust the workspace
RUN git config --global --add safe.directory /app
# Copy source code including .gemini tools
COPY . .
# Run the build script
RUN python .gemini/build_site.py
# --- Runner Stage ---
FROM nginx:alpine
# Copy static site from builder
COPY --from=builder /app/_site_src/site /usr/share/nginx/html
# Copy Nginx config (optional, using default for now)
# COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

47
_backup_config/mkdocs.yml Normal file
View File

@ -0,0 +1,47 @@
site_name: Base de Conhecimento - iT Guys
site_url: https://docs.itguys.com.br
copyright: "© 2026 iT Guys - Todos os direitos reservados"
theme:
name: material
logo: assets/logo.png
favicon: assets/logo.png
language: pt-BR
features:
- navigation.instant
- navigation.tracking
- navigation.expand
- navigation.top
- search.suggest
- search.highlight
- content.code.copy
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
primary: blue
accent: cyan
toggle:
icon: material/brightness-7
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: blue
accent: cyan
toggle:
icon: material/brightness-4
name: Switch to light mode
hooks:
- .gemini/hooks.py
plugins:
- search
- awesome-pages
- glightbox
- git-revision-date-localized:
type: datetime
fallback_to_build_date: true
enable_creation_date: true
extra_css:
- stylesheets/extra.css

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,85 @@
# MANUAL TÉCNICO - VERIFICAÇÃO DIÁRIA DE JOBS DE VM
**Código:** ITGSUP 0014/26 | **Classificação:** INTERNO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Padronizar a rotina diária de verificação dos backups de infraestrutura virtual (Hyper-V/VMware) garantindo a detectabilidade imediata de falhas.
## 3. PRÉ-REQUISITOS
> O que é necessário para executar:
* [ ] Acesso ao console do **Veeam Backup & Replication** (Conta de Operador de Backup ou Admin).
* [ ] Planilha ou sistema de checklist diário aberto.
## 4. PASSO A PASSO (EXECUÇÃO)
**Etapa 1: Acesso ao Console Principal**
1. Faça login no servidor de backup (RDP ou Console Remoto).
2. Abra o **Veeam Backup & Replication Console**.
3. No menu inferior esquerdo, clique em **Home**.
4. Na árvore de navegação à esquerda, selecione **Jobs** > **Backup**.
!!! note "Nota"
Esta visão consolida todos os jobs configurados (VMs, Agentes Físicos, Shares).
**Etapa 2: Análise do Status "Last Result"**
1. Observe a coluna **Status** e **Last Result** na lista central.
2. Identifique os jobs com status diferente de **Success**.
!!! warning "LEGENDA DE CORES"
* 🟢 **Success:** Backup completado sem erros.
* 🟡 **Warning:** Backup completado, mas com avisos (ex: falha ao indexar guest OS, snapshot demorado).
* 🔴 **Failed:** O backup FALHOU. VM não protegida nesta rodada.
![Veeam Job List Status](assets/veeam_jobs_list.png)
**Etapa 3: Investigação de Falhas (Drill-down)**
1. Dê um **duplo-clique** sobre o job que apresentou falha ou warning.
2. Na janela de sessão que abrir, observe a lista de VMs à esquerda.
3. Clique na VM que está com ícone vermelho ou amarelo.
4. No painel da direita, leia o log de execução, buscando linhas em **vermelho**.
!!! tip "Dica"
Erros comuns incluem "VSS Writer failed", "RPC server unavailable" ou "Snapshot creation failed".
![Veeam Job Session Log](assets/veeam_job_log.png)
**Etapa 4: Registro de Incidente**
1. Se houver falha (🔴), abra imediatamente um ticket no Zammad para a equipe de Nível 2 (Infra).
2. No corpo do ticket, cole o erro exato encontrado no log (passo anterior).
3. Se for apenas Warning (🟡), registre na observação do checklist, mas não requer ticket urgente se houver pontos de restauração anteriores válidos recentes.
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
**Problema 1: Job parado em "Running" há mais de 24h**
* **Causa:** Processo travado ou snapshot preso no hipervisor.
* **Solução:**
1. Tente parar o job com **Right-Click > Stop**.
2. Se não parar, verifique no vCenter/Hyper-V se há snapshots pendentes na VM e remova-os (Consolidate).
3. Reinicie os serviços do Veeam se necessário (escalonar para Nível 2).
**Problema 2: Erro "RPC Server Unavailable"**
* **Causa:** Falha de comunicação de rede ou firewall entre Veeam e VM Guest.
* **Solução:**
1. Teste ping do servidor Veeam para a VM.
2. Verifique se o serviço "Admin Share" (C$) está acessível.
## 6. DADOS TÉCNICOS
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Portas** | 9392, 10001 | Portas de Console e Serviço Veeam |
| **Logs** | `C:\ProgramData\Veeam\Backup` | Caminho de logs detalhados |
| **SLA** | 24 Horas | RPO Padrão (janela diária) |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
> O procedimento termina quando:
- [ ] Todos os jobs foram verificados visualmente.
- [ ] Falhas críticas (Failed) foram escalonadas via Ticket.
- [ ] Checklist diário foi preenchido com "Ok" ou "Incidente nº XXX".

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
# MANUAL TÉCNICO - RESTAURAÇÃO DE ARQUIVOS GUEST (WINDOWS/LINUX)
**Código:** ITGINF 0022/26 | **Classificação:** INTERNO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Executar a recuperação granular de arquivos e pastas individuais diretamente de um backup, sem a necessidade de restaurar a VM inteira.
## 3. PRÉ-REQUISITOS
* [ ] Acesso ao console do Veeam Backup & Replication.
* [ ] Credenciais administrativas da VM de origem (Guest OS) se necessário.
* [ ] Espaço em disco temporário no servidor Veeam (Mount Cache).
## 4. PASSO A PASSO (EXECUÇÃO)
**Etapa 1: Iniciar o Assistente de Restore**
1. No console Veeam, vá para **Home** > **Backups** > **Disk**.
2. Localize o job e expandan-o para ver as VMs.
3. Clique com o botão direito na VM desejada e selecione **Restore guest files** > **Microsoft Windows** (ou Linux).
!!! note "Nota"
Para Linux, o Veeam usará um "Helper Appliance" temporário se não conseguir montar diretamente.
![Veeam Restore Guest Files Menu](assets/veeam_restore_menu.png)
**Etapa 2: Seleção do Ponto de Restauração**
1. Na janela de wizard, selecione o Restore Point desejado.
2. Clique em **Next**.
3. (Opcional) Digite o motivo do restore ("Reason").
4. Clique em **Browse**.
!!! note "Aguarde"
O Veeam montará o backup como um disco virtual no servidor. Isso pode levar de 1 a 3 minutos.
**Etapa 3: "Veeam Backup Browser" (Explorador)**
1. Uma nova janela abrirá exibindo a estrutura de arquivos da VM (C:\, D:\, etc).
2. Navegue até a pasta onde os arquivos perdidos estavam.
![Veeam Backup Browser](assets/veeam_browser.png)
**Etapa 4: Executar a Restauração**
1. Selecione os arquivos ou pastas desejados.
2. Clique com o botão direito para ver as opções:
* **Restore > Overwrite:** Restaura para o local original, substituindo o atual.
* **Restore > Keep:** Restaura para o local original, renomeando o arquivo restaurado (ex: `File_RESTORED.txt`).
* **Copy To:** Salva em uma pasta local do seu PC ou rede (ex: `C:\Temp`).
!!! tip "Recomendação"
Use **Copy To** ou **Keep** para evitar sobrescrever dados válidos acidentalmente.
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
**Problema 1: Erro "Access Denied" ao restaurar para o local original**
* **Causa:** O usuário do Veeam não tem permissão na pasta da VM Guest.
* **Solução:**
1. No Backup Browser, clique em "Mount (ou Explorer)" no topo para abrir opções avançadas.
2. Tente usar a opção **Copy To** para uma pasta pública (`C:\Temp` da VM) ou para o próprio servidor de backup.
**Problema 2: Lentidão extrema ao abrir pastas (Linux)**
* **Causa:** O *Helper Appliance* pode estar em host/datastore lento ou rede congestionada.
* **Solução:**
1. Aguarde pacientemente. O FLR (File Level Restore) monta blocos sob demanda.
2. Se falhar, use o *Instant VM Recovery* (desligado da rede) para montar a VM inteira e copiar os dados.
## 6. DADOS TÉCNICOS
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Mount Path** | `C:\VeeamFLR` | Local onde o disco é montado no Veeam Server |
| **Portas Linux** | 22 (SSH) | Necessário para Helper Appliance |
| **Limite FLR** | N/A | Depende apenas da rede e IOPS do repositório |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
- [ ] O arquivo solicitado foi recuperado íntegro?
- [ ] O Veeam Backup Browser foi fechado? (Crucial para desmontar o backup e liberar o arquivo de backup).

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
# MANUAL TÉCNICO - INSTANT VM RECOVERY (RESTAURAÇÃO RÁPIDA)
**Código:** ITGINF 0023/26 | **Classificação:** INTERNO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Recuperar o funcionamento de uma VM crítica em minutos (RTO < 2 min), executando-a diretamente a partir do arquivo de backup antes de mover os dados para o storage de produção.
## 3. PRÉ-REQUISITOS
* [ ] Servidor ESXi de destino ativo e acessível pelo Veeam Server.
* [ ] Espaço livre na reserva de RAM do Host ESXi.
* [ ] Porta NFS (111, 2049) liberada entre Veeam e ESXi.
## 4. PASSO A PASSO (EXECUÇÃO)
**Etapa 1: Iniciar o Instant Recovery**
1. No console Veeam, navegue até **Home** > **Backups** > **Disk**.
2. Clique com o botão direito na VM afetada.
3. Escolha **Instant recovery** > **VMware vSphere** (ou Hyper-V).
![Menu Instant Recovery](assets/veeam_instant_recovery_menu.png)
**Etapa 2: Definição do Modo de Restore**
1. Escolha o **Restore Point** mais recente (ou o último conhecido bom).
2. Em "Restore Mode":
* **Restore to original location:** Sobrescreve a VM original (cuidado!).
* **Restore to a new location...:** Cria uma cópia (Recomendado para testes ou se a original ainda existe para análise forense).
!!! warning "Importante"
Se restaurar para o original, marque "Power on VM automatically".
**Etapa 3: Validação do Boot**
1. O Veeam montará o datastore vPower NFS e ligará a VM.
2. Acesse o console do vCenter/Hyper-V e verifique se a VM iniciou.
3. Teste o acesso aos serviços (Ping, RDP, Web).
!!! note "Nota"
Nesta fase, a performance será reduzida pois o disco está lendo via rede do servidor de backup.
**Etapa 4: Finalização (Migrate to Production)**
!!! tip "Dica"
O processo NÃO acabou. A VM está rodando temporariamente no backup. Você DEVE migrá-la.
1. No console Veeam, vá para a view **Home** > **Instant Recovery** (menu inferior esquerdo, canto inferior da árvore).
2. Clique com o botão direito na sessão ativa da VM.
3. Selecione **Migrate to production**.
4. O *Quick Migration Wizard* abrirá. Siga o wizard para mover os discos para o Datastore definitivo.
![Migrate to Production](assets/veeam_migrate_production.png)
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
**Problema 1: Erro "Failed to mount NFS Datastore"**
* **Causa:** Bloqueio de Firewall ou serviço vPower NFS parado no Veeam.
* **Solução:**
1. Verifique se o serviço `Veeam vPower NFS Service` está rodando no servidor de backup.
2. Check se o Host ESXi consegue pingar o IP do Veeam Server.
**Problema 2: Performance extremamente degradada após o boot**
* **Causa:** Gargalo na rede ou disco do repositório de backup.
* **Solução:**
1. É esperado. Inicie o "Migrate to production" IMEDIATAMENTE para mover para o Storage rápido (NVMe/SSD).
2. Se tiver vMotion de Storage licença (Enterprise Plus), o Veeam usará automaticamente para migrar sem desligar.
## 6. DADOS TÉCNICOS
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Protocolo** | NFS v3 | Usado para montar os discos no ESXi |
| **Datastore** | VeeamBackup_Host | Nome do datastore temporário criado no vCenter |
| **Write Cache** | Local (Veeam) | As alterações feitas na VM ficam em cache no servidor Veeam até a migração |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
- [ ] A VM está rodando a partir do Datastore de Produção (não mais do NFS)?
- [ ] A sessão de "Instant Recovery" sumiu do console do Veeam?
- [ ] O backup da noite seguinte rodou com sucesso (incremental)?

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
# MANUAL TÉCNICO - CONFIGURAÇÃO DE REPOSITÓRIOS IMUTÁVEIS (HARDENED LINUX)
**Código:** ITGENG 0021/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Implementar proteção contra ransomware tornando os backups imutáveis (WORM) por um período definido, utilizando um servidor Linux "Hardened" sem credenciais persistentes.
## 3. PRÉ-REQUISITOS
* [ ] Servidor Físico ou Virtual com Linux moderno (Ubuntu 20.04+ / RHEL 8+).
* [ ] Disco formatado em XFS com Reflink habilitado (`mkfs.xfs -m reflink=1,crc=1`).
* [ ] Conta de usuário **não-root** no Linux com permissão sudo temporária.
## 4. PASSO A PASSO (EXECUÇÃO)
**Etapa 1: Preparação do Linux (Shell)**
1. Garanta que o diretório do repositório pertence ao usuário do Veeam:
```bash
chown veeamuser:veeamuser /mnt/backup-repo
chmod 700 /mnt/backup-repo
```
**Etapa 2: Adicionar Servidor com "Single-Use Credentials"**
!!! warning "Crítico"
Nunca salve a senha do root/sudo no Veeam. Use credenciais de uso único para que, se o Veeam Server for hackeado, o hacker não consiga acessar o Linux.
1. No console Veeam, vá em **Backup Infrastructure** > **Managed Servers** > **Add Server** > **Linux**.
2. Digite o IP/DNS do servidor Linux.
3. Em **Credentials**, clique em **Add** e selecione **Single-use credentials for hardened repository**.
4. Insira o usuário e senha (com sudo temporário).
5. Finalize o wizard. O Veeam instalará os serviços e certificados.
6. **Segurança:** Após adicionar, remova o usuário do grupo `sudo` no Linux.
![Single Use Credentials](assets/veeam_single_use_creds.png)
**Etapa 3: Criar o Repositório Imutável**
1. Vá em **Backup Repositories** > **Add Repository** > **Direct attached storage** > **Linux**.
2. Selecione o servidor Linux adicionado.
3. Clique em **Populate** e escolha o caminho mountpoint XFS (`/mnt/backup-repo`).
4. Na tela de configurações do repositório, marque **OBRIGATORIAMENTE**:
* [x] **Use fast cloning on XFS volumes** (Economia de espaço massiva).
* [x] **Make recent backups immutable for:** `7` days (Mínimo recomendado).
![Immutable Settings](assets/veeam_immutable_checkbox.png)
**Etapa 4: Aplicação**
1. Finalize o wizard e aplique as configurações.
2. Crie ou edite um Backup Job para apontar para este novo repositório.
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
**Problema 1: "Fast Clone is not supported"**
* **Causa:** O sistema de arquivos não foi formatado com `reflink=1`.
* **Solução:** É necessário reformatar a partição XFS corretamente (destrutivo para dados).
**Problema 2: Falha ao adicionar servidor (SSH handshake fail)**
* **Causa:** O usuário single-use não tem permissão de escrita ou sudo falhou.
* **Solução:** Verifique se o usuário tem permissão `chmod 700` na pasta home e no diretório de destino.
## 6. DADOS TÉCNICOS
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Filesystem** | XFS (Reflink) | Obrigatório para Fast Clone |
| **Porta** | 6162 | Veeam Data Mover (TCP) |
| **Imutabilidade** | `chattr +i` | Flag de sistema usada para bloquear arquivos |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
- [ ] Tente deletar manualmente um arquivo de backup (`.vbk`) via console. O Veeam deve retornar erro "Access Denied" ou "Immutable".
- [ ] O ícone do Job no Veeam possui um "escudo" ou indicativo de proteção?

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,77 @@
# MANUAL TÉCNICO - CRIAÇÃO DE ROTINAS DE TESTE DE RESTORE (SUREBACKUP)
**Código:** ITGENG 0022/26 | **Classificação:** INTERNO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Automatizar a validação de backups ligando as VMs em um ambiente isolado (Virtual Lab) e testando os serviços (Ping, DNS, SQL) para garantir 100% de recuperabilidade.
## 3. PRÉ-REQUISITOS
* [ ] Licença Veeam Enterprise ou Enterprise Plus (ou VUL).
* [ ] Host ESXi com recursos sobrando (RAM/CPU) para ligar o Lab.
* [ ] DHCP habilitado na rede de produção ou IPs estáticos conhecidos.
## 4. PASSO A PASSO (EXECUÇÃO)
**Etapa 1: Criação do Virtual Lab**
1. Vá em **Backup Infrastructure** > **SureBackup Infrastructure** > **Virtual Labs** > **Add Virtual Lab**.
2. Escolha o Host ESXi.
3. **Networking:**
* **Basic Single-host:** Mais simples. O Veeam cria uma rede isolada mascarada.
* **Advanced:** Permite mapear múltiplas VLANs (Recomendado se o App depende de várias redes).
4. Configure o **Proxy Appliance** (Gateway entre Prod e Lab). Defina um IP livre na rede de produção.
5. Finalize o wizard.
![Virtual Lab Config](assets/veeam_virtual_lab.png)
**Etapa 2: Application Group (Dependências)**
!!! note "Importante"
Defina aqui as VMs que DEVEM ligar primeiro (ex: Domain Controller, DNS).
1. Vá em **Application Groups** > **Add Group**.
2. Adicione as VMs críticas (ex: SRV-DC01, SRV-DB01).
3. **Edit Startup Options:**
* **Memory:** % de RAM garantida.
* **Startup Time:** Tempo de espera para boot (Aumente para 600s ou mais para DCs lentos).
* **Test Scripts:** Habilite "DNS Test", "SQL Server Test", etc.
**Etapa 3: SureBackup Job**
1. Vá em **Home** > **Jobs** > **SureBackup Job**.
2. Selecione o **Virtual Lab** e o **Application Group** criados.
3. (Opcional) **Linked Jobs:** Adicione jobs de backup inteiros para testar TODAS as VMs daquele job sequencialmente.
4. **Schedule:** Agende para rodar fora do horário de backup (ex: Domingos às 08:00).
![SureBackup Job](assets/veeam_surebackup_job.png)
## 5. SOLUÇÃO DE PROBLEMAS (TROUBLESHOOTING)
**Problema 1: Erro "Masquerade IP address is not available"**
* **Causa:** Conflito de IP na rede de produção.
* **Solução:** No Virtual Lab settings, mude o prefixo de masquerade para uma faixa não usada (ex: 192.168.250.x).
**Problema 2: VM falha no teste de Ping**
* **Causa:** Firewall do Windows dentro da VM bloqueando ICMP ou VM demorou para ligar.
* **Solução:**
1. Aumente o "Maximum allowed boot time" no Application Group.
2. Garanta que o Firewall da VM permita "File and Printer Sharing (Echo Request)".
## 6. DADOS TÉCNICOS
| Campo | Valor | Descrição |
| :--- | :--- | :--- |
| **Proxy Appliance** | Linux (Tiny) | Roteador virtual criado pelo Veeam |
| **vSwitch** | vSwitch isolado | Criado automaticamente no ESXi (sem uplink físico) |
| **Relatório** | HTML/Email | Enviado ao fim do job com status de cada VM |
## 7. VALIDAÇÃO FINAL (Definição de Pronto)
- [ ] O Job SureBackup rodou com status "Success"?
- [ ] O relatório mostra "Heartbeat: OK, Ping: OK, Scripts: OK" para as VMs críticas?
- [ ] O Virtual Lab foi desligado automaticamente após o teste?

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 520 KiB

After

Width:  |  Height:  |  Size: 520 KiB

View File

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 374 KiB

View File

Before

Width:  |  Height:  |  Size: 503 KiB

After

Width:  |  Height:  |  Size: 503 KiB

View File

Before

Width:  |  Height:  |  Size: 776 KiB

After

Width:  |  Height:  |  Size: 776 KiB

View File

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 439 KiB

View File

Before

Width:  |  Height:  |  Size: 606 KiB

After

Width:  |  Height:  |  Size: 606 KiB

View File

Before

Width:  |  Height:  |  Size: 531 KiB

After

Width:  |  Height:  |  Size: 531 KiB

View File

Before

Width:  |  Height:  |  Size: 316 KiB

After

Width:  |  Height:  |  Size: 316 KiB

View File

Before

Width:  |  Height:  |  Size: 496 KiB

After

Width:  |  Height:  |  Size: 496 KiB

View File

Before

Width:  |  Height:  |  Size: 486 KiB

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

View File

@ -0,0 +1,101 @@
# MANUAL TÉCNICO - DIAGNÓSTICO DE CONECTIVIDADE DE BANCOS DE DADOS
**Código:** ITGSUP 0020/26 | **Classificação:** INTERNO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Padronizar o diagnóstico inicial de incidentes de "banco fora do ar" ou "erro de conexão" para PostgreSQL, MySQL e Redis.
## 3. PRÉ-REQUISITOS
* [ ] Acesso ao terminal do servidor.
* [ ] Saber qual banco está sendo diagnosticado (PG, MySQL ou Redis).
---
## 4. VERIFICAÇÃO DE STATUS DO SERVIÇO
O primeiro passo é saber se o processo está rodando.
**PostgreSQL:**
```bash
sudo systemctl status postgresql
```
* **Ativo:** `Active: active (exited)` ou `(running)`.
* **Porta Padrão:** 5432.
**MySQL / MariaDB:**
```bash
sudo systemctl status mysql
# ou
sudo systemctl status mariadb
```
* **Porta Padrão:** 3306.
**Redis:**
```bash
sudo systemctl status redis-server
# ou
sudo systemctl status redis
```
* **Porta Padrão:** 6379.
---
## 5. TESTE DE CONEXÃO LOCAL (PING)
Se o serviço roda, ele responde?
### Redis (Mais Simples)
Use o `redis-cli` para enviar um PING.
```bash
redis-cli ping
```
* **Sucesso:** Retorna `PONG`.
* **Falha:** `Could not connect...` (Serviço parado) ou `NOAUTH` (Precisa de senha: `redis-cli -a SENHA ping`).
### PostgreSQL
Tente logar como o usuário `postgres`.
```bash
sudo -u postgres psql -c "\conninfo"
```
* **Sucesso:** Mostra dados da conexão (Socket, Port).
* **Falha:** `Is the server running locally...`
### MySQL
Teste o login (pode exigir senha de root).
```bash
sudo mysqladmin -u root -p ping
```
* **Sucesso:** `mysqld is alive`.
---
## 6. DIAGNÓSTICO DE REDE (ACESSO EXTERNO)
Se local funciona, mas a aplicação não conecta, verifique a rede.
**1. A Porta está aberta? (`ss` ou `netstat`)**
```bash
sudo ss -tuln | grep 5432 # (Exemplo PG)
```
* `127.0.0.1:5432` -> **ERRO:** Só aceita conexão local (localhost).
* `0.0.0.0:5432` -> **OK:** Aceita conexões de qualquer IP.
**2. Teste de Telnet (Da estação do usuário/app)**
No computador que está com erro, tente:
```bash
telnet IP_DO_SERVIDOR 5432
```
* **Connected:** Rede OK. Problema é senha/usuario.
* **Timeout:** Firewall bloqueando ou IP errado.
## 7. VALIDAÇÃO FINAL
- [ ] O serviço está "Active (Running)"?
- [ ] O teste local (Ping/Psql) funcionou?
- [ ] A porta está escutando em `0.0.0.0` (se precisar de acesso externo)?

View File

@ -0,0 +1,108 @@
# MANUAL TÉCNICO - BACKUP E RESTORE MANUAL (DUMP)
**Código:** ITGSUP 0021/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Executar backups pontuais (Dumps) e restaurações de emergência em bancos de dados, incluindo a exportação segura para servidores remotos (Storage/NFS).
## 3. PRÉ-REQUISITOS
* [ ] Espaço em disco suficiente para o dump.
* [ ] Senhas de administração dos bancos.
---
## 4. POSTGRESQL (pg_dump)
### Backup (Exportar)
```bash
# Backup de UM banco
pg_dump -U usuario_admin -h localhost nome_do_banco > backup.sql
# Backup de TODOS os bancos (Cluster)
pg_dumpall -U usuario_admin > backup_full.sql
```
### Restore (Importar)
```bash
# Se o banco ja existe (Sobrescreve estruturas)
psql -U usuario_admin -d nome_do_banco < backup.sql
# Se foi gerado com pg_dumpall
psql -U usuario_admin -f backup_full.sql postgres
```
---
## 5. MYSQL / MARIADB (mysqldump)
### Backup (Exportar)
```bash
# Backup de UM banco
mysqldump -u root -p nome_do_banco > backup.sql
# Backup de VÁRIOS bancos
mysqldump -u root -p --databases db1 db2 > backup_db1_db2.sql
```
### Restore (Importar)
```bash
# O banco DEVE existir antes (mysqladmin create nome_do_banco)
mysql -u root -p nome_do_banco < backup.sql
```
---
## 6. REDIS (RDB Snapshot)
O Redis trabalha em memória, mas salva arquivos `.rdb`.
### Backup (Snapshot Force)
1. Acesse o CLI: `redis-cli`
2. Execute: `BGSAVE` (Salva em background).
3. Verifique o diretório de dados (geralmente `/var/lib/redis/dump.rdb`).
4. Copie o arquivo `dump.rdb` para local seguro.
### Restore
1. Pare o serviço: `systemctl stop redis`
2. Substitua o arquivo `dump.rdb` pelo seu backup.
3. Garanta as permissões: `chown redis:redis dump.rdb`
4. Inicie o serviço: `systemctl start redis`
---
## 7. EXPORTAÇÃO REMOTA (OFF-SITE)
> ⚠️ **IMPORTANTE:** Nunca deixe o backup apenas no próprio servidor. Mova-o.
### Opção A: Cópia via SCP (Windows/Linux Seguro)
Envia o arquivo para outro servidor via SSH.
```bash
scp backup.sql usuario@192.168.1.50:/home/usuario/backups/
```
### Opção B: Montagem NFS (Storage Linux)
Se você tem um storage montado em `/mnt/backup`:
```bash
# Gera direto na pasta remota
pg_dump -U postgres meu_banco > /mnt/backup/pg_$(date +%F).sql
```
### Opção C: Montagem SMB/CIFS (Windows Share)
Para enviar para uma pasta do Windows Server.
1. Instale: `apt install cifs-utils`
2. Monte:
```bash
mount -t cifs -o username=user,password=pass //servidor/share /mnt/windows
```
3. Copie: `cp backup.sql /mnt/windows/`
## 8. VALIDAÇÃO FINAL
- [ ] O arquivo `.sql` foi gerado e tem tamanho maior que 0?
- [ ] O arquivo está salvo em local externo (SCP/NFS)?

View File

@ -0,0 +1,91 @@
# MANUAL TÉCNICO - GESTÃO DE USUÁRIOS E PERMISSÕES DE BANCO
**Código:** ITGINF 0021/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Padronizar a criação de credenciais, rotação de senhas e a concessão de privilégios mínimos (Least Privilege) em bancos de dados.
---
## 3. POSTGRESQL
Acesse como admin: `sudo -u postgres psql`
### Criar Usuário (Role)
```sql
-- Criar usuario com senha
CREATE USER app_user WITH PASSWORD 'SenhaForte123';
-- Criar um banco para ele
CREATE DATABASE app_db OWNER app_user;
```
### Permissões (Grant)
```sql
-- Conectar no banco especifico
\c app_db
-- Dar acesso a todas as tabelas (SCHEMA public)
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_user;
```
---
## 4. MYSQL / MARIADB
Acesse como admin: `mysql -u root -p`
### Criar Usuário (Definindo Origem)
No MySQL, o usuário é atrelado ao IP de origem (`@'%'` = qualquer lugar, `@'localhost'` = local).
```sql
CREATE USER 'app_user'@'%' IDENTIFIED BY 'SenhaForte123';
```
### Permissões (Grant)
```sql
-- Dar acesso total a UM banco especifico
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'%';
-- Aplicar mudancas
FLUSH PRIVILEGES;
```
---
## 5. REDIS
### Redis Antigo ( < 6.0 )
Só existe UMA senha global definida no `redis.conf` (`requirepass`). Não há usuários.
### Redis Moderno (ACLs - Access Control Lists)
Acesse: `redis-cli` (com a senha de admin/default).
```bash
# Criar usuario 'app_user'
# on > Habilitado
# >Senha... > Define senha
# ~app:* > Só acessa chaves que comessem com 'app:'
# +@all > Pode rodar todos os comandos (Cuidado!)
ACL SETUSER app_user on >SenhaForte123 ~app:* +get +set
```
### Verificar Permissões
```bash
ACL LIST
ACL WHOAMI
```
## 6. VALIDAÇÃO FINAL
- [ ] O usuário consegue logar remotamente?
- [ ] O usuário consegue escrever na tabela/chave permitida?
- [ ] O usuário é BLOQUEADO ao tentar acessar outro banco/tabela (Teste de negação)?

View File

@ -0,0 +1,80 @@
# MANUAL TÉCNICO - INSTALAÇÃO E CONFIGURAÇÃO BASE DE BANCOS DE DADOS
**Código:** ITGINF 0020/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Padronizar a instalação, caminhos de configuração e liberação de acesso externo para PostgreSQL, MySQL e Redis em servidores Linux.
## 3. PRÉ-REQUISITOS
* [ ] Servidor Linux (Debian/RHEL) atualizado.
* [ ] Acesso root/sudo.
---
## 4. POSTGRESQL
**Instalação:**
* **Debian/Ubuntu:** `sudo apt install postgresql postgresql-contrib`
* **RHEL/CentOS:** `sudo dnf install postgresql-server postgresql-contrib && sudo postgresql-setup --initdb`
**Arquivos de Configuração (Padrão Debian):**
* **Principal:** `/etc/postgresql/14/main/postgresql.conf` (Versão varia).
* **Acesso (Quem conecta):** `/etc/postgresql/14/main/pg_hba.conf`
**Liberar Acesso Externo:**
1. No `postgresql.conf`, mude `listen_addresses = 'localhost'` para `listen_addresses = '*'`.
2. No `pg_hba.conf`, adicione no final:
```
# TYPE DATABASE USER ADDRESS METHOD
host all all 0.0.0.0/0 scram-sha-256
```
3. Reinicie: `sudo systemctl restart postgresql`.
---
## 5. MYSQL / MARIADB
**Instalação:**
* **Debian/Ubuntu:** `sudo apt install mariadb-server` (ou `mysql-server`).
* **RHEL/CentOS:** `sudo dnf install mariadb-server`
**Arquivos de Configuração:**
* **Principal:** `/etc/mysql/mysql.conf.d/mysqld.cnf` (ou `/etc/my.cnf`).
**Liberar Acesso Externo:**
1. Edite o arquivo de config.
2. Localize `bind-address` e mude de `127.0.0.1` para `0.0.0.0`.
3. Reinicie: `sudo systemctl restart mariadb`.
**Secure Installation (Obrigatório):**
Rode `sudo mysql_secure_installation` logo após instalar para definir senha de root e remover usuários anônimos.
---
## 6. REDIS
**Instalação:**
* **Todas as distros:** `sudo apt install redis-server` (ou `dnf install redis`).
**Arquivos de Configuração:**
* **Principal:** `/etc/redis/redis.conf`.
**Liberar Acesso Externo (CUIDADO):**
> ⚠️ **ALERTA:** O Redis não tem autenticação forte por padrão. NUNCA exponha para a internet.
1. Edite `/etc/redis/redis.conf`.
2. Mude `bind 127.0.0.1 ::1` para `bind 0.0.0.0`.
3. **OBRIGATÓRIO:** Defina uma senha na diretiva `requirepass SUA_SENHA_FORTE`.
4. Reinicie: `sudo systemctl restart redis`.
## 7. VALIDAÇÃO FINAL
- [ ] O serviço está rodando e habilitado no boot (`systemctl enable`)?
- [ ] O acesso externo funciona (se configurado)?
- [ ] (Redis) A senha foi configurada antes de abrir a rede?

View File

@ -0,0 +1,92 @@
# MANUAL TÉCNICO - MANUTENÇÃO PREVENTIVA E LOGS DE BANCO
**Código:** ITGINF 0022/26 | **Classificação:** RESTRITO
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Descrever as rotinas de limpeza de espaço (Vacuum/Optimize), desfragmentação e verificação de logs para evitar paradas por disco cheio ou corrupção.
---
## 3. POSTGRESQL (VACUUM)
O Postgres usa MVCC, gerando "tuplas mortas" quando dados são deletados/alterados.
### Manutenção Automática (Autovacuum)
Geralmente já vem ativo. Verifique:
```sql
SHOW autovacuum; -- Deve ser 'on'
```
### Manutenção Manual (Recuperar Espaço)
Se o banco inchou muito após um DELETÃO em massa.
1. Acesse o `psql`.
2. Rode em horário de baixo pico (Pode travar mesas se usar FULL):
```sql
-- Limpa e otimiza estatisticas (Rapido)
VACUUM ANALYZE;
-- (PERIGOSO) Recria o arquivo do disco para liberar espaço físico. Bloqueia escrita!
VACUUM FULL;
```
---
## 4. MYSQL / MARIADB (OPTIMIZE)
Tabelas InnoDB podem ficar fragmentadas.
### Otimizar Tabelas
```sql
-- Reconstrói a tabela e indices (Bloqueia a tabela durante a execução!)
OPTIMIZE TABLE nome_da_tabela;
```
### Logs Binários (Binlog)
Cuidado: Eles podem encher o disco.
1. Verifique a expiração no `my.cnf`:
```ini
binlog_expire_logs_seconds = 604800 # 7 Dias
```
2. Limpar manualmente (PURGE):
```sql
PURGE BINARY LOGS BEFORE '2023-10-01 00:00:00';
```
---
## 5. REDIS (Memory Defrag)
O Redis pode alocar memória fragmentada.
1. Verifique a fragmentação: `INFO MEMORY`
* `mem_fragmentation_ratio > 1.5` indica fragmentação alta.
2. Ativar Desfragmentação Ativa (Config):
```bash
CONFIG SET activedefrag yes
```
---
## 6. ROTAÇÃO DE LOGS (Logrotate)
Garanta que os logs de erro (`/var/log/mysql`, `/var/log/postgresql`) não estão gigantes.
Verifique se existem arquivos em `/etc/logrotate.d/`:
* `mysql-server`
* `postgresql-common`
Force uma verificação se achar que não está rodando:
```bash
sudo logrotate -f /etc/logrotate.d/mysql-server
```
## 7. VALIDAÇÃO FINAL
- [ ] PostgreSQL: `VACUUM` rodou sem erro?
- [ ] MySQL: Disco liberado após Purge de Binlogs?
- [ ] Logs antigos estão compactados (.gz)?

View File

@ -0,0 +1,68 @@
# MANUAL TÉCNICO - ARQUITETURA DE PERSISTÊNCIA E TROUBLESHOOTING AVANÇADO
**Código:** ITGENG 0021/26 | **Classificação:** CONFIDENCIAL
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Definir políticas de persistência de dados (Trade-off Performance vs Segurança) e procedimentos de recuperação de desastres (Crash Recovery e OOM).
---
## 3. ESTRATÉGIAS DE PERSISTÊNCIA REDIS
O Redis pode ser apenas Cache (volátil) ou Banco (persistente).
| Modo | Descrição | Prós | Contras |
| :--- | :--- | :--- | :--- |
| **RDB (Snapshot)** | Salva o banco a cada X minutos. | Rápido restore, arquivo compacto. | Perde os dados desde o último snap. |
| **AOF (Append Only)** | Salva cada comando escrito. | Perda mínima (1s), Seguro. | Arquivo cresce muito, restore lento. |
| **Híbrido** | RDB + AOF. | Segurança do AOF + Rapidez do RDB. | Mais I/O de disco. |
**Recomendação iT Guys:** Use **Híbrido** para bancos críticos e **Apenas RDB** para Cache.
---
## 4. PERSISTÊNCIA SQL (WAL / BINLOG)
Se o servidor desligar da tomada, os dados no WAL/Binlog salvam o dia.
**PostgreSQL (WAL):**
* `fsync = on`: **OBRIGATÓRIO**. Garante que o dado foi pro disco físico. Desativar dá velocidade mas corrompe o banco em crash.
* `synchronous_commit`: Pode ser `off` se você aceita perder alguns milissegundos em troca de performance.
---
## 5. CENÁRIOS DE CRASH E SOLUÇÕES
### Cenário 1: OOM Killer (Out Of Memory)
O Linux mata o banco porque acabou a RAM.
* **Sintoma:** Log do sistema diz `Killed process 1234 (postgres)`.
* **Correção Imediata:** Aumente o Swap ou reduza os Buffers do banco.
* **Prevenção:** Ajuste o `oom_score_adj` no Systemd para -1000 (Imune) - *Use com cautela extrema*.
### Cenário 2: Arquivo Corrompido (.pid lock)
Após um crash elétrico, o serviço não sobe dizendo que "já está rodando".
* **Solução:** Verifique se o processo existe (`ps aux`). Se não existir, delete o arquivo `postmaster.pid` (PG) ou `mysql.pid`.
### Cenário 3: Redis em modo Read-Only
O Redis parou de aceitar escrita.
* **Causa provável:** O disco encheu ou o RDB falhou (`stop-writes-on-bgsave-error yes`).
* **Solução:** Libere espaço em disco ou corrija a permissão da pasta de dados.
## 6. POLÍTICA DE DISASTER RECOVERY (DR)
Em caso de corrupção total:
1. Não tente consertar a produção corrompida.
2. Suba uma nova instância (VM limpa).
3. Importe o Último Dump/Backup (Nível 1).
4. Reaplique os logs de transação (Point-in-Time Recovery) se disponíveis (Requer setup prévio de Archiving).
## 7. VALIDAÇÃO FINAL
- [ ] A persistência (fsync/AOF) está ativa conforme a criticidade?
- [ ] O servidor tem Swap configurado para segurar picos antes do OOM?

View File

@ -0,0 +1,82 @@
# MANUAL TÉCNICO - TUNING E PERFORMANCE DE BANCOS DE DADOS
**Código:** ITGENG 0020/26 | **Classificação:** CONFIDENCIAL
**Responsável:** João Pedro Toledo Gonçalves | **Data:** {{DATA_ATUAL}}
## 1. HISTÓRICO DE REVISÃO
| Data | Versão | Descrição | Autor |
| :--- | :--- | :--- | :--- |
| {{DATA_ATUAL}} | 1.0 | Criação Inicial | João Pedro Toledo Gonçalves |
## 2. OBJETIVO
Guiar a identificação de gargalos (Slow Queries) e aplicar otimizações seguras (Universais) e agressivas (Tailored) para PostgreSQL, MySQL e Redis.
---
## 3. PREPARAÇÃO PROATIVA (MONITORAMENTO)
Não espere o problema acontecer. Ative isso AGORA.
1. **MySQL Slow Query Log:**
* No `my.cnf`: `slow_query_log = 1`, `long_query_time = 2` (segundos).
2. **Postgres pg_stat_statements:**
* Instale a extensão: `CREATE EXTENSION pg_stat_statements;`
* Veja as queries mais lentas: `SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;`
3. **Redis Latency Monitor:**
* Config: `CONFIG SET latency-monitor-threshold 100` (ms).
---
## 4. OTIMIZAÇÕES UNIVERSAIS (SEGURO PARA TODOS)
Alterações de Sistema Operacional (Linux) que beneficiam qualquer DB.
### 1. Swappiness (Evitar Swap)
Bancos de dados odeiam Swap. O padrão do Linux (60) é ruim.
* **Ação:** Mude para 1 ou 10.
* Arquivo `/etc/sysctl.conf`:
```ini
vm.swappiness = 10
```
### 2. Transparent Huge Pages (THP) - DESATIVAR
O THP causa picos de lag no Redis e MongoDB.
* **Ação:** Desative no boot (varie conforme distro, geralmente via GRUB ou Systemd service).
* Verifique: `cat /sys/kernel/mm/transparent_hugepage/enabled` (Deve estar `[never]`).
---
## 5. TUNING TAILORED (CENÁRIOS ESPECÍFICOS)
### Cenário A: Hardware com MUITA RAM (Cache Heavy)
O objetivo é manter o [Working Set] na RAM.
* **Postgres (`shared_buffers`):** Defina como 25% a 40% da RAM Total.
* **MySQL (`innodb_buffer_pool_size`):** Defina como 70-80% da RAM Total (Se o servidor for dedicado apenas ao DB).
### Cenário B: Aplicação Write-Heavy (Muita Escrita)
O gargalo é o disco (I/O).
* **Postgres:** Aumente `max_wal_size` (ex: 4GB) para reduzir checkpoints frequentes.
* **Redis:** Evite `AOF everysec` se puder tolerar perda de 1s. Use persistência mista (RDB + AOF).
---
## 6. DIAGNÓSTICO DE QUERIES (EXPLAIN)
O desenvolvedor diz que "o banco está lento". Prove que é a query.
1. Pegue a query lenta no log.
2. Rode com `EXPLAIN` (MySQL) ou `EXPLAIN ANALYZE` (Postgres).
```sql
EXPLAIN ANALYZE SELECT * FROM pedidos WHERE data < '2023-01-01';
```
3. **Analise:**
* **Seq Scan:** Leu a tabela inteira do inicio ao fim. **RUIM**. Falta índice.
* **Index Scan:** Usou o índice. **BOM**.
## 7. VALIDAÇÃO FINAL
- [ ] O Swappiness está baixo (1-10)?
- [ ] Slow Query Log está capturando consultas lentas?
- [ ] Índices foram criados para eliminar "Seq Scans" críticos?

Some files were not shown because too many files have changed in this diff Show More