raijin-server 0.2.7__py3-none-any.whl → 0.2.10__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.
- raijin_server/__init__.py +1 -1
- raijin_server/cli.py +108 -3
- raijin_server/config.py +4 -4
- raijin_server/healthchecks.py +22 -0
- raijin_server/modules/kubernetes.py +111 -1
- raijin_server/modules/network.py +3 -3
- raijin_server/modules/sanitize.py +49 -1
- raijin_server/modules/traefik.py +1 -2
- raijin_server/validators.py +23 -22
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/METADATA +60 -4
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/RECORD +15 -15
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/WHEEL +0 -0
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/entry_points.txt +0 -0
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/licenses/LICENSE +0 -0
- {raijin_server-0.2.7.dist-info → raijin_server-0.2.10.dist-info}/top_level.txt +0 -0
raijin_server/__init__.py
CHANGED
raijin_server/cli.py
CHANGED
|
@@ -45,7 +45,7 @@ from raijin_server.modules import (
|
|
|
45
45
|
vpn,
|
|
46
46
|
)
|
|
47
47
|
from raijin_server.utils import ExecutionContext, logger, active_log_file, available_log_files, page_text, ensure_tool
|
|
48
|
-
from raijin_server.validators import validate_system_requirements, check_module_dependencies
|
|
48
|
+
from raijin_server.validators import validate_system_requirements, check_module_dependencies, MODULE_DEPENDENCIES
|
|
49
49
|
from raijin_server.healthchecks import run_health_check
|
|
50
50
|
from raijin_server.config import ConfigManager
|
|
51
51
|
|
|
@@ -85,9 +85,9 @@ MODULES: Dict[str, Callable[[ExecutionContext], None]] = {
|
|
|
85
85
|
"vpn": vpn.run,
|
|
86
86
|
"kubernetes": kubernetes.run,
|
|
87
87
|
"calico": calico.run,
|
|
88
|
+
"traefik": traefik.run, # mover antes do cert_manager para refletir dependencia
|
|
88
89
|
"cert_manager": cert_manager.run,
|
|
89
90
|
"istio": istio.run,
|
|
90
|
-
"traefik": traefik.run,
|
|
91
91
|
"kong": kong.run,
|
|
92
92
|
"minio": minio.run,
|
|
93
93
|
"prometheus": prometheus.run,
|
|
@@ -103,6 +103,11 @@ MODULES: Dict[str, Callable[[ExecutionContext], None]] = {
|
|
|
103
103
|
"full_install": full_install.run,
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
# Rollbacks sao opcionais; por padrao apenas removem marcador de conclusao e avisam
|
|
107
|
+
ROLLBACK_HANDLERS: Dict[str, Callable[[ExecutionContext], None]] = {
|
|
108
|
+
# Exemplos para futuras customizacoes: "traefik": traefik.rollback
|
|
109
|
+
}
|
|
110
|
+
|
|
106
111
|
MODULE_DESCRIPTIONS: Dict[str, str] = {
|
|
107
112
|
"sanitize": "Remove instalacoes antigas de Kubernetes e prepara ambiente",
|
|
108
113
|
"bootstrap": "Instala ferramentas: helm, kubectl, istioctl, velero, containerd",
|
|
@@ -253,6 +258,79 @@ def _is_completed(name: str) -> bool:
|
|
|
253
258
|
return _state_file(name).exists()
|
|
254
259
|
|
|
255
260
|
|
|
261
|
+
def _clear_completed(name: str) -> None:
|
|
262
|
+
try:
|
|
263
|
+
path = _state_file(name)
|
|
264
|
+
if path.exists():
|
|
265
|
+
path.unlink()
|
|
266
|
+
typer.secho(f"Estado removido: {name}", fg=typer.colors.YELLOW)
|
|
267
|
+
except Exception as exc:
|
|
268
|
+
console.print(f"[yellow]Nao foi possivel limpar estado de {name}: {exc}[/yellow]")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _dependents_of(module: str) -> list[str]:
|
|
272
|
+
dependents = []
|
|
273
|
+
for mod, deps in MODULE_DEPENDENCIES.items():
|
|
274
|
+
if module in deps:
|
|
275
|
+
dependents.append(mod)
|
|
276
|
+
return dependents
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _default_rollback(exec_ctx: ExecutionContext, name: str) -> None:
|
|
280
|
+
"""Rollback padrao: nao aplica alteracoes, apenas sinaliza ausencia de implementacao."""
|
|
281
|
+
if exec_ctx.dry_run:
|
|
282
|
+
typer.secho(f"[dry-run] Rollback para '{name}' nao automatizado (necessario reverter manualmente).", fg=typer.colors.YELLOW)
|
|
283
|
+
else:
|
|
284
|
+
typer.secho(
|
|
285
|
+
f"Rollback para '{name}' ainda nao foi automatizado. Reverta recursos manualmente e reexecute se necessario.",
|
|
286
|
+
fg=typer.colors.YELLOW,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _get_rollback_handler(name: str) -> Callable[[ExecutionContext], None]:
|
|
291
|
+
return ROLLBACK_HANDLERS.get(name, lambda ctx: _default_rollback(ctx, name))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _rollback_module(
|
|
295
|
+
ctx: typer.Context,
|
|
296
|
+
name: str,
|
|
297
|
+
*,
|
|
298
|
+
cascade_prompt: bool = True,
|
|
299
|
+
visited: set[str] | None = None,
|
|
300
|
+
) -> None:
|
|
301
|
+
handler = _get_rollback_handler(name)
|
|
302
|
+
exec_ctx = ctx.obj or ExecutionContext()
|
|
303
|
+
|
|
304
|
+
visited = visited or set()
|
|
305
|
+
if name in visited:
|
|
306
|
+
return
|
|
307
|
+
visited.add(name)
|
|
308
|
+
|
|
309
|
+
dependents = _dependents_of(name)
|
|
310
|
+
completed_dependents = [dep for dep in dependents if _is_completed(dep)]
|
|
311
|
+
|
|
312
|
+
if completed_dependents and cascade_prompt:
|
|
313
|
+
typer.secho(
|
|
314
|
+
"Dependencias detectadas apos este modulo: " + ", ".join(completed_dependents),
|
|
315
|
+
fg=typer.colors.YELLOW,
|
|
316
|
+
)
|
|
317
|
+
typer.secho(
|
|
318
|
+
"Rollback em cascata vai tentar reverter esses modulos primeiro para evitar estado inconsistente.",
|
|
319
|
+
fg=typer.colors.YELLOW,
|
|
320
|
+
)
|
|
321
|
+
if not typer.confirm("Prosseguir com rollback em cascata?", default=False):
|
|
322
|
+
typer.secho("Rollback cancelado.", fg=typer.colors.RED)
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
for dep in completed_dependents:
|
|
326
|
+
_rollback_module(ctx, dep, cascade_prompt=False, visited=visited)
|
|
327
|
+
|
|
328
|
+
typer.secho(f"\n[ROLLBACK] {name}", fg=typer.colors.CYAN, bold=True)
|
|
329
|
+
handler(exec_ctx)
|
|
330
|
+
_clear_completed(name)
|
|
331
|
+
typer.secho(f"Rollback finalizado (best-effort) para {name}\n", fg=typer.colors.GREEN)
|
|
332
|
+
|
|
333
|
+
|
|
256
334
|
def _render_menu(dry_run: bool) -> int:
|
|
257
335
|
table = Table(
|
|
258
336
|
title="Selecione um modulo para executar",
|
|
@@ -326,9 +404,21 @@ def interactive_menu(ctx: typer.Context) -> None:
|
|
|
326
404
|
console.print("[red]Opcao invalida[/red]")
|
|
327
405
|
continue
|
|
328
406
|
|
|
407
|
+
action = Prompt.ask(
|
|
408
|
+
"Acao (e=executar, r=rollback, c=cancelar)",
|
|
409
|
+
choices=["e", "r", "c"],
|
|
410
|
+
default="e",
|
|
411
|
+
)
|
|
412
|
+
|
|
329
413
|
exec_ctx = ExecutionContext(dry_run=current_dry_run)
|
|
330
414
|
ctx.obj = exec_ctx
|
|
331
|
-
|
|
415
|
+
|
|
416
|
+
if action == "e":
|
|
417
|
+
_run_module(ctx, name)
|
|
418
|
+
elif action == "r":
|
|
419
|
+
_rollback_module(ctx, name)
|
|
420
|
+
else:
|
|
421
|
+
console.print("[yellow]Acao cancelada[/yellow]")
|
|
332
422
|
# Loop continua e menu eh re-renderizado, refletindo status atualizado quando nao eh dry-run.
|
|
333
423
|
|
|
334
424
|
|
|
@@ -375,6 +465,21 @@ def menu(ctx: typer.Context) -> None:
|
|
|
375
465
|
interactive_menu(ctx)
|
|
376
466
|
|
|
377
467
|
|
|
468
|
+
@app.command()
|
|
469
|
+
def rollback(
|
|
470
|
+
ctx: typer.Context,
|
|
471
|
+
module: str = typer.Argument(..., help="Modulo a reverter"),
|
|
472
|
+
cascade: bool = typer.Option(
|
|
473
|
+
True,
|
|
474
|
+
"--cascade/--no-cascade",
|
|
475
|
+
help="Quando habilitado, pergunta e aplica rollback em dependentes concluidos primeiro",
|
|
476
|
+
),
|
|
477
|
+
) -> None:
|
|
478
|
+
"""Executa rollback best-effort de um modulo (com aviso sobre dependencias)."""
|
|
479
|
+
|
|
480
|
+
_rollback_module(ctx, module, cascade_prompt=cascade)
|
|
481
|
+
|
|
482
|
+
|
|
378
483
|
@app.command()
|
|
379
484
|
def hardening(ctx: typer.Context) -> None:
|
|
380
485
|
_run_module(ctx, "hardening")
|
raijin_server/config.py
CHANGED
|
@@ -78,15 +78,15 @@ class ConfigManager:
|
|
|
78
78
|
"modules": {
|
|
79
79
|
"network": {
|
|
80
80
|
"interface": "ens18",
|
|
81
|
-
"address": "192.168.
|
|
82
|
-
"gateway": "192.168.
|
|
83
|
-
"dns": "
|
|
81
|
+
"address": "192.168.1.81/24",
|
|
82
|
+
"gateway": "192.168.1.254",
|
|
83
|
+
"dns": "177.128.80.44,177.128.80.45",
|
|
84
84
|
},
|
|
85
85
|
"kubernetes": {
|
|
86
86
|
"pod_cidr": "10.244.0.0/16",
|
|
87
87
|
"service_cidr": "10.96.0.0/12",
|
|
88
88
|
"cluster_name": "raijin",
|
|
89
|
-
"advertise_address": "
|
|
89
|
+
"advertise_address": "192.168.1.81",
|
|
90
90
|
},
|
|
91
91
|
"calico": {
|
|
92
92
|
"pod_cidr": "10.244.0.0/16",
|
raijin_server/healthchecks.py
CHANGED
|
@@ -124,6 +124,21 @@ def check_k8s_pods_in_namespace(namespace: str, ctx: ExecutionContext, timeout:
|
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def check_swap_disabled(ctx: ExecutionContext) -> tuple[bool, str]:
|
|
128
|
+
"""Confirma que nao ha swap ativa (requisito kubeadm/kubelet)."""
|
|
129
|
+
if ctx.dry_run:
|
|
130
|
+
return True, "dry-run"
|
|
131
|
+
try:
|
|
132
|
+
with open("/proc/swaps") as f:
|
|
133
|
+
lines = f.read().strip().splitlines()
|
|
134
|
+
# /proc/swaps tem header + linhas; se so header, swap esta off
|
|
135
|
+
if len(lines) <= 1:
|
|
136
|
+
return True, "swap desativada"
|
|
137
|
+
return False, "swap ativa (remova entradas do fstab e execute swapoff -a)"
|
|
138
|
+
except Exception as exc:
|
|
139
|
+
return False, f"falha ao verificar swap: {exc}"
|
|
140
|
+
|
|
141
|
+
|
|
127
142
|
def check_helm_release(release: str, namespace: str, ctx: ExecutionContext) -> Tuple[bool, str]:
|
|
128
143
|
"""Verifica status de um release Helm."""
|
|
129
144
|
if ctx.dry_run:
|
|
@@ -217,6 +232,13 @@ def verify_kubernetes(ctx: ExecutionContext) -> bool:
|
|
|
217
232
|
services = ["kubelet", "containerd"]
|
|
218
233
|
all_ok = True
|
|
219
234
|
|
|
235
|
+
swap_ok, swap_msg = check_swap_disabled(ctx)
|
|
236
|
+
if swap_ok:
|
|
237
|
+
typer.secho(f" ✓ Swap: {swap_msg}", fg=typer.colors.GREEN)
|
|
238
|
+
else:
|
|
239
|
+
typer.secho(f" ✗ Swap: {swap_msg}", fg=typer.colors.RED)
|
|
240
|
+
all_ok = False
|
|
241
|
+
|
|
220
242
|
for service in services:
|
|
221
243
|
ok, status = check_systemd_service(service, ctx)
|
|
222
244
|
if ok:
|
|
@@ -17,6 +17,12 @@ from raijin_server.utils import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
CALICO_VERSION = "v3.28.0"
|
|
21
|
+
CALICO_URL = f"https://raw.githubusercontent.com/projectcalico/calico/{CALICO_VERSION}/manifests/calico.yaml"
|
|
22
|
+
DEFAULT_CNI = os.environ.get("RAIJIN_CNI", "calico").lower() # calico|none
|
|
23
|
+
FORCE_CNI = os.environ.get("RAIJIN_FORCE_CNI", "0") == "1"
|
|
24
|
+
|
|
25
|
+
|
|
20
26
|
def _cleanup_old_repo(ctx: ExecutionContext) -> None:
|
|
21
27
|
"""Remove repo legado apt.kubernetes.io se existir para evitar erro 404."""
|
|
22
28
|
|
|
@@ -49,6 +55,69 @@ def _reset_cluster(ctx: ExecutionContext) -> None:
|
|
|
49
55
|
typer.secho("✓ Limpeza concluida.", fg=typer.colors.GREEN)
|
|
50
56
|
|
|
51
57
|
|
|
58
|
+
def _cni_present(ctx: ExecutionContext) -> bool:
|
|
59
|
+
"""Detecta se ja existe um CNI aplicado (qualquer DaemonSet tipico)."""
|
|
60
|
+
|
|
61
|
+
result = run_cmd(
|
|
62
|
+
[
|
|
63
|
+
"kubectl",
|
|
64
|
+
"get",
|
|
65
|
+
"daemonset",
|
|
66
|
+
"-n",
|
|
67
|
+
"kube-system",
|
|
68
|
+
"-o",
|
|
69
|
+
"jsonpath={.items[*].metadata.name}",
|
|
70
|
+
],
|
|
71
|
+
ctx,
|
|
72
|
+
check=False,
|
|
73
|
+
)
|
|
74
|
+
if result.returncode != 0:
|
|
75
|
+
return False
|
|
76
|
+
names = (result.stdout or "").split()
|
|
77
|
+
for name in names:
|
|
78
|
+
if any(token in name for token in ("calico", "cilium", "flannel", "weave", "canal")):
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _apply_calico(pod_cidr: str, ctx: ExecutionContext) -> None:
|
|
84
|
+
"""Aplica Calico com CIDR alinhado ao podSubnet informado."""
|
|
85
|
+
|
|
86
|
+
typer.echo(f"Aplicando Calico ({CALICO_VERSION}) com pod CIDR {pod_cidr}...")
|
|
87
|
+
|
|
88
|
+
if ctx.dry_run:
|
|
89
|
+
typer.echo("[dry-run] kubectl apply -f <calico.yaml>")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
cmd = (
|
|
93
|
+
f"curl -fsSL --retry 3 --retry-delay 2 {CALICO_URL} "
|
|
94
|
+
f"| sed 's#192.168.0.0/16#{pod_cidr}#g' "
|
|
95
|
+
f"| kubectl apply -f -"
|
|
96
|
+
)
|
|
97
|
+
run_cmd(cmd, ctx, use_shell=True)
|
|
98
|
+
|
|
99
|
+
# Aguarda o daemonset subir para evitar Node NotReady por falta de CNI
|
|
100
|
+
run_cmd(
|
|
101
|
+
["kubectl", "-n", "kube-system", "rollout", "status", "daemonset/calico-node", "--timeout", "300s"],
|
|
102
|
+
ctx,
|
|
103
|
+
check=False,
|
|
104
|
+
)
|
|
105
|
+
run_cmd(
|
|
106
|
+
[
|
|
107
|
+
"kubectl",
|
|
108
|
+
"-n",
|
|
109
|
+
"kube-system",
|
|
110
|
+
"rollout",
|
|
111
|
+
"status",
|
|
112
|
+
"deployment/calico-kube-controllers",
|
|
113
|
+
"--timeout",
|
|
114
|
+
"300s",
|
|
115
|
+
],
|
|
116
|
+
ctx,
|
|
117
|
+
check=False,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
52
121
|
def run(ctx: ExecutionContext) -> None:
|
|
53
122
|
require_root(ctx)
|
|
54
123
|
typer.echo("Instalando e preparando Kubernetes (kubeadm/kubelet/kubectl)...")
|
|
@@ -146,6 +215,11 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
146
215
|
enable_service("containerd", ctx)
|
|
147
216
|
enable_service("kubelet", ctx)
|
|
148
217
|
|
|
218
|
+
# Garante swap off antes de prosseguir (requisito kubeadm)
|
|
219
|
+
typer.echo("Desabilitando swap (requisito Kubernetes)...")
|
|
220
|
+
run_cmd(["swapoff", "-a"], ctx, check=False)
|
|
221
|
+
run_cmd("sed -i '/swap/d' /etc/fstab", ctx, use_shell=True, check=False)
|
|
222
|
+
|
|
149
223
|
# kubeadm exige ip_forward=1; sobrepoe ajuste de hardening para fase de cluster.
|
|
150
224
|
# Desabilita IPv6 completamente para evitar erros de preflight e simplificar rede
|
|
151
225
|
sysctl_k8s = """# Kubernetes network settings
|
|
@@ -164,7 +238,19 @@ net.ipv6.conf.lo.disable_ipv6=1
|
|
|
164
238
|
pod_cidr = typer.prompt("Pod CIDR", default="10.244.0.0/16")
|
|
165
239
|
service_cidr = typer.prompt("Service CIDR", default="10.96.0.0/12")
|
|
166
240
|
cluster_name = typer.prompt("Nome do cluster", default="raijin")
|
|
167
|
-
|
|
241
|
+
default_adv = "192.168.1.81"
|
|
242
|
+
advertise_address = typer.prompt("API advertise address", default=default_adv)
|
|
243
|
+
if advertise_address != default_adv:
|
|
244
|
+
typer.secho(
|
|
245
|
+
f"⚠ Para ambiente atual use {default_adv} (IP LAN, evita NAT).", fg=typer.colors.YELLOW
|
|
246
|
+
)
|
|
247
|
+
if not typer.confirm(f"Deseja forcar {default_adv}?", default=True):
|
|
248
|
+
typer.secho(
|
|
249
|
+
f"Usando valor informado: {advertise_address}. Certifique-se que todos os nos alcancem esse IP.",
|
|
250
|
+
fg=typer.colors.YELLOW,
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
advertise_address = default_adv
|
|
168
254
|
|
|
169
255
|
kubeadm_config = f"""apiVersion: kubeadm.k8s.io/v1beta3
|
|
170
256
|
kind: ClusterConfiguration
|
|
@@ -221,3 +307,27 @@ cgroupDriver: systemd
|
|
|
221
307
|
|
|
222
308
|
typer.echo("Comando de join para workers:")
|
|
223
309
|
run_cmd(["kubeadm", "token", "create", "--print-join-command"], ctx, check=False)
|
|
310
|
+
|
|
311
|
+
# CNI padrao: Calico (pode ser desabilitado via RAIJIN_CNI=none)
|
|
312
|
+
cni_choice = DEFAULT_CNI
|
|
313
|
+
if cni_choice == "none":
|
|
314
|
+
typer.secho(
|
|
315
|
+
"CNI nao aplicado (RAIJIN_CNI=none). Node permanecera NotReady ate aplicar um CNI manual.",
|
|
316
|
+
fg=typer.colors.YELLOW,
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
if _cni_present(ctx) and not FORCE_CNI:
|
|
320
|
+
typer.secho("CNI ja detectado em kube-system; pulando aplicacao automatica (defina RAIJIN_FORCE_CNI=1 para reaplicar).", fg=typer.colors.YELLOW)
|
|
321
|
+
else:
|
|
322
|
+
_apply_calico(pod_cidr, ctx)
|
|
323
|
+
|
|
324
|
+
# Pequeno health check basico para sinalizar ao usuario
|
|
325
|
+
typer.echo("Validando node apos CNI...")
|
|
326
|
+
run_cmd([
|
|
327
|
+
"kubectl",
|
|
328
|
+
"wait",
|
|
329
|
+
"--for=condition=Ready",
|
|
330
|
+
"nodes",
|
|
331
|
+
"--all",
|
|
332
|
+
"--timeout=180s",
|
|
333
|
+
], ctx, check=False)
|
raijin_server/modules/network.py
CHANGED
|
@@ -124,9 +124,9 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
iface = typer.prompt("Interface", default="ens18")
|
|
127
|
-
address = typer.prompt("Endereco CIDR", default="192.168.
|
|
128
|
-
gateway = typer.prompt("Gateway", default="192.168.
|
|
129
|
-
dns = typer.prompt("DNS (separe por virgula)", default="
|
|
127
|
+
address = typer.prompt("Endereco CIDR", default="192.168.1.81/24")
|
|
128
|
+
gateway = typer.prompt("Gateway", default="192.168.1.254")
|
|
129
|
+
dns = typer.prompt("DNS (separe por virgula)", default="177.128.80.44,177.128.80.45")
|
|
130
130
|
|
|
131
131
|
dns_list = ",".join([item.strip() for item in dns.split(",") if item.strip()])
|
|
132
132
|
netplan_content = f"""network:
|
|
@@ -7,7 +7,14 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
|
-
from raijin_server.utils import ExecutionContext, require_root, run_cmd
|
|
10
|
+
from raijin_server.utils import ExecutionContext, require_root, run_cmd, write_file
|
|
11
|
+
|
|
12
|
+
# Defaults alinhados com configuracao de rede solicitada
|
|
13
|
+
NETPLAN_IFACE = "ens18"
|
|
14
|
+
NETPLAN_ADDRESS = "192.168.1.81/24"
|
|
15
|
+
NETPLAN_GATEWAY = "192.168.1.254"
|
|
16
|
+
NETPLAN_DNS = "177.128.80.44,177.128.80.45"
|
|
17
|
+
NETPLAN_PATH = Path("/etc/netplan/01-raijin-static.yaml")
|
|
11
18
|
|
|
12
19
|
SYSTEMD_SERVICES = [
|
|
13
20
|
"kubelet",
|
|
@@ -48,6 +55,44 @@ APT_MARKERS = [
|
|
|
48
55
|
]
|
|
49
56
|
|
|
50
57
|
|
|
58
|
+
def _ensure_netplan(ctx: ExecutionContext) -> None:
|
|
59
|
+
"""Garante que o netplan esteja com IP fixo esperado; se ja estiver, mostra OK."""
|
|
60
|
+
|
|
61
|
+
desired = f"""network:
|
|
62
|
+
version: 2
|
|
63
|
+
renderer: networkd
|
|
64
|
+
ethernets:
|
|
65
|
+
{NETPLAN_IFACE}:
|
|
66
|
+
dhcp4: false
|
|
67
|
+
addresses: [{NETPLAN_ADDRESS}]
|
|
68
|
+
gateway4: {NETPLAN_GATEWAY}
|
|
69
|
+
nameservers:
|
|
70
|
+
addresses: [{NETPLAN_DNS}]
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
existing = None
|
|
74
|
+
if NETPLAN_PATH.exists():
|
|
75
|
+
try:
|
|
76
|
+
existing = NETPLAN_PATH.read_text()
|
|
77
|
+
except Exception:
|
|
78
|
+
existing = None
|
|
79
|
+
|
|
80
|
+
if existing and all(x in existing for x in (NETPLAN_ADDRESS, NETPLAN_GATEWAY, NETPLAN_DNS)):
|
|
81
|
+
typer.secho(
|
|
82
|
+
f"\n✓ Netplan ja configurado com {NETPLAN_ADDRESS} / gw {NETPLAN_GATEWAY} / dns {NETPLAN_DNS}",
|
|
83
|
+
fg=typer.colors.GREEN,
|
|
84
|
+
)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
typer.echo("Aplicando netplan padrao antes da limpeza...")
|
|
88
|
+
write_file(NETPLAN_PATH, desired, ctx)
|
|
89
|
+
run_cmd(["netplan", "apply"], ctx, check=False)
|
|
90
|
+
typer.secho(
|
|
91
|
+
f"✓ Netplan ajustado para {NETPLAN_ADDRESS} (gw {NETPLAN_GATEWAY}, dns {NETPLAN_DNS})",
|
|
92
|
+
fg=typer.colors.GREEN,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
51
96
|
def _stop_services(ctx: ExecutionContext) -> None:
|
|
52
97
|
typer.echo("Parando serviços relacionados (kubelet, containerd)...")
|
|
53
98
|
for service in SYSTEMD_SERVICES:
|
|
@@ -131,6 +176,9 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
131
176
|
typer.echo("Sanitizacao cancelada pelo usuario.")
|
|
132
177
|
return
|
|
133
178
|
|
|
179
|
+
# Primeiro passo: garantir netplan consistente, sem quebrar ao limpar
|
|
180
|
+
_ensure_netplan(ctx)
|
|
181
|
+
|
|
134
182
|
_stop_services(ctx)
|
|
135
183
|
_kubeadm_reset(ctx)
|
|
136
184
|
_flush_iptables(ctx)
|
raijin_server/modules/traefik.py
CHANGED
|
@@ -15,8 +15,7 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
15
15
|
values = [
|
|
16
16
|
"ingressClass.enabled=true",
|
|
17
17
|
"ingressClass.isDefaultClass=true",
|
|
18
|
-
"ports.web.redirectTo=websecure
|
|
19
|
-
"ports.websecure.tls.enabled=true",
|
|
18
|
+
"ports.web.redirectTo=websecure", # valor esperado é o nome da porta de destino
|
|
20
19
|
"service.type=LoadBalancer",
|
|
21
20
|
f"certificatesResolvers.letsencrypt.acme.email={acme_email}",
|
|
22
21
|
"certificatesResolvers.letsencrypt.acme.storage=/data/acme.json",
|
raijin_server/validators.py
CHANGED
|
@@ -15,6 +15,27 @@ import typer
|
|
|
15
15
|
|
|
16
16
|
from raijin_server.utils import ExecutionContext, logger
|
|
17
17
|
|
|
18
|
+
# Grafo de dependencias entre modulos (usado por validacoes e funcoes de rollback)
|
|
19
|
+
MODULE_DEPENDENCIES = {
|
|
20
|
+
"kubernetes": ["essentials", "network", "firewall"],
|
|
21
|
+
"calico": ["kubernetes"],
|
|
22
|
+
"cert_manager": ["kubernetes", "traefik"],
|
|
23
|
+
"istio": ["kubernetes", "calico"],
|
|
24
|
+
"traefik": ["kubernetes"],
|
|
25
|
+
"kong": ["kubernetes"],
|
|
26
|
+
"minio": ["kubernetes"],
|
|
27
|
+
"prometheus": ["kubernetes"],
|
|
28
|
+
"grafana": ["kubernetes", "prometheus"],
|
|
29
|
+
"loki": ["kubernetes"],
|
|
30
|
+
"secrets": ["kubernetes"],
|
|
31
|
+
"harness": ["kubernetes"],
|
|
32
|
+
"velero": ["kubernetes"],
|
|
33
|
+
"kafka": ["kubernetes"],
|
|
34
|
+
"observability_ingress": ["traefik", "prometheus", "grafana"],
|
|
35
|
+
"observability_dashboards": ["prometheus", "grafana"],
|
|
36
|
+
"apokolips_demo": ["kubernetes", "traefik"],
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
|
|
19
40
|
class ValidationError(Exception):
|
|
20
41
|
"""Erro de validacao de pre-requisitos."""
|
|
@@ -215,30 +236,10 @@ def check_module_dependencies(module: str, ctx: ExecutionContext) -> bool:
|
|
|
215
236
|
Returns:
|
|
216
237
|
True se todas as dependencias foram satisfeitas
|
|
217
238
|
"""
|
|
218
|
-
|
|
219
|
-
"kubernetes": ["essentials", "network", "firewall"],
|
|
220
|
-
"calico": ["kubernetes"],
|
|
221
|
-
"cert_manager": ["kubernetes", "traefik"],
|
|
222
|
-
"istio": ["kubernetes", "calico"],
|
|
223
|
-
"traefik": ["kubernetes"],
|
|
224
|
-
"kong": ["kubernetes"],
|
|
225
|
-
"minio": ["kubernetes"],
|
|
226
|
-
"prometheus": ["kubernetes"],
|
|
227
|
-
"grafana": ["kubernetes", "prometheus"],
|
|
228
|
-
"loki": ["kubernetes"],
|
|
229
|
-
"secrets": ["kubernetes"],
|
|
230
|
-
"harness": ["kubernetes"],
|
|
231
|
-
"velero": ["kubernetes"],
|
|
232
|
-
"kafka": ["kubernetes"],
|
|
233
|
-
"observability_ingress": ["traefik", "prometheus", "grafana"],
|
|
234
|
-
"observability_dashboards": ["prometheus", "grafana"],
|
|
235
|
-
"apokolips_demo": ["kubernetes", "traefik"],
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if module not in dependencies:
|
|
239
|
+
if module not in MODULE_DEPENDENCIES:
|
|
239
240
|
return True
|
|
240
241
|
|
|
241
|
-
required =
|
|
242
|
+
required = MODULE_DEPENDENCIES[module]
|
|
242
243
|
missing = []
|
|
243
244
|
|
|
244
245
|
# Verifica arquivos de estado
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: raijin-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: CLI para automacao de setup e hardening de servidores Ubuntu Server.
|
|
5
5
|
Home-page: https://example.com/raijin-server
|
|
6
6
|
Author: Equipe Raijin
|
|
@@ -44,6 +44,8 @@ CLI em Python (Typer) para automatizar setup e hardening de servidores Ubuntu Se
|
|
|
44
44
|
- Arquitetura: [ARCHITECTURE.md](ARCHITECTURE.md)
|
|
45
45
|
- Auditoria: [AUDIT.md](AUDIT.md)
|
|
46
46
|
- Segurança: [SECURITY.md](SECURITY.md)
|
|
47
|
+
- Acesso SSH (Windows): [docs/SSH_WINDOWS.md](docs/SSH_WINDOWS.md)
|
|
48
|
+
- VPN para acesso remoto (WireGuard): [docs/VPN_REMOTE_ACCESS.md](docs/VPN_REMOTE_ACCESS.md)
|
|
47
49
|
|
|
48
50
|
## Destaques
|
|
49
51
|
|
|
@@ -60,6 +62,12 @@ CLI em Python (Typer) para automatizar setup e hardening de servidores Ubuntu Se
|
|
|
60
62
|
- ✅ **Modo Dry-run**: Simula execução sem aplicar mudanças
|
|
61
63
|
|
|
62
64
|
## Requisitos
|
|
65
|
+
Ubuntu Server 20.04+ com Python 3 disponível. Se precisar instalar/atualizar no host alvo:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
sudo apt update
|
|
69
|
+
sudo apt install -y python3 python3-venv python3-pip
|
|
70
|
+
```
|
|
63
71
|
|
|
64
72
|
## Instalação (sempre em venv midgard)
|
|
65
73
|
|
|
@@ -110,6 +118,10 @@ sudo -E ~/.venvs/midgard/bin/raijin-server validate
|
|
|
110
118
|
sudo -E ~/.venvs/midgard/bin/raijin-server menu
|
|
111
119
|
```
|
|
112
120
|
|
|
121
|
+
### Rollback de módulos
|
|
122
|
+
- No menu, após escolher o módulo, selecione `r` para rollback; se houver dependentes já executados, será pedido confirmacao para rollback em cascata dos dependentes antes.
|
|
123
|
+
- Linha de comando: `sudo -E ~/.venvs/midgard/bin/raijin-server rollback <modulo> --cascade/--no-cascade` (best-effort; alguns módulos podem exigir limpeza manual). Caso seja apenas para revisar o efeito, use `-n/--dry-run` no comando principal para não aplicar.
|
|
124
|
+
|
|
113
125
|
### Execução Direta de Módulos
|
|
114
126
|
```bash
|
|
115
127
|
# Executar módulo específico
|
|
@@ -173,6 +185,9 @@ sudo -E ~/.venvs/midgard/bin/raijin-server debug journal --service containerd --
|
|
|
173
185
|
- **[AUDIT.md](AUDIT.md)**: Relatório completo de auditoria e melhorias implementadas
|
|
174
186
|
- **[ARCHITECTURE.md](ARCHITECTURE.md)**: Arquitetura técnica do ambiente
|
|
175
187
|
- **[SECURITY.md](SECURITY.md)**: Políticas de segurança e reporte de vulnerabilidades
|
|
188
|
+
- **Publicação PyPI**: ver seção "Publicar no PyPI" abaixo
|
|
189
|
+
- **CNI automático**: Calico aplicado automaticamente no passo Kubernetes (override com `RAIJIN_CNI=none`)
|
|
190
|
+
- Para reaplicar CNI (forçar mesmo se já houver): `RAIJIN_FORCE_CNI=1`
|
|
176
191
|
|
|
177
192
|
## Fluxo de Execução Recomendado
|
|
178
193
|
|
|
@@ -329,6 +344,46 @@ bash "$SCRIPT_PATH"
|
|
|
329
344
|
|
|
330
345
|
O helper garante o caminho absoluto correto independentemente de onde o pacote foi instalado.
|
|
331
346
|
|
|
347
|
+
## Publicar no PyPI
|
|
348
|
+
|
|
349
|
+
Use o venv local do repositório (`.venv`) para garantir dependências corretas:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
cd /home/rafael/github/raijin-server
|
|
353
|
+
python3 -m venv .venv
|
|
354
|
+
source .venv/bin/activate
|
|
355
|
+
python -m pip install -U pip build twine
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Gerar artefatos limpos:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
rm -rf dist build
|
|
362
|
+
python -m build --sdist --wheel --outdir dist
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Publicar no PyPI (requere token):
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
export TWINE_USERNAME="__token__"
|
|
369
|
+
export TWINE_PASSWORD="pypi-xxxxx" # token do PyPI
|
|
370
|
+
python -m twine upload dist/*
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Opcional: validar no TestPyPI antes de publicar (precisa token de TestPyPI):
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
export TWINE_USERNAME="__token__"
|
|
377
|
+
export TWINE_PASSWORD="pypi-xxxxx" # token do TestPyPI
|
|
378
|
+
python -m twine upload --repository testpypi dist/*
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Depois de publicar, atualize/instale:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
pip install -U raijin-server
|
|
385
|
+
```
|
|
386
|
+
|
|
332
387
|
## Teste de ingress (Apokolips)
|
|
333
388
|
|
|
334
389
|
O módulo [src/raijin_server/modules/apokolips_demo.py](src/raijin_server/modules/apokolips_demo.py) cria um namespace dedicado, ConfigMap com HTML, Deployment NGINX, Service e Ingress Traefik com uma landing page "Apokolips" para validar o tráfego externo.
|
|
@@ -437,17 +492,18 @@ O Twine é a ferramenta oficial para enviar pacotes Python ao PyPI com upload se
|
|
|
437
492
|
Passo a passo:
|
|
438
493
|
```bash
|
|
439
494
|
# 1) Gere artefatos
|
|
440
|
-
|
|
495
|
+
python3 -m pip install --user build
|
|
496
|
+
python3 -m build --sdist --wheel --outdir dist/
|
|
441
497
|
|
|
442
498
|
# 2) Configure o token (crie em https://pypi.org/manage/account/token/)
|
|
443
499
|
export TWINE_USERNAME=__token__
|
|
444
500
|
export TWINE_PASSWORD="<seu-token>"
|
|
445
501
|
|
|
446
502
|
# 3) Envie para o PyPI
|
|
447
|
-
|
|
503
|
+
python3 -m twine upload dist/*
|
|
448
504
|
|
|
449
505
|
# 4) Verifique instalação
|
|
450
|
-
|
|
506
|
+
python3 -m pip install -U raijin-server
|
|
451
507
|
raijin-server --version
|
|
452
508
|
```
|
|
453
509
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
raijin_server/__init__.py,sha256=
|
|
2
|
-
raijin_server/cli.py,sha256=
|
|
3
|
-
raijin_server/config.py,sha256=
|
|
4
|
-
raijin_server/healthchecks.py,sha256=
|
|
1
|
+
raijin_server/__init__.py,sha256=xZWt85TH3yzTcBTI8N0qtHFiSRHQ8nXGICr5curtX6E,95
|
|
2
|
+
raijin_server/cli.py,sha256=DwzUP5Ps-vgiOwXRjACukgHWFo-7FAnDrILbgWhs2ys,28475
|
|
3
|
+
raijin_server/config.py,sha256=QNiEVvrbW56XgvNn5-h3bkJm46Xc8mjNqPbvixXD8N0,4829
|
|
4
|
+
raijin_server/healthchecks.py,sha256=lzXdFw6S0hOYbUKbqksh4phb04lXgXdTspP1Dsz4dx8,15401
|
|
5
5
|
raijin_server/utils.py,sha256=9RnGnPoUTYOpMVRLNa4P4lIQrJNQLkSkPUxycZRGv78,20827
|
|
6
|
-
raijin_server/validators.py,sha256
|
|
6
|
+
raijin_server/validators.py,sha256=-V3BX1hF_i2on7v-7Gu6u4ilhQaVJX4jTnwqvR3uEng,9432
|
|
7
7
|
raijin_server/modules/__init__.py,sha256=e_IbkhLGPcF8to9QUmIESP6fpcTOYcIhaXLKIvqRJMY,920
|
|
8
8
|
raijin_server/modules/apokolips_demo.py,sha256=8ltsXRbVDwlDwLMIvh02NG-FeAfBWw_v6lh7IGOyNqs,13725
|
|
9
9
|
raijin_server/modules/bootstrap.py,sha256=oVIGNRW_JbgY8zXNHGAIP0vGbbHNHyQexthxo5zhbcw,9762
|
|
@@ -18,17 +18,17 @@ raijin_server/modules/harness.py,sha256=dhZ89YIhlkuxiRU1deN6wXVWnXm0xeI03PwYf_qg
|
|
|
18
18
|
raijin_server/modules/istio.py,sha256=761FOGEzEXWlTLYApQxUWY8l4cnEbnIXbIHK3itk_AQ,522
|
|
19
19
|
raijin_server/modules/kafka.py,sha256=bp8k_IhuAIO6dL0IpK1UxxLZoGih6nJp0ZnzwmiZEj8,950
|
|
20
20
|
raijin_server/modules/kong.py,sha256=2EZKYBmBhm_7Nduw9PWrvrekp0VCxQbc2gElpAJqKfg,491
|
|
21
|
-
raijin_server/modules/kubernetes.py,sha256=
|
|
21
|
+
raijin_server/modules/kubernetes.py,sha256=waSf2cCVnLicN5o3M47MzMzmHHtvKeFXm1__8ynQzA0,11871
|
|
22
22
|
raijin_server/modules/loki.py,sha256=erwFfSiSFOv-Ul3nFdrI2RElPYuqqBPBBa_MJAwyLys,676
|
|
23
23
|
raijin_server/modules/minio.py,sha256=BVvsEaJlJUV92_ep7pKsBhSYPjWZrDOB3J6XAWYAHYg,486
|
|
24
|
-
raijin_server/modules/network.py,sha256=
|
|
24
|
+
raijin_server/modules/network.py,sha256=QRlYdcryCCPAWG3QQ_W7ld9gJgETI7H8gwntOU7UqFE,4818
|
|
25
25
|
raijin_server/modules/observability_dashboards.py,sha256=fVz0WEOQrUTF5rJ__Nu_onyBuwL_exFmysWMmg8AE9w,7319
|
|
26
26
|
raijin_server/modules/observability_ingress.py,sha256=Fh1rlFWueBNHnOkHuoHYyhILmpO-iQXINybSUYbYsHQ,5738
|
|
27
27
|
raijin_server/modules/prometheus.py,sha256=Rs9BREmaoKlyteNdAQZnSIeJfsRO0RQKyyL2gTnXyCw,3716
|
|
28
|
-
raijin_server/modules/sanitize.py,sha256=
|
|
28
|
+
raijin_server/modules/sanitize.py,sha256=_RnWn1DUuNrzx3NnKEbMvf5iicgjiN_ubwT59e0rYWY,6040
|
|
29
29
|
raijin_server/modules/secrets.py,sha256=xpV3gIMnwQdAI2j69Ck5daIK4wlYJA_1rkWTtSfVNk0,3715
|
|
30
30
|
raijin_server/modules/ssh_hardening.py,sha256=oQdk-EVnEHNMKIWvoFuZzI4jK0nNO8IAY4hkB4pj8zw,4025
|
|
31
|
-
raijin_server/modules/traefik.py,sha256=
|
|
31
|
+
raijin_server/modules/traefik.py,sha256=e7Rog5fQvbCHI4WkljUUa6g0Alx2V5ZiVZboHWmMCxY,1411
|
|
32
32
|
raijin_server/modules/velero.py,sha256=_CV0QQnWr5L-CWXDOiD9Ef4J7GaQT-s9yNBwqp_FLOY,1395
|
|
33
33
|
raijin_server/modules/vpn.py,sha256=hF-0vA17VKTxhQLDBSEeqI5aPQpiaaj4IpUf9l6lr64,8297
|
|
34
34
|
raijin_server/scripts/__init__.py,sha256=deduGfHf8BMVWred4ux5LfBDT2NJ5XYeJAt2sDEU4qs,53
|
|
@@ -36,9 +36,9 @@ raijin_server/scripts/checklist.sh,sha256=j6E0Kmk1EfjLvKK1VpCqzXJAXI_7Bm67LK4ndy
|
|
|
36
36
|
raijin_server/scripts/install.sh,sha256=Y1ickbQ4siQ0NIPs6UgrqUr8WWy7U0LHmaTQbEgavoI,3949
|
|
37
37
|
raijin_server/scripts/log_size_metric.sh,sha256=Iv4SsX8AuCYRou-klYn32mX41xB6j0xJGLBO6riw4rU,1208
|
|
38
38
|
raijin_server/scripts/pre-deploy-check.sh,sha256=XqMo7IMIpwUHF17YEmU0-cVmTDMoCGMBFnmS39FidI4,4912
|
|
39
|
-
raijin_server-0.2.
|
|
40
|
-
raijin_server-0.2.
|
|
41
|
-
raijin_server-0.2.
|
|
42
|
-
raijin_server-0.2.
|
|
43
|
-
raijin_server-0.2.
|
|
44
|
-
raijin_server-0.2.
|
|
39
|
+
raijin_server-0.2.10.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
|
|
40
|
+
raijin_server-0.2.10.dist-info/METADATA,sha256=_oOzaANRnNg3d3SIcyKxk0rMzwwdbhcMPNs2c7Oyptg,22276
|
|
41
|
+
raijin_server-0.2.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
42
|
+
raijin_server-0.2.10.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
|
|
43
|
+
raijin_server-0.2.10.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
|
|
44
|
+
raijin_server-0.2.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|