Update deploy-lab.sh
This commit is contained in:
+732
-51
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Laboratorio estable v4.4.6.1: ZimaOS + Docker + k3d + Argo CD + Multi-Repo + Governance + Monitoring + CI/CD
|
||||
# Laboratorio candidato v4.5.7: ZimaOS + Docker + k3d + Argo CD + Multi-Repo + Governance + Monitoring + Portainer + CI/CD
|
||||
#
|
||||
# Modos:
|
||||
# bootstrap Crea el clúster si no existe; si existe, lo inicia y reconcilia.
|
||||
@@ -11,7 +11,10 @@
|
||||
# monitoring-diagnose Diagnostica kube-prometheus-stack/Grafana.
|
||||
# monitoring-recover Finaliza una operación obsoleta y resincroniza Grafana.
|
||||
# postdeploy-secrets Regenera y valida credenciales de Gitea Actions.
|
||||
# runner Adopta, crea o recupera el Gitea Actions runner.
|
||||
# portainer Valida o recupera el Portainer Edge Agent.
|
||||
# portainer-adopt Captura de forma persistente el Edge Agent actual.
|
||||
# retire-headlamp Elimina residuos de Headlamp cuando ya no existe en Gitea.
|
||||
# runner Adopta, crea o recupera el Gitea Actions runner.
|
||||
# install-autostart Instala servicio/timer systemd, orden de montajes y autorreparación.
|
||||
# reset Reconstrucción limpia; requiere confirmación explícita.
|
||||
#
|
||||
@@ -51,7 +54,25 @@ ARGOCD_VERSION="${ARGOCD_VERSION:-v3.2.0}"
|
||||
ARGOCD_HOST="${ARGOCD_HOST:-argocd.cruzcloud.net}"
|
||||
EXPECTED_HOST_IP="${EXPECTED_HOST_IP:-192.168.68.61}"
|
||||
ALLOW_IP_CHANGE="${ALLOW_IP_CHANGE:-false}"
|
||||
INSTALL_HEADLAMP_RBAC="${INSTALL_HEADLAMP_RBAC:-true}"
|
||||
# Portainer Edge Agent para administrar Kubernetes.
|
||||
INSTALL_PORTAINER_EDGE_AGENT="${INSTALL_PORTAINER_EDGE_AGENT:-true}"
|
||||
PORTAINER_REQUIRED="${PORTAINER_REQUIRED:-true}"
|
||||
PORTAINER_NAMESPACE="${PORTAINER_NAMESPACE:-portainer}"
|
||||
PORTAINER_AGENT_DEPLOYMENT="${PORTAINER_AGENT_DEPLOYMENT:-portainer-agent}"
|
||||
PORTAINER_AGENT_SERVICE="${PORTAINER_AGENT_SERVICE:-portainer-agent}"
|
||||
PORTAINER_AGENT_SERVICEACCOUNT="${PORTAINER_AGENT_SERVICEACCOUNT:-portainer-sa-clusteradmin}"
|
||||
PORTAINER_AGENT_CLUSTERROLEBINDING="${PORTAINER_AGENT_CLUSTERROLEBINDING:-portainer-crb-clusteradmin}"
|
||||
PORTAINER_AGENT_CONFIGMAP="${PORTAINER_AGENT_CONFIGMAP:-portainer-agent-edge}"
|
||||
PORTAINER_AGENT_SECRET="${PORTAINER_AGENT_SECRET:-portainer-agent-edge-key}"
|
||||
PORTAINER_AGENT_IMAGE="${PORTAINER_AGENT_IMAGE:-portainer/agent:2.43.0}"
|
||||
PORTAINER_SERVER_URL="${PORTAINER_SERVER_URL:-https://192.168.68.61:9444}"
|
||||
PORTAINER_TUNNEL_HOST="${PORTAINER_TUNNEL_HOST:-192.168.68.61}"
|
||||
PORTAINER_TUNNEL_PORT="${PORTAINER_TUNNEL_PORT:-8000}"
|
||||
PORTAINER_EDGE_INSECURE_POLL="${PORTAINER_EDGE_INSECURE_POLL:-1}"
|
||||
PORTAINER_ROLLOUT_TIMEOUT="${PORTAINER_ROLLOUT_TIMEOUT:-180}"
|
||||
PORTAINER_BUNDLE_FILE="${PORTAINER_BUNDLE_FILE:-${APP_DATA_PATH}/secrets/portainer-edge-agent.bundle.json}"
|
||||
PORTAINER_CAPTURE_ON_SUCCESS="${PORTAINER_CAPTURE_ON_SUCCESS:-true}"
|
||||
REMOVE_LEGACY_HEADLAMP="${REMOVE_LEGACY_HEADLAMP:-true}"
|
||||
APPLY_POSTDEPLOY_RBAC="${APPLY_POSTDEPLOY_RBAC:-true}"
|
||||
POSTDEPLOY_SERVICE_ACCOUNT="${POSTDEPLOY_SERVICE_ACCOUNT:-gitea-postdeploy-validator}"
|
||||
POSTDEPLOY_TOKEN_SECRET="${POSTDEPLOY_TOKEN_SECRET:-gitea-postdeploy-validator-token}"
|
||||
@@ -63,6 +84,16 @@ AGENT_RECOVERY_WAIT="${AGENT_RECOVERY_WAIT:-75}"
|
||||
AUTO_REREGISTER_STALE_AGENTS="${AUTO_REREGISTER_STALE_AGENTS:-true}"
|
||||
CHECK_INGRESS_AFTER_BOOT="${CHECK_INGRESS_AFTER_BOOT:-true}"
|
||||
|
||||
# Arranque conservador basado en la secuencia previamente confirmada:
|
||||
# Docker restaura k3d y el runner con unless-stopped; systemd solo interviene
|
||||
# después de una ventana amplia si la API sigue caída.
|
||||
K3D_AUTOSTART="${K3D_AUTOSTART:-false}"
|
||||
AUTOSTART_BOOT_GRACE_SECONDS="${AUTOSTART_BOOT_GRACE_SECONDS:-300}"
|
||||
AUTOSTART_DOCKER_RECOVERY_SECONDS="${AUTOSTART_DOCKER_RECOVERY_SECONDS:-180}"
|
||||
AUTOSTART_K3D_START_TIMEOUT="${AUTOSTART_K3D_START_TIMEOUT:-360}"
|
||||
K3D_CONTAINER_RESTART_POLICY="${K3D_CONTAINER_RESTART_POLICY:-unless-stopped}"
|
||||
GITEA_RUNNER_RESTART_POLICY="${GITEA_RUNNER_RESTART_POLICY:-unless-stopped}"
|
||||
|
||||
# Bootstrap GitOps / App-of-Apps.
|
||||
BOOTSTRAP_ROOT_APP="${BOOTSTRAP_ROOT_APP:-true}"
|
||||
ROOT_APP_NAME="${ROOT_APP_NAME:-root-apps-registry}"
|
||||
@@ -172,7 +203,7 @@ sudo_cmd() {
|
||||
|
||||
require_commands() {
|
||||
local command_name
|
||||
for command_name in docker k3d kubectl curl git awk sed grep findmnt ss tar base64 tr; do
|
||||
for command_name in docker k3d kubectl curl git awk sed grep findmnt ss tar base64 tr flock; do
|
||||
have "$command_name" || die "No se encontró el comando requerido: ${command_name}"
|
||||
done
|
||||
}
|
||||
@@ -241,6 +272,7 @@ prepare_directories() {
|
||||
"$APP_DATA_PATH" \
|
||||
"${APP_DATA_PATH}/config" \
|
||||
"${APP_DATA_PATH}/secrets" \
|
||||
"${APP_DATA_PATH}/run" \
|
||||
"$BOOTSTRAP_DIR" \
|
||||
"$BACKUP_DIR" \
|
||||
"$GITEA_RUNNER_ROOT" \
|
||||
@@ -404,7 +436,57 @@ create_cluster() {
|
||||
|
||||
start_cluster() {
|
||||
log "Iniciando clúster existente ${CLUSTER_NAME}"
|
||||
k3d cluster start "$CLUSTER_NAME" --wait --timeout "${WAIT_SECONDS}s" || true
|
||||
k3d cluster start \
|
||||
"$CLUSTER_NAME" \
|
||||
--wait \
|
||||
--timeout "${AUTOSTART_K3D_START_TIMEOUT}s"
|
||||
}
|
||||
|
||||
cluster_api_ready() {
|
||||
[[ -s "$KUBECONFIG_PATH" ]] || return 1
|
||||
|
||||
KUBECONFIG="$KUBECONFIG_PATH" \
|
||||
kubectl get --raw='/readyz' \
|
||||
--request-timeout='5s' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
wait_for_cluster_api_quiet() {
|
||||
local timeout_seconds="$1"
|
||||
local deadline=$((SECONDS + timeout_seconds))
|
||||
|
||||
while (( SECONDS < deadline )); do
|
||||
if cluster_api_ready; then
|
||||
return 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
acquire_ensure_lock() {
|
||||
install -d -m 0750 "${APP_DATA_PATH}/run"
|
||||
exec 9>"${APP_DATA_PATH}/run/k3d-lab-ensure.lock"
|
||||
|
||||
if ! flock -n 9; then
|
||||
warn "Ya existe otra recuperación del laboratorio en ejecución; se omite esta iteración."
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_boot_grace() {
|
||||
[[ "$K3D_AUTOSTART" == "true" ]] || return 0
|
||||
|
||||
local uptime_seconds remaining
|
||||
uptime_seconds="$(
|
||||
awk '{print int($1)}' /proc/uptime 2>/dev/null || printf '0\n'
|
||||
)"
|
||||
|
||||
if (( uptime_seconds < AUTOSTART_BOOT_GRACE_SECONDS )); then
|
||||
remaining=$((AUTOSTART_BOOT_GRACE_SECONDS - uptime_seconds))
|
||||
log "Esperando ${remaining}s para que ZimaOS y Docker restauren las aplicaciones"
|
||||
sleep "$remaining"
|
||||
fi
|
||||
}
|
||||
|
||||
set_restart_policy() {
|
||||
@@ -413,9 +495,60 @@ set_restart_policy() {
|
||||
)
|
||||
|
||||
if (( ${#cluster_containers[@]} > 0 )); then
|
||||
docker update --restart=unless-stopped "${cluster_containers[@]}" >/dev/null
|
||||
ok "Política Docker unless-stopped aplicada a ${#cluster_containers[@]} contenedores del clúster."
|
||||
docker update \
|
||||
--restart="$K3D_CONTAINER_RESTART_POLICY" \
|
||||
"${cluster_containers[@]}" >/dev/null
|
||||
|
||||
ok "Política Docker ${K3D_CONTAINER_RESTART_POLICY} aplicada a ${#cluster_containers[@]} contenedores k3d."
|
||||
fi
|
||||
|
||||
if docker inspect "$GITEA_RUNNER_CONTAINER" >/dev/null 2>&1; then
|
||||
docker update \
|
||||
--restart="$GITEA_RUNNER_RESTART_POLICY" \
|
||||
"$GITEA_RUNNER_CONTAINER" >/dev/null
|
||||
|
||||
ok "Política Docker ${GITEA_RUNNER_RESTART_POLICY} aplicada a ${GITEA_RUNNER_CONTAINER}."
|
||||
fi
|
||||
}
|
||||
|
||||
start_runner_if_needed() {
|
||||
local runner_running
|
||||
|
||||
docker inspect "$GITEA_RUNNER_CONTAINER" >/dev/null 2>&1 || return 0
|
||||
|
||||
runner_running="$(
|
||||
docker inspect \
|
||||
-f '{{.State.Running}}' \
|
||||
"$GITEA_RUNNER_CONTAINER" 2>/dev/null || true
|
||||
)"
|
||||
|
||||
if [[ "$runner_running" != "true" ]]; then
|
||||
log "Iniciando ${GITEA_RUNNER_CONTAINER}"
|
||||
docker start "$GITEA_RUNNER_CONTAINER" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
recover_cluster_conservatively() {
|
||||
if cluster_api_ready; then
|
||||
ok "La API ya está disponible; no se ejecutará k3d cluster start."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "La API aún no responde; esperando restauración natural de Docker"
|
||||
|
||||
if wait_for_cluster_api_quiet "$AUTOSTART_DOCKER_RECOVERY_SECONDS"; then
|
||||
ok "La API se recuperó mediante las políticas de Docker."
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "La API no se recuperó en ${AUTOSTART_DOCKER_RECOVERY_SECONDS}s; se ejecutará un único k3d cluster start."
|
||||
|
||||
if ! start_cluster; then
|
||||
warn "k3d cluster start devolvió error; se validará igualmente la API porque K3s puede continuar iniciando."
|
||||
fi
|
||||
|
||||
wait_for_cluster_api_quiet "$AUTOSTART_K3D_START_TIMEOUT" ||
|
||||
die "La API de Kubernetes no se recuperó después del arranque conservador."
|
||||
}
|
||||
|
||||
write_kubeconfig() {
|
||||
@@ -845,28 +978,500 @@ spec:
|
||||
YAML
|
||||
}
|
||||
|
||||
install_headlamp_rbac() {
|
||||
[[ "$INSTALL_HEADLAMP_RBAC" == "true" ]] || return 0
|
||||
|
||||
log "Creando RBAC de Headlamp para laboratorio"
|
||||
portainer_fail_or_warn() {
|
||||
local message="$1"
|
||||
|
||||
kubectl create namespace headlamp --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl create serviceaccount headlamp-admin -n headlamp --dry-run=client -o yaml | kubectl apply -f -
|
||||
if [[ "$PORTAINER_REQUIRED" == "true" ]]; then
|
||||
die "$message"
|
||||
fi
|
||||
|
||||
cat <<'YAML' | kubectl apply -f -
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: headlamp-admin-binding
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp-admin
|
||||
namespace: headlamp
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
YAML
|
||||
warn "$message"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_portainer_server() {
|
||||
[[ "$INSTALL_PORTAINER_EDGE_AGENT" == "true" ]] || return 0
|
||||
|
||||
log "Validando Portainer Server"
|
||||
|
||||
if ! curl -ksSf \
|
||||
--connect-timeout 5 \
|
||||
--max-time 12 \
|
||||
-o /dev/null \
|
||||
"${PORTAINER_SERVER_URL%/}/api/status"; then
|
||||
portainer_fail_or_warn \
|
||||
"Portainer API no responde en ${PORTAINER_SERVER_URL%/}/api/status."
|
||||
return
|
||||
fi
|
||||
|
||||
# El servidor Portainer está en el propio ZimaOS. Validamos que el túnel
|
||||
# Standard Edge esté escuchando sin abrir sockets desde el contenedor.
|
||||
if [[ "$PORTAINER_TUNNEL_HOST" == "$LAB_HOST_IP" ||
|
||||
"$PORTAINER_TUNNEL_HOST" == "$EXPECTED_HOST_IP" ||
|
||||
"$PORTAINER_TUNNEL_HOST" == "127.0.0.1" ||
|
||||
"$PORTAINER_TUNNEL_HOST" == "localhost" ]]; then
|
||||
if ! ss -ltn |
|
||||
awk -v port=":${PORTAINER_TUNNEL_PORT}" '
|
||||
$1 == "LISTEN" && $4 ~ port "$" {found=1}
|
||||
END {exit(found ? 0 : 1)}
|
||||
'; then
|
||||
portainer_fail_or_warn \
|
||||
"Portainer no escucha el túnel Edge en ${PORTAINER_TUNNEL_HOST}:${PORTAINER_TUNNEL_PORT}."
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
ok "Portainer API y túnel Edge disponibles."
|
||||
}
|
||||
|
||||
portainer_resources_exist() {
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" >/dev/null 2>&1 &&
|
||||
kubectl get configmap "$PORTAINER_AGENT_CONFIGMAP" \
|
||||
-n "$PORTAINER_NAMESPACE" >/dev/null 2>&1 &&
|
||||
kubectl get secret "$PORTAINER_AGENT_SECRET" \
|
||||
-n "$PORTAINER_NAMESPACE" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
capture_portainer_edge_bundle() {
|
||||
[[ "$INSTALL_PORTAINER_EDGE_AGENT" == "true" ]] || return 0
|
||||
|
||||
have python3 ||
|
||||
die "Se requiere python3 para generar el respaldo declarativo del Portainer Edge Agent."
|
||||
|
||||
portainer_resources_exist ||
|
||||
die "No existe un Portainer Edge Agent completo para adoptar."
|
||||
|
||||
local temp_dir output_tmp
|
||||
temp_dir="$(mktemp -d)"
|
||||
output_tmp="${PORTAINER_BUNDLE_FILE}.tmp"
|
||||
|
||||
kubectl get namespace "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/00-namespace.json"
|
||||
|
||||
kubectl get serviceaccount "$PORTAINER_AGENT_SERVICEACCOUNT" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/10-serviceaccount.json"
|
||||
|
||||
kubectl get clusterrolebinding "$PORTAINER_AGENT_CLUSTERROLEBINDING" \
|
||||
-o json \
|
||||
> "${temp_dir}/20-clusterrolebinding.json"
|
||||
|
||||
kubectl get service "$PORTAINER_AGENT_SERVICE" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/30-service.json"
|
||||
|
||||
kubectl get configmap "$PORTAINER_AGENT_CONFIGMAP" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/40-configmap.json"
|
||||
|
||||
kubectl get secret "$PORTAINER_AGENT_SECRET" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/50-secret.json"
|
||||
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json \
|
||||
> "${temp_dir}/60-deployment.json"
|
||||
|
||||
python3 - "$temp_dir" "$output_tmp" <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
source = pathlib.Path(sys.argv[1])
|
||||
destination = pathlib.Path(sys.argv[2])
|
||||
|
||||
items = []
|
||||
|
||||
for filename in sorted(source.glob("*.json")):
|
||||
obj = json.loads(filename.read_text(encoding="utf-8"))
|
||||
metadata = obj.get("metadata", {})
|
||||
|
||||
clean_meta = {
|
||||
"name": metadata["name"],
|
||||
}
|
||||
|
||||
if metadata.get("namespace"):
|
||||
clean_meta["namespace"] = metadata["namespace"]
|
||||
|
||||
if metadata.get("labels"):
|
||||
clean_meta["labels"] = metadata["labels"]
|
||||
|
||||
annotations = {
|
||||
key: value
|
||||
for key, value in metadata.get("annotations", {}).items()
|
||||
if key not in {
|
||||
"kubectl.kubernetes.io/last-applied-configuration",
|
||||
"deployment.kubernetes.io/revision",
|
||||
"argocd.argoproj.io/tracking-id",
|
||||
}
|
||||
}
|
||||
if annotations:
|
||||
clean_meta["annotations"] = annotations
|
||||
|
||||
clean = {
|
||||
"apiVersion": obj["apiVersion"],
|
||||
"kind": obj["kind"],
|
||||
"metadata": clean_meta,
|
||||
}
|
||||
|
||||
kind = obj["kind"]
|
||||
|
||||
if kind == "Namespace":
|
||||
pass
|
||||
elif kind == "Secret":
|
||||
clean["type"] = obj.get("type", "Opaque")
|
||||
if obj.get("data"):
|
||||
clean["data"] = obj["data"]
|
||||
if obj.get("stringData"):
|
||||
clean["stringData"] = obj["stringData"]
|
||||
elif kind == "ConfigMap":
|
||||
if obj.get("data"):
|
||||
clean["data"] = obj["data"]
|
||||
if obj.get("binaryData"):
|
||||
clean["binaryData"] = obj["binaryData"]
|
||||
else:
|
||||
spec = obj.get("spec", {})
|
||||
|
||||
if kind == "Service":
|
||||
for key in (
|
||||
"clusterIP",
|
||||
"clusterIPs",
|
||||
"ipFamilies",
|
||||
"ipFamilyPolicy",
|
||||
"healthCheckNodePort",
|
||||
"allocateLoadBalancerNodePorts",
|
||||
):
|
||||
spec.pop(key, None)
|
||||
|
||||
for port in spec.get("ports", []):
|
||||
port.pop("nodePort", None)
|
||||
|
||||
if kind == "ServiceAccount":
|
||||
spec.pop("secrets", None)
|
||||
|
||||
clean["spec"] = spec
|
||||
|
||||
# ClusterRoleBinding no usa spec.
|
||||
if kind == "ClusterRoleBinding":
|
||||
clean.pop("spec", None)
|
||||
clean["roleRef"] = obj["roleRef"]
|
||||
clean["subjects"] = obj["subjects"]
|
||||
|
||||
if kind == "ServiceAccount":
|
||||
clean.pop("spec", None)
|
||||
if "automountServiceAccountToken" in obj:
|
||||
clean["automountServiceAccountToken"] = obj[
|
||||
"automountServiceAccountToken"
|
||||
]
|
||||
if obj.get("imagePullSecrets"):
|
||||
clean["imagePullSecrets"] = obj["imagePullSecrets"]
|
||||
|
||||
items.append(clean)
|
||||
|
||||
bundle = {
|
||||
"apiVersion": "v1",
|
||||
"kind": "List",
|
||||
"items": items,
|
||||
}
|
||||
|
||||
destination.write_text(
|
||||
json.dumps(bundle, indent=2, sort_keys=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
PY
|
||||
|
||||
install -m 0600 "$output_tmp" "$PORTAINER_BUNDLE_FILE"
|
||||
rm -rf "$temp_dir" "$output_tmp"
|
||||
|
||||
ok "Portainer Edge Agent adoptado en ${PORTAINER_BUNDLE_FILE}."
|
||||
warn "El bundle contiene EDGE_KEY; mantenlo con permisos 0600 y fuera de Gitea."
|
||||
}
|
||||
|
||||
restore_portainer_edge_agent() {
|
||||
[[ -s "$PORTAINER_BUNDLE_FILE" ]] ||
|
||||
return 1
|
||||
|
||||
log "Restaurando Portainer Edge Agent desde el bundle persistente"
|
||||
kubectl apply -f "$PORTAINER_BUNDLE_FILE"
|
||||
}
|
||||
|
||||
validate_portainer_edge_agent() {
|
||||
local edge_id insecure actual_image
|
||||
local secret_keys secret_encoded_bytes
|
||||
local desired_replicas ready_replicas available_replicas
|
||||
local pod_count ready_pod_count
|
||||
local configmap_referenced=false secret_referenced=false
|
||||
|
||||
portainer_resources_exist ||
|
||||
return 1
|
||||
|
||||
edge_id="$(
|
||||
kubectl get configmap "$PORTAINER_AGENT_CONFIGMAP" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.data.EDGE_ID}' 2>/dev/null || true
|
||||
)"
|
||||
|
||||
insecure="$(
|
||||
kubectl get configmap "$PORTAINER_AGENT_CONFIGMAP" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.data.EDGE_INSECURE_POLL}' 2>/dev/null || true
|
||||
)"
|
||||
|
||||
actual_image="$(
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].image}' \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
|
||||
secret_keys="$(
|
||||
kubectl get secret "$PORTAINER_AGENT_SECRET" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o go-template='{{range $key, $value := .data}}{{printf "%s\n" $key}}{{end}}' \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
|
||||
secret_encoded_bytes="$(
|
||||
kubectl get secret "$PORTAINER_AGENT_SECRET" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o go-template='{{range $key, $value := .data}}{{printf "%d\n" (len $value)}}{{end}}' \
|
||||
2>/dev/null |
|
||||
awk '{sum += $1} END {print sum + 0}'
|
||||
)"
|
||||
|
||||
[[ -n "$edge_id" ]] ||
|
||||
die "ConfigMap/${PORTAINER_AGENT_CONFIGMAP} no contiene EDGE_ID."
|
||||
|
||||
[[ -n "$secret_keys" && "${secret_encoded_bytes:-0}" -gt 0 ]] ||
|
||||
die "Secret/${PORTAINER_AGENT_SECRET} no contiene una credencial Edge válida."
|
||||
|
||||
# Portainer puede cambiar entre env, envFrom, secretKeyRef y volúmenes
|
||||
# según la versión del instalador. La validación no depende de una forma
|
||||
# concreta; solo registra si los objetos aparecen referenciados.
|
||||
if kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json |
|
||||
grep -Fq "\"${PORTAINER_AGENT_CONFIGMAP}\""; then
|
||||
configmap_referenced=true
|
||||
fi
|
||||
|
||||
if kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" -o json |
|
||||
grep -Fq "\"${PORTAINER_AGENT_SECRET}\""; then
|
||||
secret_referenced=true
|
||||
fi
|
||||
|
||||
if [[ "$configmap_referenced" == "true" ]]; then
|
||||
ok "Deployment referencia ConfigMap/${PORTAINER_AGENT_CONFIGMAP}."
|
||||
else
|
||||
warn "No se detectó una referencia textual al ConfigMap; se validará el runtime."
|
||||
fi
|
||||
|
||||
if [[ "$secret_referenced" == "true" ]]; then
|
||||
ok "Deployment referencia Secret/${PORTAINER_AGENT_SECRET}."
|
||||
else
|
||||
warn "No se detectó una referencia textual al Secret; se validará el runtime."
|
||||
fi
|
||||
|
||||
if [[ "$PORTAINER_EDGE_INSECURE_POLL" == "1" && "$insecure" != "1" ]]; then
|
||||
warn "EDGE_INSECURE_POLL esperado=1; actual=${insecure:-vacío}."
|
||||
fi
|
||||
|
||||
if [[ -n "$actual_image" && "$actual_image" != "$PORTAINER_AGENT_IMAGE" ]]; then
|
||||
warn "Imagen Portainer Agent actual=${actual_image}; referencia=${PORTAINER_AGENT_IMAGE}."
|
||||
fi
|
||||
|
||||
kubectl rollout status "deployment/${PORTAINER_AGENT_DEPLOYMENT}" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
--timeout="${PORTAINER_ROLLOUT_TIMEOUT}s"
|
||||
|
||||
desired_replicas="$(
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.spec.replicas}'
|
||||
)"
|
||||
|
||||
ready_replicas="$(
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.status.readyReplicas}'
|
||||
)"
|
||||
|
||||
available_replicas="$(
|
||||
kubectl get deployment "$PORTAINER_AGENT_DEPLOYMENT" \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-o jsonpath='{.status.availableReplicas}'
|
||||
)"
|
||||
|
||||
pod_count="$(
|
||||
kubectl get pods \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-l app=portainer-agent \
|
||||
--no-headers 2>/dev/null |
|
||||
wc -l |
|
||||
tr -d ' '
|
||||
)"
|
||||
|
||||
ready_pod_count="$(
|
||||
kubectl get pods \
|
||||
-n "$PORTAINER_NAMESPACE" \
|
||||
-l app=portainer-agent \
|
||||
-o go-template='{{range .items}}{{range .status.conditions}}{{if and (eq .type "Ready") (eq .status "True")}}{{printf "1\n"}}{{end}}{{end}}{{end}}' \
|
||||
2>/dev/null |
|
||||
wc -l |
|
||||
tr -d ' '
|
||||
)"
|
||||
|
||||
[[ "${desired_replicas:-0}" -gt 0 ]] ||
|
||||
die "Deployment/${PORTAINER_AGENT_DEPLOYMENT} tiene réplicas deseadas inválidas."
|
||||
|
||||
[[ "${ready_replicas:-0}" -eq "$desired_replicas" ]] ||
|
||||
die "Portainer Agent Ready=${ready_replicas:-0}; esperado=${desired_replicas}."
|
||||
|
||||
[[ "${available_replicas:-0}" -eq "$desired_replicas" ]] ||
|
||||
die "Portainer Agent Available=${available_replicas:-0}; esperado=${desired_replicas}."
|
||||
|
||||
# Algunos manifiestos cambian las etiquetas. El rollout es la fuente
|
||||
# principal; el conteo por selector se usa como información adicional.
|
||||
if [[ "${pod_count:-0}" -gt 0 ]]; then
|
||||
printf 'Pods Portainer detectados : %s\n' "$pod_count"
|
||||
printf 'Pods Portainer Ready : %s\n' "$ready_pod_count"
|
||||
fi
|
||||
|
||||
ok "Portainer Edge Agent disponible en namespace ${PORTAINER_NAMESPACE}."
|
||||
}
|
||||
|
||||
ensure_portainer_edge_agent() {
|
||||
[[ "$INSTALL_PORTAINER_EDGE_AGENT" == "true" ]] || return 0
|
||||
|
||||
validate_portainer_server
|
||||
|
||||
if ! portainer_resources_exist; then
|
||||
if ! restore_portainer_edge_agent; then
|
||||
portainer_fail_or_warn \
|
||||
"No existe Portainer Edge Agent ni bundle persistente. Ejecuta el comando generado por Portainer y luego: $0 portainer-adopt"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! validate_portainer_edge_agent; then
|
||||
portainer_fail_or_warn "Portainer Edge Agent no quedó disponible."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$PORTAINER_CAPTURE_ON_SUCCESS" == "true" &&
|
||||
! -s "$PORTAINER_BUNDLE_FILE" ]]; then
|
||||
capture_portainer_edge_bundle
|
||||
fi
|
||||
}
|
||||
|
||||
portainer_adopt_mode() {
|
||||
require_commands
|
||||
ensure_docker
|
||||
validate_data_mount
|
||||
validate_host_ip
|
||||
prepare_directories
|
||||
|
||||
cluster_exists ||
|
||||
die "No existe ${CLUSTER_NAME}."
|
||||
|
||||
write_kubeconfig
|
||||
wait_for_api
|
||||
validate_portainer_server
|
||||
validate_portainer_edge_agent
|
||||
capture_portainer_edge_bundle
|
||||
}
|
||||
|
||||
headlamp_git_references() {
|
||||
local found=0 path
|
||||
|
||||
if [[ -d "${APP_REGISTRY_CACHE_DIR}/workloads/headlamp" ]]; then
|
||||
printf '%s\n' "${APP_REGISTRY_CACHE_DIR}/workloads/headlamp"
|
||||
found=1
|
||||
fi
|
||||
|
||||
if [[ -d "$APP_REGISTRY_CACHE_DIR" ]]; then
|
||||
while IFS= read -r path; do
|
||||
printf '%s\n' "$path"
|
||||
found=1
|
||||
done < <(
|
||||
grep -RIl \
|
||||
--exclude-dir=.git \
|
||||
--include='*.yaml' \
|
||||
--include='*.yml' \
|
||||
-E 'name:[[:space:]]*headlamp-app([[:space:]]|$)' \
|
||||
"$APP_REGISTRY_CACHE_DIR" 2>/dev/null || true
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ -d "$PLATFORM_INFRA_CACHE_DIR" ]]; then
|
||||
while IFS= read -r path; do
|
||||
printf '%s\n' "$path"
|
||||
found=1
|
||||
done < <(
|
||||
grep -RIl \
|
||||
--exclude-dir=.git \
|
||||
--include='*.yaml' \
|
||||
--include='*.yml' \
|
||||
-E 'name:[[:space:]]*headlamp-governance([[:space:]]|$)' \
|
||||
"$PLATFORM_INFRA_CACHE_DIR" 2>/dev/null || true
|
||||
)
|
||||
|
||||
while IFS= read -r path; do
|
||||
printf '%s\n' "$path"
|
||||
found=1
|
||||
done < <(
|
||||
find "$PLATFORM_INFRA_CACHE_DIR" \
|
||||
-path '*/.git' -prune -o \
|
||||
-type d -name 'headlamp-governance' -print \
|
||||
2>/dev/null || true
|
||||
)
|
||||
fi
|
||||
|
||||
return "$found"
|
||||
}
|
||||
|
||||
retire_headlamp_mode() {
|
||||
require_commands
|
||||
ensure_docker
|
||||
validate_data_mount
|
||||
validate_host_ip
|
||||
prepare_directories
|
||||
|
||||
cluster_exists ||
|
||||
die "No existe ${CLUSTER_NAME}."
|
||||
|
||||
write_kubeconfig
|
||||
wait_for_api
|
||||
sync_gitops_repositories
|
||||
|
||||
local references
|
||||
references="$(headlamp_git_references || true)"
|
||||
|
||||
if [[ -n "$references" ]]; then
|
||||
printf '%s\n' "$references" >&2
|
||||
die "Headlamp todavía está declarado en Gitea. Elimina esas rutas, haz commit/push y repite retire-headlamp."
|
||||
fi
|
||||
|
||||
log "Retirando residuos de Headlamp"
|
||||
|
||||
kubectl delete application \
|
||||
headlamp-app \
|
||||
headlamp-governance \
|
||||
-n argocd \
|
||||
--ignore-not-found=true \
|
||||
--wait=false
|
||||
|
||||
kubectl delete clusterrolebinding \
|
||||
headlamp-admin-binding \
|
||||
--ignore-not-found=true
|
||||
|
||||
kubectl delete namespace headlamp \
|
||||
--ignore-not-found=true \
|
||||
--wait=false
|
||||
|
||||
ok "Headlamp retirado. Portainer queda como consola Kubernetes."
|
||||
}
|
||||
|
||||
apply_postdeploy_rbac() {
|
||||
@@ -2541,7 +3146,9 @@ print_credentials() {
|
||||
printf ' NPM Argo CD : http://%s:%s\n' "$LAB_HOST_IP" "$HTTP_PORT"
|
||||
printf ' Host Argo CD : %s\n' "$ARGOCD_HOST"
|
||||
printf ' ArgoCD admin : %s\n' "$argocd_password"
|
||||
printf ' Token Headlamp : kubectl create token headlamp-admin -n headlamp\n'
|
||||
printf ' Portainer API : %s\n' "$PORTAINER_SERVER_URL"
|
||||
printf ' Portainer Edge : deployment/%s namespace/%s\n' \
|
||||
"$PORTAINER_AGENT_DEPLOYMENT" "$PORTAINER_NAMESPACE"
|
||||
printf ' Kubeconfig : %s\n' "$KUBECONFIG_PATH"
|
||||
printf ' Root Application: %s -> %s/%s@%s\n' \
|
||||
"$ROOT_APP_NAME" "$ROOT_APP_REPO_URL" "$ROOT_APP_REPO_PATH" "$ROOT_APP_REVISION"
|
||||
@@ -2596,6 +3203,16 @@ status_report() {
|
||||
else
|
||||
warn "No existe ${GITEA_RUNNER_CONTAINER}."
|
||||
fi
|
||||
|
||||
log "Portainer"
|
||||
printf 'Server: %s\n' "$PORTAINER_SERVER_URL"
|
||||
printf 'Tunnel: %s:%s\n' "$PORTAINER_TUNNEL_HOST" "$PORTAINER_TUNNEL_PORT"
|
||||
|
||||
if cluster_exists && kubectl get namespace "$PORTAINER_NAMESPACE" >/dev/null 2>&1; then
|
||||
kubectl get deployment,service,configmap,secret -n "$PORTAINER_NAMESPACE" 2>/dev/null || true
|
||||
else
|
||||
warn "No existe el namespace ${PORTAINER_NAMESPACE}."
|
||||
fi
|
||||
}
|
||||
|
||||
backup_cluster_state() {
|
||||
@@ -2646,6 +3263,8 @@ ensure_cluster_only() {
|
||||
ensure_docker
|
||||
validate_data_mount
|
||||
validate_host_ip
|
||||
acquire_ensure_lock
|
||||
wait_for_boot_grace
|
||||
|
||||
if ! cluster_exists; then
|
||||
if cluster_containers_exist; then
|
||||
@@ -2654,8 +3273,10 @@ ensure_cluster_only() {
|
||||
die "El clúster ${CLUSTER_NAME} no existe. Ejecuta bootstrap manualmente."
|
||||
fi
|
||||
|
||||
start_cluster
|
||||
set_restart_policy
|
||||
start_runner_if_needed
|
||||
recover_cluster_conservatively
|
||||
|
||||
write_kubeconfig
|
||||
wait_for_api
|
||||
wait_for_nodes
|
||||
@@ -2663,6 +3284,7 @@ ensure_cluster_only() {
|
||||
ensure_root_application_present
|
||||
auto_recover_monitoring_if_stale
|
||||
ensure_gitea_runner
|
||||
ensure_portainer_edge_agent
|
||||
}
|
||||
|
||||
|
||||
@@ -2726,6 +3348,7 @@ install_autostart() {
|
||||
local service_user="${SUDO_USER:-devops}"
|
||||
local service_home service_group docker_root installed_script
|
||||
local k3d_path kubectl_path docker_path curl_path tool_path tool_dir
|
||||
local service_result service_status
|
||||
|
||||
service_home="$(getent passwd "$service_user" | cut -d: -f6)"
|
||||
service_group="$(id -gn "$service_user")"
|
||||
@@ -2804,19 +3427,22 @@ install_autostart() {
|
||||
|
||||
install -d -m 0755 \
|
||||
/etc/default \
|
||||
/etc/systemd/system \
|
||||
/etc/systemd/system/docker.service.d
|
||||
/etc/systemd/system
|
||||
|
||||
systemctl enable docker.service >/dev/null
|
||||
if systemctl list-unit-files containerd.service >/dev/null 2>&1; then
|
||||
systemctl enable containerd.service >/dev/null || true
|
||||
fi
|
||||
|
||||
cat > /etc/systemd/system/docker.service.d/10-k3d-storage.conf <<UNIT
|
||||
[Unit]
|
||||
RequiresMountsFor=${APP_DATA_PATH} ${docker_root}
|
||||
After=local-fs.target
|
||||
UNIT
|
||||
# v4.5.5: el laboratorio nunca debe modificar las dependencias de
|
||||
# docker.service. Un RequiresMountsFor sobre almacenamiento administrado por
|
||||
# ZimaOS puede impedir el arranque global de Docker y, con ello, de todas las
|
||||
# aplicaciones del NAS. Eliminamos el drop-in creado por versiones previas.
|
||||
rm -f /etc/systemd/system/docker.service.d/10-k3d-storage.conf
|
||||
rmdir /etc/systemd/system/docker.service.d 2>/dev/null || true
|
||||
|
||||
set_restart_policy
|
||||
start_runner_if_needed
|
||||
|
||||
cat > /etc/default/k3d-lab <<ENV
|
||||
CLUSTER_NAME=${CLUSTER_NAME}
|
||||
@@ -2829,6 +3455,11 @@ ARGOCD_ROLLOUT_TIMEOUT=${ARGOCD_ROLLOUT_TIMEOUT}
|
||||
AGENT_RECOVERY_WAIT=${AGENT_RECOVERY_WAIT}
|
||||
AUTO_REREGISTER_STALE_AGENTS=${AUTO_REREGISTER_STALE_AGENTS}
|
||||
CHECK_INGRESS_AFTER_BOOT=${CHECK_INGRESS_AFTER_BOOT}
|
||||
AUTOSTART_BOOT_GRACE_SECONDS=${AUTOSTART_BOOT_GRACE_SECONDS}
|
||||
AUTOSTART_DOCKER_RECOVERY_SECONDS=${AUTOSTART_DOCKER_RECOVERY_SECONDS}
|
||||
AUTOSTART_K3D_START_TIMEOUT=${AUTOSTART_K3D_START_TIMEOUT}
|
||||
K3D_CONTAINER_RESTART_POLICY=${K3D_CONTAINER_RESTART_POLICY}
|
||||
GITEA_RUNNER_RESTART_POLICY=${GITEA_RUNNER_RESTART_POLICY}
|
||||
K3D_BIN=${k3d_path}
|
||||
KUBECTL_BIN=${kubectl_path}
|
||||
DOCKER_BIN=${docker_path}
|
||||
@@ -2858,6 +3489,24 @@ GITEA_RUNNER_DATA_DIR=${GITEA_RUNNER_DATA_DIR}
|
||||
GITEA_RUNNER_IMAGE_FILE=${GITEA_RUNNER_IMAGE_FILE}
|
||||
GITEA_RUNNER_TOKEN_FILE=${GITEA_RUNNER_TOKEN_FILE}
|
||||
MIGRATE_EXISTING_RUNNER=false
|
||||
INSTALL_PORTAINER_EDGE_AGENT=${INSTALL_PORTAINER_EDGE_AGENT}
|
||||
PORTAINER_REQUIRED=${PORTAINER_REQUIRED}
|
||||
PORTAINER_NAMESPACE=${PORTAINER_NAMESPACE}
|
||||
PORTAINER_AGENT_DEPLOYMENT=${PORTAINER_AGENT_DEPLOYMENT}
|
||||
PORTAINER_AGENT_SERVICE=${PORTAINER_AGENT_SERVICE}
|
||||
PORTAINER_AGENT_SERVICEACCOUNT=${PORTAINER_AGENT_SERVICEACCOUNT}
|
||||
PORTAINER_AGENT_CLUSTERROLEBINDING=${PORTAINER_AGENT_CLUSTERROLEBINDING}
|
||||
PORTAINER_AGENT_CONFIGMAP=${PORTAINER_AGENT_CONFIGMAP}
|
||||
PORTAINER_AGENT_SECRET=${PORTAINER_AGENT_SECRET}
|
||||
PORTAINER_AGENT_IMAGE=${PORTAINER_AGENT_IMAGE}
|
||||
PORTAINER_SERVER_URL=${PORTAINER_SERVER_URL}
|
||||
PORTAINER_TUNNEL_HOST=${PORTAINER_TUNNEL_HOST}
|
||||
PORTAINER_TUNNEL_PORT=${PORTAINER_TUNNEL_PORT}
|
||||
PORTAINER_EDGE_INSECURE_POLL=${PORTAINER_EDGE_INSECURE_POLL}
|
||||
PORTAINER_ROLLOUT_TIMEOUT=${PORTAINER_ROLLOUT_TIMEOUT}
|
||||
PORTAINER_BUNDLE_FILE=${PORTAINER_BUNDLE_FILE}
|
||||
PORTAINER_CAPTURE_ON_SUCCESS=${PORTAINER_CAPTURE_ON_SUCCESS}
|
||||
REMOVE_LEGACY_HEADLAMP=${REMOVE_LEGACY_HEADLAMP}
|
||||
APP_REGISTRY_CACHE_DIR=${APP_REGISTRY_CACHE_DIR}
|
||||
APP_REGISTRY_ROOT_MANIFEST=${APP_REGISTRY_ROOT_MANIFEST}
|
||||
APP_REGISTRY_COMMIT_FILE=${APP_REGISTRY_COMMIT_FILE}
|
||||
@@ -2893,7 +3542,8 @@ Description=Recuperar y validar laboratorio k3d
|
||||
Wants=network-online.target
|
||||
After=local-fs.target network-online.target docker.service
|
||||
Requires=docker.service
|
||||
RequiresMountsFor=${APP_DATA_PATH} ${docker_root}
|
||||
# El propio modo ensure valida /DATA y sale de forma segura si todavía no está montado.
|
||||
# No se agregan dependencias de montaje que puedan bloquear Docker o el arranque global.
|
||||
StartLimitIntervalSec=15min
|
||||
StartLimitBurst=3
|
||||
|
||||
@@ -2906,8 +3556,9 @@ Environment=HOME=${service_home}
|
||||
Environment=KUBECONFIG=${KUBECONFIG_PATH}
|
||||
Environment="PATH=${tool_path}"
|
||||
EnvironmentFile=-/etc/default/k3d-lab
|
||||
Environment=K3D_AUTOSTART=true
|
||||
ExecStart=/bin/bash ${installed_script} ensure
|
||||
TimeoutStartSec=900
|
||||
TimeoutStartSec=1500
|
||||
Restart=no
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@@ -2918,33 +3569,43 @@ UNIT
|
||||
Description=Watchdog del laboratorio k3d
|
||||
|
||||
[Timer]
|
||||
OnBootSec=90s
|
||||
OnUnitInactiveSec=5min
|
||||
# Docker restaura primero k3d y el runner usando unless-stopped. El watchdog
|
||||
# interviene después y solo llama k3d cluster start si la API sigue caída.
|
||||
OnBootSec=5min
|
||||
OnUnitInactiveSec=10min
|
||||
AccuracySec=20s
|
||||
Persistent=true
|
||||
Persistent=false
|
||||
Unit=k3d-lab-ensure.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
UNIT
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now k3d-lab-ensure.timer
|
||||
# Evitamos que el timer y la prueba inicial intenten arrancar el
|
||||
# oneshot al mismo tiempo. También limpiamos contadores históricos de
|
||||
# StartLimit antes de probar la unidad recién instalada.
|
||||
systemctl stop k3d-lab-ensure.timer >/dev/null 2>&1 || true
|
||||
systemctl stop k3d-lab-ensure.service >/dev/null 2>&1 || true
|
||||
systemctl reset-failed k3d-lab-ensure.service >/dev/null 2>&1 || true
|
||||
|
||||
if ! systemctl start k3d-lab-ensure.service; then
|
||||
systemctl status k3d-lab-ensure.service --no-pager -l || true
|
||||
journalctl -u k3d-lab-ensure.service -n 200 --no-pager || true
|
||||
die "La prueba inicial de k3d-lab-ensure.service falló."
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now k3d-lab-ensure.timer >/dev/null
|
||||
|
||||
systemctl is-active --quiet k3d-lab-ensure.timer ||
|
||||
die "El timer k3d-lab-ensure.timer no quedó activo."
|
||||
|
||||
# No ejecutamos inmediatamente el oneshot durante la instalación. Así
|
||||
# evitamos competir con un clúster que todavía se está recuperando.
|
||||
|
||||
ok "Autostart instalado para ${service_user}."
|
||||
printf 'Script persistente: %s\n' "$installed_script"
|
||||
printf 'k3d para systemd: %s\n' "$k3d_path"
|
||||
printf 'kubectl para systemd: %s\n' "$kubectl_path"
|
||||
printf 'PATH para systemd: %s\n' "$tool_path"
|
||||
printf 'DockerRootDir protegido por systemd: %s\n' "$docker_root"
|
||||
printf 'DockerRootDir detectado (sin modificar docker.service): %s\n' "$docker_root"
|
||||
printf 'Logs: journalctl -u k3d-lab-ensure.service -n 200 --no-pager\n'
|
||||
printf 'Nota: el drop-in de Docker será plenamente verificable después del reinicio de prueba.\n'
|
||||
printf 'Seguridad: docker.service no contiene dependencias del laboratorio.\n'
|
||||
printf 'Orden estable: Docker unless-stopped primero; watchdog después de 5 minutos.\n'
|
||||
}
|
||||
|
||||
main() {
|
||||
@@ -3030,6 +3691,26 @@ main() {
|
||||
validate_postdeploy_credentials
|
||||
return
|
||||
;;
|
||||
portainer)
|
||||
require_commands
|
||||
ensure_docker
|
||||
validate_data_mount
|
||||
validate_host_ip
|
||||
prepare_directories
|
||||
cluster_exists || die "No existe ${CLUSTER_NAME}."
|
||||
write_kubeconfig
|
||||
wait_for_api
|
||||
ensure_portainer_edge_agent
|
||||
return
|
||||
;;
|
||||
portainer-adopt)
|
||||
portainer_adopt_mode
|
||||
return
|
||||
;;
|
||||
retire-headlamp)
|
||||
retire_headlamp_mode
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
require_commands
|
||||
@@ -3056,7 +3737,7 @@ main() {
|
||||
reset_cluster
|
||||
;;
|
||||
*)
|
||||
die "Modo no válido: ${MODE}. Usa bootstrap, recover, ensure, status, repo-sync, gitops, runner, monitoring-diagnose, monitoring-recover, postdeploy-secrets, install-autostart o reset."
|
||||
die "Modo no válido: ${MODE}. Usa bootstrap, recover, ensure, status, repo-sync, gitops, runner, monitoring-diagnose, monitoring-recover, postdeploy-secrets, portainer, portainer-adopt, retire-headlamp, install-autostart o reset."
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -3067,7 +3748,6 @@ main() {
|
||||
|
||||
install_argocd
|
||||
apply_argocd_ingress
|
||||
install_headlamp_rbac
|
||||
apply_postdeploy_rbac
|
||||
generate_postdeploy_credentials
|
||||
sync_gitops_repositories
|
||||
@@ -3081,6 +3761,7 @@ main() {
|
||||
auto_recover_monitoring_if_stale
|
||||
wait_for_gitops_apps_health
|
||||
ensure_gitea_runner
|
||||
ensure_portainer_edge_agent
|
||||
validate_runtime_after_boot
|
||||
|
||||
log "Validación final"
|
||||
|
||||
Reference in New Issue
Block a user