raijin-server 0.3.6__py3-none-any.whl → 0.3.7__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.
Potentially problematic release.
This version of raijin-server might be problematic. Click here for more details.
- raijin_server/__init__.py +1 -1
- raijin_server/cli.py +2 -8
- raijin_server/healthchecks.py +1 -55
- raijin_server/minio_utils.py +562 -0
- raijin_server/modules/__init__.py +1 -2
- raijin_server/modules/harbor.py +10 -26
- raijin_server/modules/secrets.py +13 -38
- raijin_server/modules/velero.py +49 -2
- raijin_server/validators.py +1 -1
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/METADATA +1 -1
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/RECORD +15 -15
- raijin_server/modules/apokolips_demo.py +0 -414
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/WHEEL +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/entry_points.txt +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.7.dist-info}/top_level.txt +0 -0
raijin_server/modules/harbor.py
CHANGED
|
@@ -25,6 +25,7 @@ from raijin_server.utils import (
|
|
|
25
25
|
run_cmd,
|
|
26
26
|
write_file,
|
|
27
27
|
)
|
|
28
|
+
from raijin_server.minio_utils import get_or_create_minio_user
|
|
28
29
|
|
|
29
30
|
HARBOR_NAMESPACE = "harbor"
|
|
30
31
|
|
|
@@ -65,34 +66,17 @@ def _uninstall_harbor(ctx: ExecutionContext, namespace: str) -> None:
|
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
def _get_minio_credentials(ctx: ExecutionContext) -> tuple[str, str]:
|
|
68
|
-
"""Obtem credenciais do MinIO
|
|
69
|
-
typer.echo("Obtendo credenciais do MinIO...")
|
|
69
|
+
"""Obtem ou cria credenciais específicas do MinIO para Harbor.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
Esta função cria um usuário MinIO dedicado para o Harbor com acesso
|
|
72
|
+
restrito apenas aos buckets: harbor-registry, harbor-chartmuseum, harbor-jobservice.
|
|
73
|
+
"""
|
|
74
|
+
return get_or_create_minio_user(
|
|
75
|
+
ctx=ctx,
|
|
76
|
+
app_name="harbor",
|
|
77
|
+
buckets=["harbor-registry", "harbor-chartmuseum", "harbor-jobservice"],
|
|
78
|
+
namespace=HARBOR_NAMESPACE,
|
|
75
79
|
)
|
|
76
|
-
|
|
77
|
-
if result.returncode == 0 and result.stdout:
|
|
78
|
-
access_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
|
|
79
|
-
|
|
80
|
-
result = run_cmd(
|
|
81
|
-
["kubectl", "-n", "minio", "get", "secret", "minio-credentials", "-o", "jsonpath={.data.secretkey}"],
|
|
82
|
-
ctx,
|
|
83
|
-
check=False,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if result.returncode == 0 and result.stdout:
|
|
87
|
-
secret_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
|
|
88
|
-
return access_key, secret_key
|
|
89
|
-
|
|
90
|
-
# Fallback para prompt manual
|
|
91
|
-
typer.secho("Não foi possível obter credenciais automaticamente.", fg=typer.colors.YELLOW)
|
|
92
|
-
access_key = typer.prompt("MinIO Access Key", default="thor")
|
|
93
|
-
secret_key = typer.prompt("MinIO Secret Key", default="rebel1on", hide_input=True)
|
|
94
|
-
|
|
95
|
-
return access_key, secret_key
|
|
96
80
|
|
|
97
81
|
|
|
98
82
|
def _wait_for_pods_ready(ctx: ExecutionContext, namespace: str, timeout: int = 300) -> bool:
|
raijin_server/modules/secrets.py
CHANGED
|
@@ -24,6 +24,7 @@ from raijin_server.utils import (
|
|
|
24
24
|
run_cmd,
|
|
25
25
|
write_file,
|
|
26
26
|
)
|
|
27
|
+
from raijin_server.minio_utils import get_or_create_minio_user
|
|
27
28
|
|
|
28
29
|
VAULT_NAMESPACE = "vault"
|
|
29
30
|
ESO_NAMESPACE = "external-secrets"
|
|
@@ -124,35 +125,17 @@ def _wait_for_pods_ready(ctx: ExecutionContext, namespace: str, label: str, time
|
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
def _get_minio_credentials(ctx: ExecutionContext) -> tuple[str, str]:
|
|
127
|
-
"""Obtem credenciais do MinIO
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
ctx,
|
|
134
|
-
|
|
128
|
+
"""Obtem ou cria credenciais específicas do MinIO para Vault.
|
|
129
|
+
|
|
130
|
+
Esta função cria um usuário MinIO dedicado para o Vault com acesso
|
|
131
|
+
restrito apenas ao bucket 'vault-storage'.
|
|
132
|
+
"""
|
|
133
|
+
return get_or_create_minio_user(
|
|
134
|
+
ctx=ctx,
|
|
135
|
+
app_name="vault",
|
|
136
|
+
buckets=["vault-storage"],
|
|
137
|
+
namespace=VAULT_NAMESPACE,
|
|
135
138
|
)
|
|
136
|
-
|
|
137
|
-
if result.returncode == 0 and result.stdout:
|
|
138
|
-
access_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
|
|
139
|
-
|
|
140
|
-
result = run_cmd(
|
|
141
|
-
["kubectl", "-n", "minio", "get", "secret", "minio-credentials", "-o", "jsonpath={.data.secretkey}"],
|
|
142
|
-
ctx,
|
|
143
|
-
check=False,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if result.returncode == 0 and result.stdout:
|
|
147
|
-
secret_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
|
|
148
|
-
return access_key, secret_key
|
|
149
|
-
|
|
150
|
-
# Fallback para prompt manual
|
|
151
|
-
typer.secho("Não foi possível obter credenciais automaticamente.", fg=typer.colors.YELLOW)
|
|
152
|
-
access_key = typer.prompt("MinIO Access Key", default="thor")
|
|
153
|
-
secret_key = typer.prompt("MinIO Secret Key", default="rebel1on", hide_input=True)
|
|
154
|
-
|
|
155
|
-
return access_key, secret_key
|
|
156
139
|
|
|
157
140
|
|
|
158
141
|
def _initialize_vault(ctx: ExecutionContext, vault_ns: str, node_ip: str) -> tuple[str, list[str]]:
|
|
@@ -410,16 +393,8 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
410
393
|
if cleanup:
|
|
411
394
|
_uninstall_vault(ctx, vault_ns)
|
|
412
395
|
|
|
413
|
-
#
|
|
414
|
-
|
|
415
|
-
run_cmd(
|
|
416
|
-
[
|
|
417
|
-
"mc", "mb", "--ignore-existing",
|
|
418
|
-
f"minio/vault-storage"
|
|
419
|
-
],
|
|
420
|
-
ctx,
|
|
421
|
-
check=False,
|
|
422
|
-
)
|
|
396
|
+
# Credenciais são obtidas automaticamente via get_or_create_minio_user
|
|
397
|
+
# que já cria o bucket 'vault-storage' e o usuário 'vault-user'
|
|
423
398
|
|
|
424
399
|
vault_values_yaml = f"""server:
|
|
425
400
|
ha:
|
raijin_server/modules/velero.py
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
"""Backup e restore com Velero (production-ready)."""
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import typer
|
|
6
7
|
|
|
7
8
|
from raijin_server.utils import ExecutionContext, ensure_tool, require_root, run_cmd
|
|
9
|
+
from raijin_server.minio_utils import get_or_create_minio_user
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _create_velero_credentials_file(ctx: ExecutionContext) -> str:
|
|
13
|
+
"""Cria arquivo de credenciais para Velero com usuário MinIO dedicado.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Caminho para o arquivo de credenciais
|
|
17
|
+
"""
|
|
18
|
+
typer.echo("\nConfigurando credenciais MinIO para Velero...")
|
|
19
|
+
|
|
20
|
+
# Cria usuário MinIO específico para Velero
|
|
21
|
+
access_key, secret_key = get_or_create_minio_user(
|
|
22
|
+
ctx=ctx,
|
|
23
|
+
app_name="velero",
|
|
24
|
+
buckets=["velero-backups"],
|
|
25
|
+
namespace="velero",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Cria arquivo de credenciais no formato esperado pelo Velero
|
|
29
|
+
credentials_path = Path("/etc/velero/credentials")
|
|
30
|
+
credentials_path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
|
|
32
|
+
credentials_content = f"""[default]
|
|
33
|
+
aws_access_key_id = {access_key}
|
|
34
|
+
aws_secret_access_key = {secret_key}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if not ctx.dry_run:
|
|
38
|
+
credentials_path.write_text(credentials_content)
|
|
39
|
+
# Protege o arquivo
|
|
40
|
+
credentials_path.chmod(0o600)
|
|
41
|
+
typer.secho(f" ✓ Credenciais salvas em {credentials_path}", fg=typer.colors.GREEN)
|
|
42
|
+
|
|
43
|
+
return str(credentials_path)
|
|
8
44
|
|
|
9
45
|
|
|
10
46
|
def _check_existing_velero(ctx: ExecutionContext) -> bool:
|
|
@@ -91,8 +127,19 @@ def run(ctx: ExecutionContext) -> None:
|
|
|
91
127
|
provider = typer.prompt("Provider (aws, azure, gcp)", default="aws")
|
|
92
128
|
bucket = typer.prompt("Bucket para backups", default="velero-backups")
|
|
93
129
|
region = typer.prompt("Region", default="us-east-1")
|
|
94
|
-
s3_url = typer.prompt("S3 URL (para MinIO usar http://minio.minio.svc:9000)", default="
|
|
95
|
-
|
|
130
|
+
s3_url = typer.prompt("S3 URL (para MinIO usar http://minio.minio.svc:9000)", default="http://minio.minio.svc:9000")
|
|
131
|
+
|
|
132
|
+
# Cria credenciais automaticamente usando usuário MinIO dedicado
|
|
133
|
+
use_auto_credentials = typer.confirm(
|
|
134
|
+
"Criar credenciais MinIO automaticamente (usuário dedicado)?",
|
|
135
|
+
default=True,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if use_auto_credentials:
|
|
139
|
+
secret_file = _create_velero_credentials_file(ctx)
|
|
140
|
+
else:
|
|
141
|
+
secret_file = typer.prompt("Arquivo de credenciais (secret-file)", default="/etc/velero/credentials")
|
|
142
|
+
|
|
96
143
|
use_restic = typer.confirm("Habilitar Restic/Kopia para backups de PV?", default=True)
|
|
97
144
|
schedule = typer.prompt("Schedule cron para backups (ex: '0 2 * * *')", default="0 2 * * *")
|
|
98
145
|
|
raijin_server/validators.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
raijin_server/__init__.py,sha256=
|
|
2
|
-
raijin_server/cli.py,sha256=
|
|
1
|
+
raijin_server/__init__.py,sha256=kD4ksBx35-1QYm5EkRuSYWXJAwwJx_-sM9cp92bOhh4,94
|
|
2
|
+
raijin_server/cli.py,sha256=WvZaPJ5AVjhzzs_jLLe2QGvVEH_VphRwnUkTMEgycbI,37320
|
|
3
3
|
raijin_server/config.py,sha256=QNiEVvrbW56XgvNn5-h3bkJm46Xc8mjNqPbvixXD8N0,4829
|
|
4
|
-
raijin_server/healthchecks.py,sha256=
|
|
4
|
+
raijin_server/healthchecks.py,sha256=UHSRyeKTsCGeL_4dxDSGZ1t8164Q7wYTi1c3ZiU0cro,13536
|
|
5
|
+
raijin_server/minio_utils.py,sha256=NQxIGoVf4-eM8rNwEHdd4QFnEIh2OxY3DyOiFkznsYs,18299
|
|
5
6
|
raijin_server/module_manager.py,sha256=Wmhj603CN0XGUVr7_Fo8CHzKd9yIbS9x5BJLqDj78kw,10259
|
|
6
7
|
raijin_server/utils.py,sha256=9RnGnPoUTYOpMVRLNa4P4lIQrJNQLkSkPUxycZRGv78,20827
|
|
7
|
-
raijin_server/validators.py,sha256=
|
|
8
|
-
raijin_server/modules/__init__.py,sha256=
|
|
9
|
-
raijin_server/modules/apokolips_demo.py,sha256=8ltsXRbVDwlDwLMIvh02NG-FeAfBWw_v6lh7IGOyNqs,13725
|
|
8
|
+
raijin_server/validators.py,sha256=LvxAIq7F8CSIihdfCCDlDtVd6WPBBntiBHuD8fuM2kg,11713
|
|
9
|
+
raijin_server/modules/__init__.py,sha256=UOv7c7UpxZq-JRZM0i55LGfI3LyucZhTL4lypeHs2Jw,858
|
|
10
10
|
raijin_server/modules/bootstrap.py,sha256=oVIGNRW_JbgY8zXNHGAIP0vGbbHNHyQexthxo5zhbcw,9762
|
|
11
11
|
raijin_server/modules/calico.py,sha256=TTPF1bLFdAKb3IVOqFqRxNblULkRmMMRylsIBp4w8I8,6700
|
|
12
12
|
raijin_server/modules/cert_manager.py,sha256=XkFlXJjiP4_9It_PJaFcVYMS-QKTzzFAt839QQ9qNsg,50223
|
|
@@ -14,7 +14,7 @@ raijin_server/modules/essentials.py,sha256=2xUXCyCQtFGd2DnCKV81N1R6bEJqH8zaet8mL
|
|
|
14
14
|
raijin_server/modules/firewall.py,sha256=h6AISqiZeTinVT7BjmQIS872qRAFZJLg7meqlth3cfw,757
|
|
15
15
|
raijin_server/modules/full_install.py,sha256=M4SV4OA-r41xhpMmZQvUieRugiq0faLa4f0N499ksd4,15035
|
|
16
16
|
raijin_server/modules/grafana.py,sha256=8YbKG-UL19lwTkH1-ZxpUetOZd-CLI4_kPsuZblNaWI,18080
|
|
17
|
-
raijin_server/modules/harbor.py,sha256=
|
|
17
|
+
raijin_server/modules/harbor.py,sha256=hFD8x5K4m0unzVBiDOT_yBzDDfbuPW_2SNVrjSCXhGc,21324
|
|
18
18
|
raijin_server/modules/hardening.py,sha256=4hz3ifkMhPlXa2n7gPxN0gitQgzALZ-073vuU3LM4RI,1616
|
|
19
19
|
raijin_server/modules/harness.py,sha256=uWTxTVJlY_VB6xi4ftMtTSaIb96HA8WJQS-RbyxU45M,5391
|
|
20
20
|
raijin_server/modules/internal_dns.py,sha256=Jynngq0TEEUo3jkAR4m8F1ihF10rkQuKHVP-gZYyDFY,15191
|
|
@@ -28,10 +28,10 @@ raijin_server/modules/minio.py,sha256=ZoxugJvvuGLzViDfEzrVCRZUevoiFwcEy0PNyn0My4
|
|
|
28
28
|
raijin_server/modules/network.py,sha256=QRlYdcryCCPAWG3QQ_W7ld9gJgETI7H8gwntOU7UqFE,4818
|
|
29
29
|
raijin_server/modules/prometheus.py,sha256=lyhaqLIfMl0GtQ2b2Hre7_A47HrHBB5gspmnWtwXZ4Y,21880
|
|
30
30
|
raijin_server/modules/sanitize.py,sha256=_RnWn1DUuNrzx3NnKEbMvf5iicgjiN_ubwT59e0rYWY,6040
|
|
31
|
-
raijin_server/modules/secrets.py,sha256=
|
|
31
|
+
raijin_server/modules/secrets.py,sha256=HOFk57LFyzW4XJ3c8uEEPRd5Dj_OYDI1NBVLzJMp0vY,18562
|
|
32
32
|
raijin_server/modules/ssh_hardening.py,sha256=Zd0dlylUBr01SkrI1CS05-0DB9xIto5rWH1bUVs80ow,5422
|
|
33
33
|
raijin_server/modules/traefik.py,sha256=omziywss4o-8t64Kj-upLqbXdFYm2JwqOoOukDUmqxY,5008
|
|
34
|
-
raijin_server/modules/velero.py,sha256=
|
|
34
|
+
raijin_server/modules/velero.py,sha256=nH7WI145OOK-DZo_ZjNegEnwkppi8h98DeQaB5A_kVg,7161
|
|
35
35
|
raijin_server/modules/vpn.py,sha256=qWyROiHx2-FzMqhfpmzslrdfRBTewd53ylUq90wR7SQ,9149
|
|
36
36
|
raijin_server/modules/vpn_client.py,sha256=SjWzSQKLSJCpjz7Y1i1dFaNfPOVIQXFwgD9uM3GYaIY,30035
|
|
37
37
|
raijin_server/scripts/__init__.py,sha256=deduGfHf8BMVWred4ux5LfBDT2NJ5XYeJAt2sDEU4qs,53
|
|
@@ -39,9 +39,9 @@ raijin_server/scripts/checklist.sh,sha256=j6E0Kmk1EfjLvKK1VpCqzXJAXI_7Bm67LK4ndy
|
|
|
39
39
|
raijin_server/scripts/install.sh,sha256=Y1ickbQ4siQ0NIPs6UgrqUr8WWy7U0LHmaTQbEgavoI,3949
|
|
40
40
|
raijin_server/scripts/log_size_metric.sh,sha256=Iv4SsX8AuCYRou-klYn32mX41xB6j0xJGLBO6riw4rU,1208
|
|
41
41
|
raijin_server/scripts/pre-deploy-check.sh,sha256=XqMo7IMIpwUHF17YEmU0-cVmTDMoCGMBFnmS39FidI4,4912
|
|
42
|
-
raijin_server-0.3.
|
|
43
|
-
raijin_server-0.3.
|
|
44
|
-
raijin_server-0.3.
|
|
45
|
-
raijin_server-0.3.
|
|
46
|
-
raijin_server-0.3.
|
|
47
|
-
raijin_server-0.3.
|
|
42
|
+
raijin_server-0.3.7.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
|
|
43
|
+
raijin_server-0.3.7.dist-info/METADATA,sha256=KGvWKBpPSa4-6oSXFHJ0rykLQ-_VFftLobt0mFm-Co0,8829
|
|
44
|
+
raijin_server-0.3.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
45
|
+
raijin_server-0.3.7.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
|
|
46
|
+
raijin_server-0.3.7.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
|
|
47
|
+
raijin_server-0.3.7.dist-info/RECORD,,
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
"""Provisiona uma landing page tema Apokolips para validar ingress."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from textwrap import dedent, indent
|
|
8
|
-
|
|
9
|
-
import typer
|
|
10
|
-
|
|
11
|
-
from raijin_server.utils import ExecutionContext, ensure_tool, run_cmd, write_file
|
|
12
|
-
|
|
13
|
-
NAMESPACE = "apokolips-demo"
|
|
14
|
-
TMP_MANIFEST = Path("/tmp/raijin-apokolips.yaml")
|
|
15
|
-
DEFAULT_HOST = "apokolips.raijin.local"
|
|
16
|
-
HTML_TEMPLATE = dedent(
|
|
17
|
-
"""
|
|
18
|
-
<!DOCTYPE html>
|
|
19
|
-
<html lang="pt-BR">
|
|
20
|
-
<head>
|
|
21
|
-
<meta charset="UTF-8" />
|
|
22
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
23
|
-
<title>Apokolips Signal Check</title>
|
|
24
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
25
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
26
|
-
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&family=Unica+One&display=swap" rel="stylesheet">
|
|
27
|
-
<style>
|
|
28
|
-
:root {
|
|
29
|
-
--lava: #ff4d00;
|
|
30
|
-
--ember: #ff9500;
|
|
31
|
-
--ash: #2c2c34;
|
|
32
|
-
--void: #050007;
|
|
33
|
-
--smoke: #a0a3b1;
|
|
34
|
-
}
|
|
35
|
-
* {
|
|
36
|
-
box-sizing: border-box;
|
|
37
|
-
}
|
|
38
|
-
body {
|
|
39
|
-
margin: 0;
|
|
40
|
-
padding: 0;
|
|
41
|
-
min-height: 100vh;
|
|
42
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
43
|
-
color: #f7f7ff;
|
|
44
|
-
background: radial-gradient(circle at top, rgba(255,77,0,0.45), rgba(5,0,7,0.9)), #050007;
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
justify-content: center;
|
|
48
|
-
overflow: hidden;
|
|
49
|
-
}
|
|
50
|
-
.atmosphere {
|
|
51
|
-
position: absolute;
|
|
52
|
-
inset: 0;
|
|
53
|
-
background: url('https://www.transparenttextures.com/patterns/asfalt-dark.png');
|
|
54
|
-
opacity: 0.35;
|
|
55
|
-
mix-blend-mode: screen;
|
|
56
|
-
pointer-events: none;
|
|
57
|
-
}
|
|
58
|
-
.container {
|
|
59
|
-
width: min(960px, 90vw);
|
|
60
|
-
background: linear-gradient(135deg, rgba(20,22,35,0.9), rgba(10,12,22,0.95));
|
|
61
|
-
border: 1px solid rgba(255,255,255,0.08);
|
|
62
|
-
border-radius: 28px;
|
|
63
|
-
padding: 48px;
|
|
64
|
-
position: relative;
|
|
65
|
-
overflow: hidden;
|
|
66
|
-
box-shadow: 0 40px 120px rgba(0,0,0,0.55);
|
|
67
|
-
animation: rise 1.2s ease forwards;
|
|
68
|
-
}
|
|
69
|
-
.container::before, .container::after {
|
|
70
|
-
content: '';
|
|
71
|
-
position: absolute;
|
|
72
|
-
width: 320px;
|
|
73
|
-
height: 320px;
|
|
74
|
-
border-radius: 50%;
|
|
75
|
-
background: radial-gradient(circle, rgba(255,77,0,0.45), transparent 60%);
|
|
76
|
-
filter: blur(20px);
|
|
77
|
-
z-index: 0;
|
|
78
|
-
}
|
|
79
|
-
.container::before {
|
|
80
|
-
top: -120px;
|
|
81
|
-
right: -60px;
|
|
82
|
-
}
|
|
83
|
-
.container::after {
|
|
84
|
-
bottom: -140px;
|
|
85
|
-
left: -80px;
|
|
86
|
-
background: radial-gradient(circle, rgba(255,149,0,0.4), transparent 60%);
|
|
87
|
-
}
|
|
88
|
-
@keyframes rise {
|
|
89
|
-
from { transform: translateY(40px); opacity: 0; }
|
|
90
|
-
to { transform: translateY(0); opacity: 1; }
|
|
91
|
-
}
|
|
92
|
-
h1 {
|
|
93
|
-
font-family: 'Unica One', sans-serif;
|
|
94
|
-
font-size: clamp(3rem, 5vw, 4.5rem);
|
|
95
|
-
letter-spacing: 0.08em;
|
|
96
|
-
text-transform: uppercase;
|
|
97
|
-
color: var(--lava);
|
|
98
|
-
margin: 0 0 12px;
|
|
99
|
-
z-index: 1;
|
|
100
|
-
}
|
|
101
|
-
.subhead {
|
|
102
|
-
font-size: 1.15rem;
|
|
103
|
-
color: var(--smoke);
|
|
104
|
-
letter-spacing: 0.05em;
|
|
105
|
-
margin-bottom: 32px;
|
|
106
|
-
text-transform: uppercase;
|
|
107
|
-
}
|
|
108
|
-
.panels {
|
|
109
|
-
display: grid;
|
|
110
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
111
|
-
gap: 24px;
|
|
112
|
-
z-index: 1;
|
|
113
|
-
}
|
|
114
|
-
.panel {
|
|
115
|
-
background: rgba(12,14,24,0.85);
|
|
116
|
-
border-radius: 18px;
|
|
117
|
-
padding: 20px;
|
|
118
|
-
border: 1px solid rgba(255,255,255,0.05);
|
|
119
|
-
}
|
|
120
|
-
.panel h2 {
|
|
121
|
-
font-size: 0.95rem;
|
|
122
|
-
text-transform: uppercase;
|
|
123
|
-
letter-spacing: 0.08em;
|
|
124
|
-
color: var(--ember);
|
|
125
|
-
margin: 0 0 12px;
|
|
126
|
-
}
|
|
127
|
-
.status {
|
|
128
|
-
display: flex;
|
|
129
|
-
flex-direction: column;
|
|
130
|
-
gap: 6px;
|
|
131
|
-
font-size: 1rem;
|
|
132
|
-
color: var(--smoke);
|
|
133
|
-
}
|
|
134
|
-
.status span::before {
|
|
135
|
-
content: '●';
|
|
136
|
-
margin-right: 8px;
|
|
137
|
-
color: var(--lava);
|
|
138
|
-
}
|
|
139
|
-
.cta {
|
|
140
|
-
margin-top: 36px;
|
|
141
|
-
display: flex;
|
|
142
|
-
flex-wrap: wrap;
|
|
143
|
-
gap: 18px;
|
|
144
|
-
z-index: 1;
|
|
145
|
-
}
|
|
146
|
-
a.button {
|
|
147
|
-
background: linear-gradient(135deg, var(--lava), var(--ember));
|
|
148
|
-
color: #050007;
|
|
149
|
-
text-decoration: none;
|
|
150
|
-
padding: 14px 26px;
|
|
151
|
-
border-radius: 999px;
|
|
152
|
-
font-weight: 600;
|
|
153
|
-
letter-spacing: 0.08em;
|
|
154
|
-
text-transform: uppercase;
|
|
155
|
-
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
156
|
-
}
|
|
157
|
-
a.button:hover {
|
|
158
|
-
transform: translateY(-2px);
|
|
159
|
-
box-shadow: 0 20px 35px rgba(255,77,0,0.25);
|
|
160
|
-
}
|
|
161
|
-
pre {
|
|
162
|
-
background: rgba(5,0,7,0.75);
|
|
163
|
-
border-radius: 16px;
|
|
164
|
-
padding: 16px;
|
|
165
|
-
color: var(--smoke);
|
|
166
|
-
font-size: 0.95rem;
|
|
167
|
-
line-height: 1.5;
|
|
168
|
-
overflow-x: auto;
|
|
169
|
-
}
|
|
170
|
-
footer {
|
|
171
|
-
margin-top: 28px;
|
|
172
|
-
font-size: 0.85rem;
|
|
173
|
-
letter-spacing: 0.04em;
|
|
174
|
-
color: rgba(247,247,255,0.7);
|
|
175
|
-
}
|
|
176
|
-
@media (max-width: 640px) {
|
|
177
|
-
.container {
|
|
178
|
-
padding: 32px 24px;
|
|
179
|
-
}
|
|
180
|
-
.cta {
|
|
181
|
-
flex-direction: column;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
</style>
|
|
185
|
-
</head>
|
|
186
|
-
<body>
|
|
187
|
-
<div class="atmosphere"></div>
|
|
188
|
-
<main class="container">
|
|
189
|
-
<h1>Apokolips Online</h1>
|
|
190
|
-
<p class="subhead">Canal de prova para ingress / load balancer</p>
|
|
191
|
-
<section class="panels">
|
|
192
|
-
<article class="panel">
|
|
193
|
-
<h2>Estado</h2>
|
|
194
|
-
<div class="status">
|
|
195
|
-
<span>Pods sincronizados</span>
|
|
196
|
-
<span>ConfigMap montado</span>
|
|
197
|
-
<span>Ingress publicado</span>
|
|
198
|
-
</div>
|
|
199
|
-
</article>
|
|
200
|
-
<article class="panel">
|
|
201
|
-
<h2>Checklist</h2>
|
|
202
|
-
<div class="status">
|
|
203
|
-
<span>DNS aponta para balanceador</span>
|
|
204
|
-
<span>TLS (opcional) emitido</span>
|
|
205
|
-
<span>Firewall libera HTTP/S</span>
|
|
206
|
-
</div>
|
|
207
|
-
</article>
|
|
208
|
-
<article class="panel">
|
|
209
|
-
<h2>Debug Rápido</h2>
|
|
210
|
-
<pre>kubectl -n apokolips-demo get all
|
|
211
|
-
kubectl -n apokolips-demo describe ingress apokolips-demo
|
|
212
|
-
curl -H "Host: SEU_HOST" https://LB_IP/</pre>
|
|
213
|
-
</article>
|
|
214
|
-
</section>
|
|
215
|
-
<div class="cta">
|
|
216
|
-
<a class="button" href="https://github.com/darkseid/raijin-server" target="_blank" rel="noreferrer noopener">Docs Raijin</a>
|
|
217
|
-
<a class="button" href="https://status.cloudflare.com/" target="_blank" rel="noreferrer noopener">Status Externo</a>
|
|
218
|
-
</div>
|
|
219
|
-
<footer>
|
|
220
|
-
Se esta página carregou via ingress, seu cluster respondeu à chamada de Apokolips.
|
|
221
|
-
</footer>
|
|
222
|
-
</main>
|
|
223
|
-
</body>
|
|
224
|
-
</html>
|
|
225
|
-
"""
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _resolve_host() -> str:
|
|
230
|
-
env_host = os.environ.get("APOKOLIPS_HOST")
|
|
231
|
-
if env_host:
|
|
232
|
-
return env_host.strip()
|
|
233
|
-
return typer.prompt("Host (FQDN) para o ingress", default=DEFAULT_HOST).strip()
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def _resolve_tls_secret() -> str | None:
|
|
237
|
-
env_secret = os.environ.get("APOKOLIPS_TLS_SECRET")
|
|
238
|
-
if env_secret:
|
|
239
|
-
return env_secret.strip()
|
|
240
|
-
use_tls = typer.confirm("Deseja referenciar um Secret TLS existente?", default=False)
|
|
241
|
-
if not use_tls:
|
|
242
|
-
return None
|
|
243
|
-
secret = typer.prompt("Nome do Secret TLS", default="apokolips-demo-tls")
|
|
244
|
-
return secret.strip() or None
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _resolve_ip_access() -> bool:
|
|
248
|
-
"""Pergunta se deseja acesso via IP direto (para testes)."""
|
|
249
|
-
env_ip = os.environ.get("APOKOLIPS_IP_ACCESS")
|
|
250
|
-
if env_ip:
|
|
251
|
-
return env_ip.strip().lower() in ("1", "true", "yes")
|
|
252
|
-
return typer.confirm("Habilitar acesso via IP direto? (apenas para testes)", default=True)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _build_manifest(host: str, tls_secret: str | None, ip_access: bool = False) -> str:
|
|
256
|
-
html_block = indent(HTML_TEMPLATE.strip("\n"), " " * 4)
|
|
257
|
-
tls_block = ""
|
|
258
|
-
if tls_secret:
|
|
259
|
-
tls_block = (
|
|
260
|
-
" tls:\n"
|
|
261
|
-
" - hosts:\n"
|
|
262
|
-
f" - {host}\n"
|
|
263
|
-
f" secretName: {tls_secret}\n"
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
# Regra adicional para acesso via IP (sem host)
|
|
267
|
-
ip_rule = ""
|
|
268
|
-
if ip_access:
|
|
269
|
-
ip_rule = """
|
|
270
|
-
- http:
|
|
271
|
-
paths:
|
|
272
|
-
- path: /
|
|
273
|
-
pathType: Prefix
|
|
274
|
-
backend:
|
|
275
|
-
service:
|
|
276
|
-
name: apokolips-demo
|
|
277
|
-
port:
|
|
278
|
-
number: 80"""
|
|
279
|
-
|
|
280
|
-
template = """\
|
|
281
|
-
apiVersion: v1
|
|
282
|
-
kind: Namespace
|
|
283
|
-
metadata:
|
|
284
|
-
name: {namespace}
|
|
285
|
-
---
|
|
286
|
-
apiVersion: v1
|
|
287
|
-
kind: ConfigMap
|
|
288
|
-
metadata:
|
|
289
|
-
name: apokolips-html
|
|
290
|
-
namespace: {namespace}
|
|
291
|
-
data:
|
|
292
|
-
index.html: |
|
|
293
|
-
__HTML__
|
|
294
|
-
---
|
|
295
|
-
apiVersion: apps/v1
|
|
296
|
-
kind: Deployment
|
|
297
|
-
metadata:
|
|
298
|
-
name: apokolips-demo
|
|
299
|
-
namespace: {namespace}
|
|
300
|
-
labels:
|
|
301
|
-
app: apokolips-demo
|
|
302
|
-
spec:
|
|
303
|
-
replicas: 1
|
|
304
|
-
selector:
|
|
305
|
-
matchLabels:
|
|
306
|
-
app: apokolips-demo
|
|
307
|
-
template:
|
|
308
|
-
metadata:
|
|
309
|
-
labels:
|
|
310
|
-
app: apokolips-demo
|
|
311
|
-
spec:
|
|
312
|
-
containers:
|
|
313
|
-
- name: apokolips-web
|
|
314
|
-
image: nginx:1.25
|
|
315
|
-
ports:
|
|
316
|
-
- containerPort: 80
|
|
317
|
-
resources:
|
|
318
|
-
limits:
|
|
319
|
-
cpu: 100m
|
|
320
|
-
memory: 128Mi
|
|
321
|
-
requests:
|
|
322
|
-
cpu: 50m
|
|
323
|
-
memory: 64Mi
|
|
324
|
-
volumeMounts:
|
|
325
|
-
- name: site
|
|
326
|
-
mountPath: /usr/share/nginx/html
|
|
327
|
-
readOnly: true
|
|
328
|
-
volumes:
|
|
329
|
-
- name: site
|
|
330
|
-
configMap:
|
|
331
|
-
name: apokolips-html
|
|
332
|
-
---
|
|
333
|
-
apiVersion: v1
|
|
334
|
-
kind: Service
|
|
335
|
-
metadata:
|
|
336
|
-
name: apokolips-demo
|
|
337
|
-
namespace: {namespace}
|
|
338
|
-
spec:
|
|
339
|
-
type: ClusterIP
|
|
340
|
-
selector:
|
|
341
|
-
app: apokolips-demo
|
|
342
|
-
ports:
|
|
343
|
-
- port: 80
|
|
344
|
-
targetPort: 80
|
|
345
|
-
---
|
|
346
|
-
apiVersion: networking.k8s.io/v1
|
|
347
|
-
kind: Ingress
|
|
348
|
-
metadata:
|
|
349
|
-
name: apokolips-demo
|
|
350
|
-
namespace: {namespace}
|
|
351
|
-
annotations:
|
|
352
|
-
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
|
353
|
-
spec:
|
|
354
|
-
ingressClassName: traefik
|
|
355
|
-
rules:
|
|
356
|
-
- host: {host}
|
|
357
|
-
http:
|
|
358
|
-
paths:
|
|
359
|
-
- path: /
|
|
360
|
-
pathType: Prefix
|
|
361
|
-
backend:
|
|
362
|
-
service:
|
|
363
|
-
name: apokolips-demo
|
|
364
|
-
port:
|
|
365
|
-
number: 80__IP_RULE__
|
|
366
|
-
__TLS__
|
|
367
|
-
"""
|
|
368
|
-
|
|
369
|
-
manifest = template.format(namespace=NAMESPACE, host=host)
|
|
370
|
-
manifest = manifest.replace("__HTML__", html_block)
|
|
371
|
-
manifest = manifest.replace("__IP_RULE__", ip_rule)
|
|
372
|
-
manifest = manifest.replace("__TLS__", tls_block.rstrip())
|
|
373
|
-
return f"{manifest.strip()}\n"
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def run(ctx: ExecutionContext) -> None:
|
|
377
|
-
ensure_tool("kubectl", ctx, install_hint="Instale kubectl para aplicar o manifesto do site.")
|
|
378
|
-
host = _resolve_host()
|
|
379
|
-
tls_secret = _resolve_tls_secret()
|
|
380
|
-
ip_access = _resolve_ip_access()
|
|
381
|
-
manifest = _build_manifest(host, tls_secret, ip_access)
|
|
382
|
-
|
|
383
|
-
typer.echo("Gerando manifesto Apokolips...")
|
|
384
|
-
write_file(TMP_MANIFEST, manifest, ctx)
|
|
385
|
-
|
|
386
|
-
try:
|
|
387
|
-
run_cmd(["kubectl", "apply", "-f", str(TMP_MANIFEST)], ctx)
|
|
388
|
-
finally:
|
|
389
|
-
TMP_MANIFEST.unlink(missing_ok=True)
|
|
390
|
-
|
|
391
|
-
typer.secho("\nLanding page implantada!", fg=typer.colors.GREEN, bold=True)
|
|
392
|
-
typer.echo(f" • Namespace: {NAMESPACE}")
|
|
393
|
-
typer.echo(f" • Host: {host}")
|
|
394
|
-
if tls_secret:
|
|
395
|
-
typer.echo(f" • Secret TLS: {tls_secret}")
|
|
396
|
-
if ip_access:
|
|
397
|
-
typer.secho(" • Acesso via IP: HABILITADO (apenas para testes)", fg=typer.colors.YELLOW)
|
|
398
|
-
|
|
399
|
-
typer.echo("\nTestes sugeridos:")
|
|
400
|
-
if ip_access:
|
|
401
|
-
typer.echo(" # Acesso direto via IP (teste):")
|
|
402
|
-
typer.echo(" curl http://<IP_DO_SERVIDOR>/")
|
|
403
|
-
typer.echo("")
|
|
404
|
-
typer.echo(f" # Acesso via DNS (produção):")
|
|
405
|
-
typer.echo(f" curl -H 'Host: {host}' http://<IP_DO_LOAD_BALANCER>/")
|
|
406
|
-
typer.echo(f" kubectl -n {NAMESPACE} get ingress {NAMESPACE}")
|
|
407
|
-
typer.echo(f" kubectl -n {NAMESPACE} get pods")
|
|
408
|
-
|
|
409
|
-
if ip_access:
|
|
410
|
-
typer.secho("\n⚠️ Lembre-se de desabilitar o acesso via IP após configurar o DNS!", fg=typer.colors.YELLOW)
|
|
411
|
-
typer.echo(" Rode novamente com APOKOLIPS_IP_ACCESS=false ou responda 'não' na pergunta.")
|
|
412
|
-
|
|
413
|
-
typer.echo("\nPara remover:")
|
|
414
|
-
typer.echo(f" kubectl delete namespace {NAMESPACE}")
|