diff --git a/add-on/AgentPlatform/Dockerfile.byo-agent-example b/add-on/AgentPlatform/Dockerfile.byo-agent-example new file mode 100644 index 0000000..d17151d --- /dev/null +++ b/add-on/AgentPlatform/Dockerfile.byo-agent-example @@ -0,0 +1,21 @@ +# Dockerfile di esempio — containerizza un flow esportato da Langflow +# come Python app standalone, da usare nel percorso BYO (Esempio B). + +FROM python:3.11-slim + +WORKDIR /app + +# requirements.txt generato/estratto insieme all'export del flow +# (langflow, langgraph-checkpointer-kagent, e le dipendenze del tuo flow) +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Codice esportato dal builder low-code + eventuali customizzazioni +# del developer (es. integrazione kagentCheckpointer se usi LangGraph) +COPY . . + +# L'agente deve esporre un endpoint compatibile A2A sulla porta +# dichiarata in byo.deployment.port dell'Agent CR +EXPOSE 8080 + +CMD ["python", "agent_server.py"] diff --git a/add-on/AgentPlatform/IDP-AgentPlatform.docx b/add-on/AgentPlatform/IDP-AgentPlatform.docx new file mode 100644 index 0000000..af2d717 Binary files /dev/null and b/add-on/AgentPlatform/IDP-AgentPlatform.docx differ diff --git a/add-on/AgentPlatform/Langflow/IDP-AgentPlatform-formattato.docx b/add-on/AgentPlatform/Langflow/IDP-AgentPlatform-formattato.docx new file mode 100644 index 0000000..652b8e8 Binary files /dev/null and b/add-on/AgentPlatform/Langflow/IDP-AgentPlatform-formattato.docx differ diff --git a/add-on/AgentPlatform/Langflow/Langflow installazione.txt b/add-on/AgentPlatform/Langflow/Langflow installazione.txt new file mode 100644 index 0000000..3217289 --- /dev/null +++ b/add-on/AgentPlatform/Langflow/Langflow installazione.txt @@ -0,0 +1,7 @@ +# crea secret con langflow-secrets-setup.sh + + +# Poi installa con i valori personalizzati +helm install langflow-ide langflow/langflow-ide \ + -f langflow-values.yaml \ + -n langflow-team-alpha --create-namespace \ No newline at end of file diff --git a/add-on/AgentPlatform/Langflow/langflow-httproute.yaml b/add-on/AgentPlatform/Langflow/langflow-httproute.yaml new file mode 100644 index 0000000..e07615a --- /dev/null +++ b/add-on/AgentPlatform/Langflow/langflow-httproute.yaml @@ -0,0 +1,61 @@ +# HTTPRoute (Gateway API) per Langflow IDE +# Presuppone un Gateway nginx già esistente nel cluster (nginx Gateway +# Fabric), referenziato come parentRef qui sotto. + +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: langflow-team-alpha + namespace: langflow-team-alpha +spec: + parentRefs: + - name: nginx-gateway # nome del Gateway condiviso — verifica con: + namespace: nginx-gateway # kubectl get gateway -A + sectionName: https # nome del listener HTTPS sul Gateway + + hostnames: + - "langflow-team-alpha.pigreco66.it" + + rules: + # UI/editor visuale (frontend) — porta 8080 come da doc Langflow + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: langflow-ide-frontend # verifica nome esatto: kubectl get svc -n langflow-team-alpha + port: 8080 + + # API backend, se vuoi esporla separatamente (es. per invocazione + # programmatica di un flow senza passare dall'editor) — porta 7860 + - matches: + - path: + type: PathPrefix + value: /api + backendRefs: + - name: langflow-ide-backend # verifica nome esatto: kubectl get svc -n langflow-team-alpha + port: 7860 +--- +# TLS: con Gateway API il certificato si referenzia sul Gateway stesso +# (non sull'HTTPRoute). Se il Gateway condiviso non ha già un listener +# per questo hostname, serve aggiungerlo lì, es.: +# +# apiVersion: gateway.networking.k8s.io/v1 +# kind: Gateway +# metadata: +# name: nginx-gateway +# namespace: nginx-gateway +# spec: +# gatewayClassName: nginx +# listeners: +# - name: https +# protocol: HTTPS +# port: 443 +# hostname: "*.pigreco66.it" +# tls: +# mode: Terminate +# certificateRefs: +# - name: wildcard-pigreco66-it-tls +# allowedRoutes: +# namespaces: +# from: All diff --git a/add-on/AgentPlatform/Langflow/langflow-secrets-setup.sh b/add-on/AgentPlatform/Langflow/langflow-secrets-setup.sh new file mode 100644 index 0000000..6f4c48a --- /dev/null +++ b/add-on/AgentPlatform/Langflow/langflow-secrets-setup.sh @@ -0,0 +1,35 @@ +# Creazione dei Secret richiesti da langflow-values.yaml +# Namespace di esempio: langflow-team-alpha (adatta al tuo tenant) + +# 1. Secret connessione database Postgres +kubectl create secret generic langflow-db-secret \ + -n langflow-team-alpha \ + --from-literal=connection-string="postgresql://user:pass@host:5432/langflow" + +# 2. Secret credenziali admin (superuser Langflow) +kubectl create secret generic langflow-admin-secret \ + -n langflow-team-alpha \ + --from-literal=username="admin" \ + --from-literal=password="" + +# 3. Secret credenziali LLM — STESSO Secret già usato per kagent nel +# golden path di onboarding-team: se lo hai già creato per kagent +# in questo namespace, questo passaggio è già fatto, verifica solo +# che contenga le chiavi coi nomi attesi (vedi sotto). +# +# Se non esiste ancora, crealo così (aggiungi/rimuovi provider a +# seconda di cosa serve al team): +kubectl create secret generic llm-credentials \ + -n langflow-team-alpha \ + --from-literal=openai-api-key="sk-..." \ + --from-literal=anthropic-api-key="sk-ant-..." + +# Se il Secret esiste già (es. creato per kagent) e vuoi solo +# aggiungere/aggiornare una chiave senza ricrearlo da zero: +kubectl patch secret llm-credentials -n langflow-team-alpha \ + --type=json \ + -p='[{"op":"add","path":"/data/openai-api-key","value":"'$(echo -n "sk-..." | base64)'"}]' + +# 4. Verifica che tutti i Secret siano presenti prima di installare/ +# aggiornare la release Helm +kubectl get secrets -n langflow-team-alpha diff --git a/add-on/AgentPlatform/Langflow/langflow-values.yaml b/add-on/AgentPlatform/Langflow/langflow-values.yaml new file mode 100644 index 0000000..632ff97 --- /dev/null +++ b/add-on/AgentPlatform/Langflow/langflow-values.yaml @@ -0,0 +1,87 @@ +# values.yaml — Langflow IDE, personalizzato per un namespace-tenant +# Uso: helm install langflow-ide langflow/langflow-ide -f values.yaml -n + +langflow: + backend: + image: + repository: langflowai/langflow + tag: "1.10.0" # fissa una versione esplicita, evita 'latest' in produzione + + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + + # Variabili LLM/DB — usa Secret, mai valori in chiaro qui + env: + - name: LANGFLOW_DATABASE_URL + valueFrom: + secretKeyRef: + name: langflow-db-secret + key: connection-string + - name: LANGFLOW_SUPERUSER + valueFrom: + secretKeyRef: + name: langflow-admin-secret + key: username + - name: LANGFLOW_SUPERUSER_PASSWORD + valueFrom: + secretKeyRef: + name: langflow-admin-secret + key: password + # Rimuove automaticamente eventuali API key salvate nei flow prima + # di persisterli a DB — best practice di sicurezza, seconda linea + # di difesa oltre alle Global Variable qui sotto + - name: LANGFLOW_REMOVE_API_KEYS + value: "true" + + # --- Pre-caricamento credenziali LLM come Global Variable --- + # Riusa lo stesso Secret 'llm-credentials' già previsto per kagent + # in fase di onboarding del team (vedi golden path Backstage). + # I developer trovano le chiavi già pronte nel dropdown Global + # Variable, senza mai vederne il valore reale. + - name: LANGFLOW_STORE_ENVIRONMENT_VARIABLES + value: "true" + - name: LANGFLOW_VARIABLES_TO_GET_FROM_ENVIRONMENT + value: "OPENAI_API_KEY,ANTHROPIC_API_KEY" + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: llm-credentials + key: openai-api-key + optional: true # non tutti i team useranno tutti i provider + - name: ANTHROPIC_API_KEY + valueFrom: + secretKeyRef: + name: llm-credentials + key: anthropic-api-key + optional: true + + frontend: + image: + repository: langflowai/langflow + tag: "1.10.0" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# Ingress classico DISABILITATO — l'esposizione avviene via Gateway API +# (nginx Gateway Fabric), vedi manifest separato langflow-httproute.yaml +ingress: + enabled: false + +# Il Service del frontend resta ClusterIP (default del chart), sarà +# l'HTTPRoute a instradare il traffico dal Gateway verso questo Service + +# Persistenza dei flow salvati (altrimenti persi al riavvio del pod) +persistence: + enabled: true + size: 5Gi + storageClassName: standard # adatta alla tua StorageClass diff --git a/add-on/AgentPlatform/agent-declarative-vs-byo.yaml b/add-on/AgentPlatform/agent-declarative-vs-byo.yaml new file mode 100644 index 0000000..8a43ca1 --- /dev/null +++ b/add-on/AgentPlatform/agent-declarative-vs-byo.yaml @@ -0,0 +1,166 @@ +# ===================================================================== +# ESEMPIO A — Agent dichiarativo nativo (kagent esegue direttamente) +# Caso d'uso: flow Langflow/Flowise semplice — un LLM + un paio di tool, +# nessuna logica custom complessa. Tradotto 1:1 in prompt + tool MCP. +# ===================================================================== +apiVersion: kagent.dev/v1alpha2 +kind: Agent +metadata: + name: k8s-troubleshooter + namespace: team-alpha +spec: + description: Agente di troubleshooting Kubernetes + type: Declarative + declarative: + modelConfig: gpt4o-config + systemMessage: | + Sei un agente di troubleshooting per Kubernetes... + a2aConfig: + skills: + - id: diagnose-pod-issue + name: Diagnose Pod Issue + description: Investiga problemi su pod/servizi Kubernetes + inputModes: ["text"] + outputModes: ["text"] + tags: ["kubernetes"] + tools: + - type: McpServer + mcpServer: + name: kagent-tool-server + kind: RemoteMCPServer + toolNames: + - k8s_get_pods + - k8s_describe_pod + - k8s_get_logs +--- +apiVersion: kagent.dev/v1alpha1 +kind: ModelConfig +metadata: + name: gpt4o-config + namespace: team-alpha +spec: + provider: OpenAI + model: gpt-4o + apiKeySecretRef: + name: llm-credentials + key: openai-api-key + + + + +# invocazione +Le 4 modalità di invocazione +1. Dashboard kagent (per gli utenti umani, uso interattivo) +bash +kagent dashboard + +Apre una UI web dove cerchi k8s-troubleshooter nella lista e chatti direttamente — il modo più immediato per un developer che vuole testare l'agente senza altri strumenti. + +2. CLI kagent (per script, pipeline, debug da terminale) +bash +kagent invoke --agent k8s-troubleshooter -n team-alpha \ + --task "Il pod payment-service-7d9f in default sta crashando, investiga" +3. Chiamata diretta via A2A REST (per integrazione da altri sistemi/servizi) + +Ogni Agent espone un endpoint A2A standard, con un "agent card" che ne descrive le capability: + +bash +kubectl port-forward -n kagent svc/kagent-service 8083:8083 + +# scopri le capability dell'agente +curl localhost:8083/api/a2a/team-alpha/k8s-troubleshooter/.well-known/agent.json + +# invocalo +curl -X POST localhost:8083/api/a2a/team-alpha/k8s-troubleshooter/ \ + -H "Content-Type: application/json" \ + -d '{"task": "Perché il pod payment-service sta in CrashLoopBackOff?"}' + +Questa è la via che useresti se, ad esempio, un tuo servizio interno (o un'automazione CI/CD) deve invocare l'agente programmaticamente. + +4. Canali chat (Slack/Teams/Discord/Telegram) — via AgentHarness CRD + +Se vuoi che i developer interroghino l'agente direttamente da Slack invece che aprire la dashboard, kagent ha una CRD dedicata (AgentHarness) che fa da ponte: + +yaml +apiVersion: kagent.dev/v1alpha2 +kind: AgentHarness +metadata: + name: k8s-troubleshooter-slack + namespace: kagent +spec: + backend: hermes + modelConfigRef: default-model-config + channels: + - name: platform + type: slack + slack: + botToken: + valueFrom: {type: Secret, name: slack-tokens, key: bot-token} + appToken: + valueFrom: {type: Secret, name: slack-tokens, key: app-token} + +Poi in Slack: /mykagent perché il pod X sta crashando? — il bot inoltra la richiesta all'agente via A2A e posta la risposta nel canale. Interessante per un'IDP perché è il canale più naturale per i developer, senza dover imparare dashboard o CLI dedicate. + +# ===================================================================== +# ESEMPIO B — Agent BYO (Bring Your Own), container esportato da +# Langflow/Flowise dopo containerizzazione custom. +# Caso d'uso: flow complesso con logica di stato/loop che il developer +# ha personalizzato oltre quello che il builder low-code esporta, +# quindi ha bisogno di pieno controllo del codice. +# ===================================================================== +apiVersion: kagent.dev/v1alpha1 +kind: Agent +metadata: + name: currency-exchange-agent + namespace: team-alpha +spec: + type: BYO + byo: + deployment: + image: ghcr.io/team-alpha/langflow-currency-agent:latest + workingDir: /app + env: + - name: GOOGLE_API_KEY + valueFrom: + secretKeyRef: + name: llm-credentials + key: gemini-api-key + port: 8080 # porta su cui l'agente espone l'endpoint A2A + + # Nota: kagent invoca questo agente via protocollo A2A, trattandolo + # come black box — nessuna decomposizione in prompt/tool separati, + # perché la logica vive interamente dentro l'immagine. + + +## Esempio Agent di Orchestrazione +apiVersion: kagent.dev/v1alpha2 +kind: Agent +metadata: + name: incident-commander + namespace: team-alpha +spec: + type: Declarative + declarative: + systemMessage: | + Sei il coordinatore per la gestione incidenti. Quando ricevi una + segnalazione, decidi quali specialisti coinvolgere: se riguarda + pod/servizi K8s, delega al k8s-troubleshooter; se servono i log, + delega al log-analyzer. Puoi chiamare entrambi in sequenza se + il problema lo richiede. + modelConfig: gpt4o-config + tools: + - type: McpServer + mcpServer: + name: kagent-tool-server + toolNames: ["k8s_get_events"] + + # Sotto-agenti esposti come "tool" — la scelta di chiamarli + # è dell'LLM di incident-commander, non di kagent + - type: Agent + agent: + name: k8s-troubleshooter + namespace: team-alpha + - type: Agent + agent: + name: log-analyzer + namespace: team-alpha \ No newline at end of file diff --git a/add-on/monitoring/blackbox-exporter.md b/add-on/monitoring/blackbox-exporter.md index 42eab03..f271882 100644 --- a/add-on/monitoring/blackbox-exporter.md +++ b/add-on/monitoring/blackbox-exporter.md @@ -619,6 +619,11 @@ spec: EOF ``` + + + + + COMANDO - applica alert ```bash kubectl apply -f blackbox-http-alerts.yaml @@ -631,8 +636,119 @@ kubectl describe prometheusrule blackbox-http-alerts -n monitoring ``` -10. TEST MANUALE BLACKBOX EXPORTER -================================= +10. CONFIGURA ALERTMANAGER - INVIO EMAIL SU FAIL DI UN SERVIZIO +================================================================ + +Obiettivo: +- inviare una email a un indirizzo specifico quando la probe di un + servizio specifico (serviceX) fallisce (alert BlackboxHttpProbeFailed). + +FILE - blackbox-email-alert.yaml + +Descrizione: +- crea un receiver email dedicato al servizio da monitorare; +- instrada verso quel receiver solo gli alert che riguardano il + target specifico, identificato tramite il label instance. + +NOTA - credenziali SMTP +Se il server SMTP richiede autenticazione, crea prima un Secret con +la password: + +COMANDO - crea secret con password SMTP +```bash +kubectl create secret generic alertmanager-smtp \ + -n monitoring --from-literal=password='' +``` + +COMANDO - crea blackbox-email-alert.yaml +```bash +cat > blackbox-email-alert.yaml <<'EOF' +apiVersion: monitoring.coreos.com/v1alpha1 +kind: AlertmanagerConfig +metadata: + name: blackbox-email-routing + namespace: monitoring + labels: + release: kube-prometheus-stack +spec: + route: + groupBy: ["alertname", "instance"] + groupWait: 30s + groupInterval: 5m + repeatInterval: 1h + receiver: "email-default" + routes: + - matchers: + - name: alertname + value: BlackboxHttpProbeFailed + matchType: "=" + - name: instance + value: "" + matchType: "=" + receiver: "email-servizioX" + + receivers: + - name: "email-default" + emailConfigs: [] + + - name: "email-servizioX" + emailConfigs: + - to: "" + from: "" + smarthost: ":" + authUsername: "" + authPassword: + name: alertmanager-smtp + key: password + requireTLS: true + sendResolved: true + headers: + subject: "[ALERT] Servizio X non raggiungibile" + html: | +

