raijin-server 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. raijin_server/__init__.py +1 -1
  2. raijin_server/cli.py +58 -4
  3. raijin_server/healthchecks.py +76 -0
  4. raijin_server/modules/__init__.py +11 -2
  5. raijin_server/modules/apokolips_demo.py +378 -0
  6. raijin_server/modules/bootstrap.py +65 -0
  7. raijin_server/modules/calico.py +93 -22
  8. raijin_server/modules/cert_manager.py +127 -0
  9. raijin_server/modules/full_install.py +54 -19
  10. raijin_server/modules/network.py +96 -2
  11. raijin_server/modules/observability_dashboards.py +233 -0
  12. raijin_server/modules/observability_ingress.py +218 -0
  13. raijin_server/modules/sanitize.py +142 -0
  14. raijin_server/modules/secrets.py +109 -0
  15. raijin_server/modules/ssh_hardening.py +128 -0
  16. raijin_server/modules/traefik.py +1 -1
  17. raijin_server/modules/vpn.py +68 -3
  18. raijin_server/scripts/__init__.py +1 -0
  19. raijin_server/scripts/checklist.sh +60 -0
  20. raijin_server/scripts/install.sh +134 -0
  21. raijin_server/scripts/log_size_metric.sh +31 -0
  22. raijin_server/scripts/pre-deploy-check.sh +183 -0
  23. raijin_server/utils.py +45 -12
  24. raijin_server/validators.py +5 -0
  25. raijin_server-0.2.0.dist-info/METADATA +407 -0
  26. raijin_server-0.2.0.dist-info/RECORD +44 -0
  27. raijin_server-0.1.0.dist-info/METADATA +0 -219
  28. raijin_server-0.1.0.dist-info/RECORD +0 -32
  29. {raijin_server-0.1.0.dist-info → raijin_server-0.2.0.dist-info}/WHEEL +0 -0
  30. {raijin_server-0.1.0.dist-info → raijin_server-0.2.0.dist-info}/entry_points.txt +0 -0
  31. {raijin_server-0.1.0.dist-info → raijin_server-0.2.0.dist-info}/licenses/LICENSE +0 -0
  32. {raijin_server-0.1.0.dist-info → raijin_server-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,128 @@
1
+ """Hardening de SSH com usuario dedicado e chaves publicas."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import pwd
7
+ from pathlib import Path
8
+
9
+ import typer
10
+
11
+ from raijin_server.utils import ExecutionContext, apt_install, require_root, run_cmd, write_file
12
+
13
+ SSHD_DROPIN = Path("/etc/ssh/sshd_config.d/99-raijin.conf")
14
+ FAIL2BAN_JAIL = Path("/etc/fail2ban/jail.d/raijin-sshd.conf")
15
+ AUTHORIZED_KEYS_TEMPLATE = "# gerenciado pelo raijin-server\n{key}\n"
16
+
17
+
18
+ def _user_exists(username: str) -> bool:
19
+ try:
20
+ pwd.getpwnam(username)
21
+ return True
22
+ except KeyError:
23
+ return False
24
+
25
+
26
+ def _ensure_user(username: str, ctx: ExecutionContext) -> None:
27
+ if _user_exists(username):
28
+ typer.echo(f"Usuario {username} ja existe, reutilizando...")
29
+ return
30
+
31
+ typer.echo(f"Criando usuario {username} sem senha...")
32
+ run_cmd(["useradd", "-m", "-s", "/bin/bash", username], ctx)
33
+ run_cmd(["passwd", "-l", username], ctx, check=False)
34
+
35
+
36
+ def _write_authorized_keys(username: str, content: str, ctx: ExecutionContext) -> None:
37
+ ssh_dir = Path("/home") / username / ".ssh"
38
+ auth_file = ssh_dir / "authorized_keys"
39
+
40
+ if ctx.dry_run:
41
+ typer.echo(f"[dry-run] escrever {auth_file} com chave publica")
42
+ return
43
+
44
+ ssh_dir.mkdir(parents=True, exist_ok=True)
45
+ os.chmod(ssh_dir, 0o700)
46
+ auth_file.write_text(AUTHORIZED_KEYS_TEMPLATE.format(key=content.strip()))
47
+ os.chmod(auth_file, 0o600)
48
+ run_cmd(["chown", "-R", f"{username}:{username}", str(ssh_dir)], ctx)
49
+
50
+
51
+ def _load_public_key(path_input: str) -> str:
52
+ path = Path(path_input).expanduser()
53
+ if path.exists():
54
+ return path.read_text().strip()
55
+ typer.echo("Arquivo nao encontrado. Cole a chave publica completa (ssh-ed25519...).")
56
+ key = typer.prompt("Chave publica", default="")
57
+ if not key:
58
+ raise typer.BadParameter("Nenhuma chave publica fornecida.")
59
+ return key.strip()
60
+
61
+
62
+ def run(ctx: ExecutionContext) -> None:
63
+ """Configura SSH seguro com usuario dedicado e chaves publicas."""
64
+ require_root(ctx)
65
+
66
+ typer.echo("Hardening de SSH em andamento...")
67
+ apt_install(["openssh-server", "fail2ban"], ctx)
68
+
69
+ username = typer.prompt("Usuario administrativo para SSH", default="adminops")
70
+ ssh_port = typer.prompt("Porta SSH", default="22")
71
+ sudo_access = typer.confirm("Adicionar usuario ao grupo sudo?", default=True)
72
+ pubkey_path = typer.prompt(
73
+ "Arquivo com chave publica (ENTER para ~/.ssh/id_ed25519.pub)",
74
+ default=str(Path.home() / ".ssh/id_ed25519.pub"),
75
+ )
76
+
77
+ public_key = _load_public_key(pubkey_path)
78
+
79
+ _ensure_user(username, ctx)
80
+ if sudo_access:
81
+ run_cmd(["usermod", "-aG", "sudo", username], ctx)
82
+
83
+ _write_authorized_keys(username, public_key, ctx)
84
+
85
+ config = f"""
86
+ # Arquivo gerenciado pelo raijin-server
87
+ Port {ssh_port}
88
+ Protocol 2
89
+ PermitRootLogin no
90
+ PasswordAuthentication no
91
+ PermitEmptyPasswords no
92
+ ChallengeResponseAuthentication no
93
+ UsePAM yes
94
+ AllowUsers {username}
95
+ AuthenticationMethods publickey
96
+ X11Forwarding no
97
+ ClientAliveInterval 300
98
+ ClientAliveCountMax 2
99
+ MaxAuthTries 3
100
+ """.strip() + "\n"
101
+
102
+ write_file(SSHD_DROPIN, config, ctx)
103
+
104
+ fail2ban_jail = f"""
105
+ [sshd-raijin]
106
+ enabled = true
107
+ port = {ssh_port}
108
+ filter = sshd
109
+ logpath = /var/log/auth.log
110
+ maxretry = 5
111
+ findtime = 600
112
+ bantime = 3600
113
+ """.strip() + "\n"
114
+ write_file(FAIL2BAN_JAIL, fail2ban_jail, ctx)
115
+
116
+ run_cmd(["sshd", "-t"], ctx)
117
+ run_cmd(["systemctl", "enable", "ssh"], ctx)
118
+ run_cmd(["systemctl", "restart", "ssh"], ctx)
119
+ run_cmd(["systemctl", "restart", "fail2ban"], ctx, check=False)
120
+
121
+ if ssh_port != "22":
122
+ run_cmd(["ufw", "allow", ssh_port], ctx, check=False)
123
+ run_cmd(["ufw", "delete", "allow", "22"], ctx, check=False)
124
+
125
+ typer.secho("\n✓ SSH hardening concluido com sucesso!", fg=typer.colors.GREEN, bold=True)
126
+ typer.echo(f"Usuario permitido: {username}")
127
+ typer.echo(f"Porta configurada: {ssh_port}")
128
+ typer.echo("Certifique-se de testar a nova sessao antes de encerrar conexoes atuais.")
@@ -22,7 +22,7 @@ def run(ctx: ExecutionContext) -> None:
22
22
  "certificatesResolvers.letsencrypt.acme.storage=/data/acme.json",
23
23
  "certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web",
24
24
  "logs.general.level=INFO",
25
- f"providers.kubernetesIngress.ingressClass=traefik",
25
+ "providers.kubernetesIngress.ingressClass=traefik",
26
26
  ]
27
27
 
28
28
  if dashboard_host:
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import ipaddress
6
6
  import os
7
+ import platform
7
8
  import subprocess
8
9
  import textwrap
9
10
  from pathlib import Path
@@ -24,6 +25,37 @@ CLIENTS_DIR = WIREGUARD_DIR / "clients"
24
25
  SYSCTL_FILE = Path("/etc/sysctl.d/99-wireguard.conf")
25
26
 
26
27
 
28
+ def _is_secure_boot_enabled() -> bool:
29
+ """Retorna True se Secure Boot estiver ativo (EFI)."""
30
+
31
+ try:
32
+ sb_path = Path("/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c")
33
+ if not sb_path.exists():
34
+ return False
35
+ data = sb_path.read_bytes()
36
+ # Estrutura: atributos (4 bytes) + valor (1 byte)
37
+ return len(data) >= 5 and data[4] == 1
38
+ except Exception:
39
+ return False
40
+
41
+
42
+ def _kernel_headers_present() -> bool:
43
+ """Verifica se os headers do kernel atual estao instalados."""
44
+
45
+ release = platform.uname().release
46
+ return Path(f"/lib/modules/{release}/build").exists()
47
+
48
+
49
+ def _modprobe_check(module: str) -> bool:
50
+ """Tenta carregar o modulo em modo dry-run para validar disponibilidade."""
51
+
52
+ try:
53
+ result = subprocess.run(["modprobe", "-n", module], capture_output=True, text=True, timeout=5)
54
+ return result.returncode == 0
55
+ except Exception:
56
+ return False
57
+
58
+
27
59
  def _generate_keypair(
28
60
  label: str,
29
61
  private_path: Path,
@@ -69,6 +101,40 @@ def run(ctx: ExecutionContext) -> None:
69
101
  require_root(ctx)
70
102
  typer.echo("Configurando WireGuard (VPN site-to-site)...")
71
103
 
104
+ if _is_secure_boot_enabled():
105
+ typer.secho(
106
+ "Secure Boot detectado: modulos DKMS (wireguard) podem exigir assinatura.\n"
107
+ "Se o modulo nao carregar, assine-o ou desabilite Secure Boot temporariamente.",
108
+ fg=typer.colors.YELLOW,
109
+ )
110
+
111
+ # NIC offload pode interferir em VPN/perf em alguns hardwares; aviso leve
112
+ typer.secho(
113
+ "Considere desabilitar offloads problemáticos em NICs (tso/gso/gro) se notar latência ou perda.",
114
+ fg=typer.colors.YELLOW,
115
+ )
116
+
117
+ if not _kernel_headers_present():
118
+ typer.secho(
119
+ "Headers do kernel nao encontrados; instalando linux-headers-$(uname -r) para suportar WireGuard.",
120
+ fg=typer.colors.YELLOW,
121
+ )
122
+ release = platform.uname().release
123
+ header_pkg = f"linux-headers-{release}"
124
+ apt_install([header_pkg], ctx)
125
+ if not _kernel_headers_present():
126
+ typer.secho(
127
+ f"Headers ainda ausentes apos tentar instalar {header_pkg}. Verifique repos ou kernel custom.",
128
+ fg=typer.colors.RED,
129
+ )
130
+ raise typer.Exit(code=1)
131
+
132
+ if not _modprobe_check("wireguard"):
133
+ typer.secho(
134
+ "Aviso: modulo wireguard pode nao estar disponivel (modprobe -n wireguard falhou).",
135
+ fg=typer.colors.YELLOW,
136
+ )
137
+
72
138
  apt_install(["wireguard", "wireguard-tools", "qrencode"], ctx)
73
139
 
74
140
  interface = typer.prompt("Interface WireGuard", default="wg0")
@@ -146,7 +212,6 @@ def run(ctx: ExecutionContext) -> None:
146
212
  typer.secho("\n✓ WireGuard configurado com sucesso!", fg=typer.colors.GREEN, bold=True)
147
213
  typer.echo(f"Configuracao do servidor: {server_conf_path}")
148
214
  typer.echo(f"Cliente inicial salvo em: {client_conf_path}")
149
- typer.echo("Para gerar QR code no terminal: qrencode -t ansiutf8 < {client_conf_path}")
215
+ typer.echo("Para gerar QR code no terminal: qrencode -t ansiutf8 < caminho-do-cliente.conf")
150
216
  typer.echo("Para novos clientes, gere chaves com 'wg genkey' e adicione entradas em ambos os arquivos.")
151
- typer.echo("Clientes Linux/macOS: sudo wg-quick up ./cliente.conf | Windows: importe o arquivo no app WireGuard.")
152
- ```}```} CPU? Need to ensure string quotes? there is newline with braces? we used f string in echo referencing braces? we inserted literal braces within string? `typer.echo(
217
+ typer.echo("Clientes Linux/macOS: sudo wg-quick up ./cliente.conf | Windows: importe o arquivo no app WireGuard.")
@@ -0,0 +1 @@
1
+ """Shell helpers empacotados com o raijin-server."""
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Checklist/smoke tests basicos para um nodo provisionado pelo raijin-server.
5
+ # Execute como root (ou sudo) em um nodo control-plane ou worker.
6
+
7
+ log() { printf "[%s] %s\n" "$(date +%H:%M:%S)" "$*"; }
8
+
9
+ require_cmd() {
10
+ command -v "$1" >/dev/null 2>&1 || { echo "Falta comando: $1"; exit 1; }
11
+ }
12
+
13
+ log "Verificando comandos basicos"
14
+ for c in kubectl helm ufw curl jq; do require_cmd "$c"; done
15
+
16
+ log "Verificando conectividade basica"
17
+ ping -c1 1.1.1.1 >/dev/null && log "Ping OK para 1.1.1.1" || echo "Ping falhou"
18
+
19
+ log "Status de data/hora"
20
+ timedatectl status | head -n 5
21
+
22
+ log "Status fail2ban"
23
+ systemctl is-active --quiet fail2ban && echo "fail2ban ativo" || echo "fail2ban inativo"
24
+
25
+ log "Status firewall (ufw)"
26
+ ufw status numbered || true
27
+
28
+ log "Sysctl rede (hardening)"
29
+ sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.default.rp_filter net.ipv4.conf.all.accept_redirects net.ipv4.conf.default.accept_redirects net.ipv4.conf.all.send_redirects net.ipv4.ip_forward
30
+
31
+ log "Kubernetes: nodes"
32
+ kubectl get nodes -o wide || true
33
+
34
+ log "Kubernetes: pods principais"
35
+ kubectl get pods -A | head -n 40 || true
36
+
37
+ log "Calico: pods"
38
+ kubectl get pods -n kube-system -l k8s-app=calico-node -o wide || true
39
+
40
+ log "Ingress (Traefik): serviços"
41
+ kubectl get svc -n traefik || true
42
+
43
+ log "Prometheus/Grafana/Loki: pods"
44
+ kubectl get pods -n observability || true
45
+
46
+ log "Velero: backups"
47
+ velero backup get || true
48
+
49
+ log "MinIO: serviço"
50
+ kubectl get svc -n minio || true
51
+
52
+ log "Kafka: serviços"
53
+ kubectl get svc -n kafka || true
54
+
55
+ log "Teste HTTP interno (se ingress exposto)"
56
+ if kubectl get svc -n traefik traefik >/dev/null 2>&1; then
57
+ kubectl run curltest --rm -i --restart=Never --image=curlimages/curl -- curl -s -o /dev/null -w "%{http_code}\n" http://traefik.traefik.svc.cluster.local || true
58
+ fi
59
+
60
+ log "Checklist finalizado"
@@ -0,0 +1,134 @@
1
+ #!/bin/bash
2
+ # Script de instalacao rapida do Raijin Server CLI
3
+ # Para Ubuntu Server 20.04+
4
+
5
+ set -euo pipefail
6
+
7
+ RED='\033[0;31m'
8
+ GREEN='\033[0;32m'
9
+ YELLOW='\033[1;33m'
10
+ CYAN='\033[0;36m'
11
+ NC='\033[0m' # No Color
12
+
13
+ echo -e "${CYAN}=========================================${NC}"
14
+ echo -e "${CYAN} Raijin Server - Instalação Rápida${NC}"
15
+ echo -e "${CYAN}=========================================${NC}"
16
+ echo ""
17
+
18
+ # Verificar Python
19
+ if ! command -v python3 &> /dev/null; then
20
+ echo -e "${RED}✗ Python3 não encontrado${NC}"
21
+ echo "Instale com: sudo apt install python3 python3-pip python3-venv"
22
+ exit 1
23
+ fi
24
+
25
+ PY_VERSION=$(python3 --version | awk '{print $2}')
26
+ echo -e "${GREEN}✓${NC} Python $PY_VERSION encontrado"
27
+
28
+ # Verificar se está no diretório do projeto
29
+ if [ ! -f "setup.cfg" ]; then
30
+ echo -e "${RED}✗ Não está no diretório do projeto raijin-server${NC}"
31
+ echo "Execute este script no diretório raiz do projeto"
32
+ exit 1
33
+ fi
34
+
35
+ # Perguntar tipo de instalação
36
+ echo ""
37
+ echo "Escolha o tipo de instalação:"
38
+ echo " 1) Global (requer sudo, todos os usuários)"
39
+ echo " 2) Virtual env (recomendado para desenvolvimento)"
40
+ echo " 3) User install (apenas usuário atual)"
41
+ read -p "Opção [2]: " INSTALL_TYPE
42
+ INSTALL_TYPE=${INSTALL_TYPE:-2}
43
+
44
+ echo ""
45
+ case $INSTALL_TYPE in
46
+ 1)
47
+ echo -e "${YELLOW}Instalando globalmente...${NC}"
48
+ sudo python3 -m pip install .
49
+ BIN_PATH=$(which raijin-server)
50
+ ;;
51
+ 2)
52
+ echo -e "${YELLOW}Criando virtual environment...${NC}"
53
+ python3 -m venv .venv
54
+ source .venv/bin/activate
55
+ pip install --upgrade pip
56
+ pip install -e .
57
+ BIN_PATH=".venv/bin/raijin-server"
58
+
59
+ # Criar wrapper
60
+ cat > raijin-server-run.sh << 'EOF'
61
+ #!/bin/bash
62
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
+ source "$SCRIPT_DIR/.venv/bin/activate"
64
+ raijin-server "$@"
65
+ EOF
66
+ chmod +x raijin-server-run.sh
67
+ echo -e "${GREEN}✓${NC} Wrapper criado: ./raijin-server-run.sh"
68
+ ;;
69
+ 3)
70
+ echo -e "${YELLOW}Instalando para usuário atual...${NC}"
71
+ python3 -m pip install --user .
72
+ BIN_PATH="$HOME/.local/bin/raijin-server"
73
+
74
+ # Adicionar ao PATH se necessário
75
+ if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
76
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
77
+ echo -e "${YELLOW}⚠${NC} Adicionado $HOME/.local/bin ao PATH"
78
+ echo "Execute: source ~/.bashrc"
79
+ fi
80
+ ;;
81
+ *)
82
+ echo -e "${RED}✗ Opção inválida${NC}"
83
+ exit 1
84
+ ;;
85
+ esac
86
+
87
+ echo ""
88
+ echo -e "${GREEN}✓ Instalação concluída!${NC}"
89
+ echo ""
90
+
91
+ # Testar instalação
92
+ echo "Testando instalação..."
93
+ if [ "$INSTALL_TYPE" -eq 2 ]; then
94
+ VERSION=$($BIN_PATH version)
95
+ else
96
+ VERSION=$(raijin-server version)
97
+ fi
98
+ echo -e "${GREEN}✓${NC} $VERSION"
99
+
100
+ # Mostrar próximos passos
101
+ echo ""
102
+ echo -e "${CYAN}=========================================${NC}"
103
+ echo -e "${CYAN} Próximos Passos${NC}"
104
+ echo -e "${CYAN}=========================================${NC}"
105
+ echo ""
106
+
107
+ case $INSTALL_TYPE in
108
+ 2)
109
+ echo "Para usar o CLI:"
110
+ echo " source .venv/bin/activate"
111
+ echo " sudo raijin-server validate"
112
+ echo ""
113
+ echo "Ou use o wrapper:"
114
+ echo " sudo ./raijin-server-run.sh validate"
115
+ ;;
116
+ *)
117
+ echo "Para validar o sistema:"
118
+ echo " sudo raijin-server validate"
119
+ echo ""
120
+ echo "Para ver o menu interativo:"
121
+ echo " sudo raijin-server"
122
+ echo ""
123
+ echo "Para gerar configuração:"
124
+ echo " raijin-server generate-config -o production.yaml"
125
+ ;;
126
+ esac
127
+
128
+ echo ""
129
+ echo "Documentação:"
130
+ echo " README.md - Guia principal"
131
+ echo " AUDIT.md - Relatório de auditoria"
132
+ echo " EXAMPLES.md - Exemplos práticos"
133
+ echo ""
134
+ echo -e "${GREEN}Instalação bem-sucedida!${NC} 🚀"
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # Gera metricas em formato Prometheus para tamanho dos logs do raijin-server.
3
+ # Pode ser usado com node_exporter textfile collector.
4
+
5
+ set -euo pipefail
6
+
7
+ LOG_DIR=${RAIJIN_LOG_DIR:-/var/log/raijin-server}
8
+ LOG_PATTERN=${RAIJIN_LOG_PATTERN:-raijin-server.log*}
9
+ OUTPUT=${RAIJIN_METRIC_FILE:-/var/lib/node_exporter/textfile_collector/raijin_log_size.prom}
10
+
11
+ # Calcula soma de todos os logs (principal + rotações)
12
+ TOTAL_BYTES=0
13
+ shopt -s nullglob
14
+ for f in "$LOG_DIR"/$LOG_PATTERN; do
15
+ size=$(stat -c%s "$f" 2>/dev/null || echo 0)
16
+ TOTAL_BYTES=$((TOTAL_BYTES + size))
17
+ if [[ "$f" =~ raijin-server\.log(\.\d+)?$ ]]; then
18
+ printf "raijin_log_size_bytes{file=\"%s\"} %d\n" "$(basename "$f")" "$size"
19
+ fi
20
+ done | {
21
+ # Escreve métricas no arquivo final
22
+ mkdir -p "$(dirname "$OUTPUT")"
23
+ {
24
+ echo "# HELP raijin_log_size_bytes Tamanho dos logs do raijin-server (bytes)"
25
+ echo "# TYPE raijin_log_size_bytes gauge"
26
+ cat
27
+ echo "# HELP raijin_log_size_total_bytes Soma dos logs do raijin-server (bytes)"
28
+ echo "# TYPE raijin_log_size_total_bytes gauge"
29
+ echo "raijin_log_size_total_bytes ${TOTAL_BYTES}"
30
+ } > "$OUTPUT"
31
+ }
@@ -0,0 +1,183 @@
1
+ #!/bin/bash
2
+ # Checklist de validacao pre-deploy Raijin Server
3
+ # Versao auditada com verificacoes completas
4
+
5
+ set -euo pipefail
6
+
7
+ RED='\033[0;31m'
8
+ GREEN='\033[0;32m'
9
+ YELLOW='\033[1;33m'
10
+ CYAN='\033[0;36m'
11
+ NC='\033[0m' # No Color
12
+
13
+ echo -e "${CYAN}==========================================${NC}"
14
+ echo -e "${CYAN} Raijin Server - Checklist Pre-Deploy${NC}"
15
+ echo -e "${CYAN}==========================================${NC}"
16
+ echo ""
17
+
18
+ # Funcao auxiliar para checks
19
+ check_pass() {
20
+ echo -e "${GREEN}✓${NC} $1"
21
+ }
22
+
23
+ check_fail() {
24
+ echo -e "${RED}✗${NC} $1"
25
+ }
26
+
27
+ check_warn() {
28
+ echo -e "${YELLOW}⚠${NC} $1"
29
+ }
30
+
31
+ FAILED=0
32
+
33
+ # 1. Verificar Python
34
+ echo "1. Verificando Python..."
35
+ if command -v python3 &> /dev/null; then
36
+ PY_VERSION=$(python3 --version | awk '{print $2}')
37
+ if [[ $(echo "$PY_VERSION 3.9" | awk '{print ($1 >= $2)}') -eq 1 ]]; then
38
+ check_pass "Python $PY_VERSION >= 3.9"
39
+ else
40
+ check_fail "Python $PY_VERSION < 3.9"
41
+ FAILED=1
42
+ fi
43
+ else
44
+ check_fail "Python3 nao encontrado"
45
+ FAILED=1
46
+ fi
47
+
48
+ # 2. Verificar OS
49
+ echo ""
50
+ echo "2. Verificando Sistema Operacional..."
51
+ if [ -f /etc/os-release ]; then
52
+ . /etc/os-release
53
+ if [[ "$ID" == "ubuntu" ]]; then
54
+ VERSION_NUM=$(echo "$VERSION_ID" | cut -d. -f1)
55
+ if [[ "$VERSION_NUM" -ge 20 ]]; then
56
+ check_pass "Ubuntu $VERSION_ID"
57
+ else
58
+ check_warn "Ubuntu $VERSION_ID (recomendado >= 20.04)"
59
+ fi
60
+ else
61
+ check_warn "Sistema nao e Ubuntu: $ID"
62
+ fi
63
+ else
64
+ check_fail "/etc/os-release nao encontrado"
65
+ FAILED=1
66
+ fi
67
+
68
+ # 3. Verificar root/sudo
69
+ echo ""
70
+ echo "3. Verificando permissoes..."
71
+ if [[ $EUID -eq 0 ]]; then
72
+ check_pass "Executando como root"
73
+ elif groups | grep -q sudo; then
74
+ check_pass "Usuario no grupo sudo"
75
+ else
76
+ check_fail "Usuario nao tem permissoes sudo"
77
+ FAILED=1
78
+ fi
79
+
80
+ # 4. Verificar espaco em disco
81
+ echo ""
82
+ echo "4. Verificando espaco em disco..."
83
+ DISK_AVAIL=$(df / | tail -1 | awk '{print $4}')
84
+ DISK_AVAIL_GB=$((DISK_AVAIL / 1024 / 1024))
85
+ if [[ $DISK_AVAIL_GB -ge 20 ]]; then
86
+ check_pass "Espaco disponivel: ${DISK_AVAIL_GB}GB"
87
+ else
88
+ check_fail "Espaco insuficiente: ${DISK_AVAIL_GB}GB (minimo: 20GB)"
89
+ FAILED=1
90
+ fi
91
+
92
+ # 5. Verificar memoria
93
+ echo ""
94
+ echo "5. Verificando memoria RAM..."
95
+ MEM_TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}')
96
+ MEM_TOTAL_GB=$((MEM_TOTAL / 1024 / 1024))
97
+ if [[ $MEM_TOTAL_GB -ge 4 ]]; then
98
+ check_pass "Memoria RAM: ${MEM_TOTAL_GB}GB"
99
+ else
100
+ check_fail "Memoria insuficiente: ${MEM_TOTAL_GB}GB (minimo: 4GB)"
101
+ FAILED=1
102
+ fi
103
+
104
+ # 6. Verificar conectividade
105
+ echo ""
106
+ echo "6. Verificando conectividade..."
107
+ if ping -c 1 8.8.8.8 &> /dev/null; then
108
+ check_pass "Conectividade com internet OK"
109
+ else
110
+ check_fail "Sem conectividade com internet"
111
+ FAILED=1
112
+ fi
113
+
114
+ # 7. Verificar comandos essenciais
115
+ echo ""
116
+ echo "7. Verificando comandos essenciais..."
117
+ COMMANDS=("curl" "wget" "apt-get" "systemctl" "gpg")
118
+ for cmd in "${COMMANDS[@]}"; do
119
+ if command -v "$cmd" &> /dev/null; then
120
+ check_pass "$cmd instalado"
121
+ else
122
+ check_fail "$cmd nao encontrado"
123
+ FAILED=1
124
+ fi
125
+ done
126
+
127
+ # 8. Verificar instalacao raijin-server
128
+ echo ""
129
+ echo "8. Verificando instalacao raijin-server..."
130
+ if command -v raijin-server &> /dev/null; then
131
+ RAIJIN_VERSION=$(raijin-server version)
132
+ check_pass "$RAIJIN_VERSION instalado"
133
+ else
134
+ check_warn "raijin-server nao instalado (instale com: pip install .)"
135
+ fi
136
+
137
+ # 9. Verificar logs
138
+ echo ""
139
+ echo "9. Verificando diretorio de logs..."
140
+ if [[ -d /var/log/raijin-server ]]; then
141
+ check_pass "Diretorio de logs: /var/log/raijin-server"
142
+ elif [[ -f ~/.raijin-server.log ]]; then
143
+ check_warn "Usando fallback: ~/.raijin-server.log"
144
+ else
145
+ check_warn "Nenhum log encontrado ainda"
146
+ fi
147
+
148
+ # 10. Verificar estado
149
+ echo ""
150
+ echo "10. Verificando estado dos modulos..."
151
+ STATE_DIRS=("/var/lib/raijin-server/state" "$HOME/.local/share/raijin-server/state")
152
+ FOUND_STATE=0
153
+ for dir in "${STATE_DIRS[@]}"; do
154
+ if [[ -d "$dir" ]]; then
155
+ MODULE_COUNT=$(ls -1 "$dir"/*.done 2>/dev/null | wc -l)
156
+ if [[ $MODULE_COUNT -gt 0 ]]; then
157
+ check_pass "$MODULE_COUNT modulos concluidos (em $dir)"
158
+ FOUND_STATE=1
159
+ break
160
+ fi
161
+ fi
162
+ done
163
+ if [[ $FOUND_STATE -eq 0 ]]; then
164
+ check_warn "Nenhum modulo executado ainda"
165
+ fi
166
+
167
+ # Resumo
168
+ echo ""
169
+ echo -e "${CYAN}==========================================${NC}"
170
+ if [[ $FAILED -eq 0 ]]; then
171
+ echo -e "${GREEN}✓ Sistema pronto para executar raijin-server${NC}"
172
+ echo ""
173
+ echo "Proximos passos:"
174
+ echo " 1. sudo raijin-server validate"
175
+ echo " 2. sudo raijin-server"
176
+ echo " 3. Ou: raijin-server generate-config -o production.yaml"
177
+ exit 0
178
+ else
179
+ echo -e "${RED}✗ Sistema NAO atende pre-requisitos${NC}"
180
+ echo ""
181
+ echo "Corrija os problemas acima antes de prosseguir."
182
+ exit 1
183
+ fi