Il servizio {{ "{{" }} .CommonLabels.instance {{ "}}" }} non risponde.

+

{{ "{{" }} .CommonAnnotations.description {{ "}}" }}

+EOF +``` + +Da personalizzare: +- : valore del label instance della probe da + monitorare (es. http://my-service.my-namespace.svc.cluster.local:8080/health); +- : indirizzo email destinatario; +- , : server SMTP (es. smtp.gmail.com:587); +- : mittente email; +- : utente SMTP, se richiesta autenticazione. + +COMANDO - applica la configurazione +```bash +kubectl apply -f blackbox-email-alert.yaml +``` + +COMANDO - verifica AlertmanagerConfig +```bash +kubectl get alertmanagerconfig -n monitoring +kubectl describe alertmanagerconfig blackbox-email-routing -n monitoring +``` + +NOTA - matcher su instance vs job +Se vuoi far scattare l'email per TUTTI i target di una Probe (job) +invece che per un singolo servizio, sostituisci il matcher su +`instance` con: +```yaml +- name: job + value: "http-services-probe" + matchType: "=" +``` + +COMANDO - verifica alert in Alertmanager +```bash +kubectl port-forward -n monitoring svc/kube-prometheus-stack-alertmanager 9093 +# -> http://localhost:9093, controlla che l'alert BlackboxHttpProbeFailed +# per il servizio X sia instradato sul receiver email-servizioX +``` + + +11. TEST MANUALE BLACKBOX EXPORTER +================================== COMANDO - port-forward blackbox-exporter ```bash @@ -651,7 +767,7 @@ probe_http_status_code 200 ``` -11. TROUBLESHOOTING +12. TROUBLESHOOTING =================== CASO - Prometheus non vede la Probe @@ -700,7 +816,7 @@ Azioni consigliate: - aggiungere un modulo dedicato in blackbox-values.yaml se serve una configurazione HTTP specifica. -12. ORDINE DI ESECUZIONE CONSIGLIATO +13. ORDINE DI ESECUZIONE CONSIGLIATO ==================================== COMANDO - sequenza completa @@ -727,7 +843,7 @@ blackbox-http-alerts.yaml ``` -13. NOTE PER DEV, QA E PROD +14. NOTE PER DEV, QA E PROD =========================== Per separare gli ambienti e semplificare dashboard e alert, usa Probe distinte: diff --git a/pipeline/add-hostalias.sh b/pipeline/add-hostalias.sh new file mode 100644 index 0000000..c52bcac --- /dev/null +++ b/pipeline/add-hostalias.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Aggiunge un hostname a spec.template.spec.hostAliases del Deployment blackbox. +# +# Uso: +# ./add-hostalias.sh +# +# Esempio: +# ./add-hostalias.sh idcidcp.pigreco66.it cert-manager cert-manager +# + +DEPLOYMENT_NAME="${2:-}" +DEPLOYMENT_NS="${3:-}" +TARGET_IP="10.43.37.118" +HOSTNAME_VAL="${1:-}" + +if [[ -z "$HOSTNAME_VAL" ]]; then + echo "Uso: $0 " >&2 + exit 1 +fi + +if ! command -v kubectl >/dev/null 2>&1; then + echo "Errore: kubectl non trovato nel PATH" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "Errore: jq non trovato nel PATH" >&2 + echo "Installa jq e riprova." >&2 + exit 1 +fi + +# Idempotenza: controlla solo il blocco hostAliases associato a TARGET_IP. +EXISTING_HOSTNAMES="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o jsonpath="{.spec.template.spec.hostAliases[?(@.ip=='${TARGET_IP}')].hostnames[*]}" 2>/dev/null || true)" +if echo " $EXISTING_HOSTNAMES " | grep -Fq " $HOSTNAME_VAL "; then + echo "Hostname '$HOSTNAME_VAL' gia presente per IP ${TARGET_IP} in ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}. Nessuna modifica." + exit 0 +fi + +DEPLOY_JSON="$(kubectl get deployment "$DEPLOYMENT_NAME" -n "$DEPLOYMENT_NS" -o json)" + +UPDATED_HOST_ALIASES="$({ + echo "$DEPLOY_JSON" | jq -c --arg ip "$TARGET_IP" --arg hostname "$HOSTNAME_VAL" ' + (.spec.template.spec.hostAliases //= []) + | if any(.spec.template.spec.hostAliases[]?; .ip == $ip) then + .spec.template.spec.hostAliases |= map( + if .ip == $ip then + if ((.hostnames // []) | index($hostname)) then + . + else + .hostnames = ((.hostnames // []) + [$hostname]) + end + else + . + end + ) + else + .spec.template.spec.hostAliases += [{"ip": $ip, "hostnames": [$hostname]}] + end + | .spec.template.spec.hostAliases + ' +} )" + +PATCH_PAYLOAD="$(jq -cn --argjson hostAliases "$UPDATED_HOST_ALIASES" '{spec:{template:{spec:{hostAliases:$hostAliases}}}}')" + +kubectl patch deployment "$DEPLOYMENT_NAME" \ + -n "$DEPLOYMENT_NS" \ + --type=merge \ + -p "$PATCH_PAYLOAD" >/dev/null + +echo "Hostname aggiunto con successo." +echo " Deployment: ${DEPLOYMENT_NS}/${DEPLOYMENT_NAME}" +echo " IP : ${TARGET_IP}" +echo " Hostname : ${HOSTNAME_VAL}" diff --git a/pipeline/addlistener.sh b/pipeline/addlistener.sh index 389ca07..69fef11 100644 --- a/pipeline/addlistener.sh +++ b/pipeline/addlistener.sh @@ -45,11 +45,13 @@ fi # Se l'hostname non e nel dominio *.italiadatacenter.com, aggiungilo anche a cert-manager hostAliases. if [[ "$host_no_port" != *.italiadatacenter.com ]]; then - HOSTALIAS_SCRIPT="/root/work/pipeline/add-cert-manager-hostalias.sh" + HOSTALIAS_SCRIPT="/root/work/pipeline/add-hostalias.sh" if [[ -x "$HOSTALIAS_SCRIPT" ]]; then - "$HOSTALIAS_SCRIPT" "$host_no_port" + "$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager" + "$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring" elif [[ -f "$HOSTALIAS_SCRIPT" ]]; then - bash "$HOSTALIAS_SCRIPT" "$host_no_port" + bash "$HOSTALIAS_SCRIPT" "$host_no_port" "cert-manager" "cert-manager" + bash "$HOSTALIAS_SCRIPT" "$host_no_port" "blackbox-exporter" "monitoring" else echo "Errore: script non trovato: $HOSTALIAS_SCRIPT" >&2 exit 1