# Newt (Pangolin) — noble This is the **primary** automation path for **public** hostnames to workloads in this cluster (it **replaces** in-cluster ExternalDNS). [Newt](https://github.com/fosrl/newt) is the on-prem agent that connects your cluster to a **Pangolin** site (WireGuard tunnel). The [Fossorial Helm chart](https://github.com/fosrl/helm-charts) deploys one or more instances. **Secrets:** Never commit endpoint, Newt ID, or Newt secret in **plain** YAML. If credentials were pasted into chat or CI logs, **rotate them** in Pangolin and recreate the Kubernetes Secret. ## 1. Create the Secret Keys must match `values.yaml` (`PANGOLIN_ENDPOINT`, `NEWT_ID`, `NEWT_SECRET`). ### Option A — SOPS (safe for GitOps) Encrypt a normal **`Secret`** with [Mozilla SOPS](https://github.com/getsops/sops) and **age** (see **`clusters/noble/secrets/README.md`** and **`.sops.yaml`**). The repo includes an encrypted example at **`clusters/noble/secrets/newt-pangolin-auth.secret.yaml`** — edit with `sops` after exporting **`SOPS_AGE_KEY_FILE`** to your **`age-key.txt`**, or create a new file and encrypt it. ```bash export SOPS_AGE_KEY_FILE=/absolute/path/to/home-server/age-key.txt sops clusters/noble/secrets/newt-pangolin-auth.secret.yaml # then: sops -d clusters/noble/secrets/newt-pangolin-auth.secret.yaml | kubectl apply -f - ``` **Ansible** (`noble.yml`) applies all **`clusters/noble/secrets/*.yaml`** automatically when **`age-key.txt`** exists at the repo root. ### Option B — Imperative Secret (not in git) ```bash kubectl apply -f clusters/noble/bootstrap/newt/namespace.yaml kubectl -n newt create secret generic newt-pangolin-auth \ --from-literal=PANGOLIN_ENDPOINT='https://pangolin.pcenicni.dev' \ --from-literal=NEWT_ID='YOUR_NEWT_ID' \ --from-literal=NEWT_SECRET='YOUR_NEWT_SECRET' ``` Use the Pangolin UI or [Integration API](https://docs.pangolin.net/manage/common-api-routes) (`pick-site-defaults` + `create site`) to obtain a Newt ID and secret for a new site if you are not reusing an existing pair. ## 2. Install the chart ```bash helm repo add fossorial https://charts.fossorial.io helm repo update helm upgrade --install newt fossorial/newt \ --namespace newt \ --version 1.5.0 \ -f clusters/noble/bootstrap/newt/values.yaml \ --wait ``` ## 3. DNS: CNAME at your DNS host + Pangolin API for routes Pangolin does not replace your public DNS provider. Typical flow: 1. **Link a domain** in Pangolin (organization **Domains**). For **CNAME**-style domains, Pangolin shows the hostname you must **CNAME** to at Cloudflare / your registrar (see [Domains](https://docs.pangolin.net/manage/common-api-routes#list-domains)). 2. **Create public HTTP resources** (and **targets** to your Newt **site**) via the [Integration API](https://docs.pangolin.net/manage/integration-api) — same flows as the UI. Swagger: `https:///v1/docs` (self-hosted: enable `enable_integration_api` and route `api.example.com` → integration port per [docs](https://docs.pangolin.net/self-host/advanced/integration-api)). Minimal patterns (Bearer token = org or root API key): ```bash export API_BASE='https://api.example.com/v1' # your Pangolin Integration API base export ORG_ID='your-org-id' export TOKEN='your-integration-api-key' # Domains already linked to the org (use domainId when creating a resource) curl -sS -H "Authorization: Bearer ${TOKEN}" \ "${API_BASE}/org/${ORG_ID}/domains" # Create an HTTP resource on a domain (FQDN = subdomain + base domain for NS/wildcard domains) curl -sS -X PUT -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \ "${API_BASE}/org/${ORG_ID}/resource" \ -d '{ "name": "Example app", "http": true, "domainId": "YOUR_DOMAIN_ID", "protocol": "tcp", "subdomain": "my-app" }' # Point the resource at your Newt site backend (siteId from list sites / create site; ip:port inside the tunnel) curl -sS -X PUT -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \ "${API_BASE}/resource/RESOURCE_ID/target" \ -d '{ "siteId": YOUR_SITE_ID, "ip": "10.x.x.x", "port": 443, "method": "http" }' ``` Exact JSON fields and IDs differ by domain type (**ns** vs **cname** vs **wildcard**); see [Common API routes](https://docs.pangolin.net/manage/common-api-routes) and Swagger. ## 4. Automate HTTP resources (Integration API + Ansible) You still **link domains** in Pangolin and create **CNAME** records at your DNS host manually (Pangolin does not replace your registrar). After that, this repository can **ensure** public **HTTP** resources and **Traefik** targets exist for the same FQDNs you use in GitOps / Ansible: - **`noble_authentik_ingress_extra_hosts`** (e.g. **`auth.example.com`**) - **`noble_open_webui_public_host`** when set (e.g. **`webui.example.com`**) - Optional extra list **`noble_pangolin_http_fqdns_extra`** in **`ansible/inventory/group_vars/all.yml`** Steps: 1. In Pangolin, create an **organization API key** with permission to manage domains, resources, and targets ([Integration API](https://docs.pangolin.net/manage/integration-api)). 2. Add to repository **`.env`** (never commit secrets): **`NOBLE_PANGOLIN_API_BASE`**, **`NOBLE_PANGOLIN_ORG_ID`**, **`NOBLE_PANGOLIN_API_TOKEN`**, **`NOBLE_PANGOLIN_SITE_ID`** (numeric site that owns your **Newt** pair). Optionally **`NOBLE_PANGOLIN_TRAEFIK_IP`** / **`NOBLE_PANGOLIN_TRAEFIK_PORT`** — if unset, Ansible uses **`kubectl`** to read the Traefik Service **LoadBalancer** IP. 3. Set **`noble_pangolin_sync_http_resources: true`** in **`ansible/inventory/group_vars/all.yml`** (or pass **`-e noble_pangolin_sync_http_resources=true`**). 4. Run **`ansible-playbook playbooks/noble.yml --tags newt`** (or a full **`noble.yml`**) with **`KUBECONFIG`** pointed at the cluster. Implementation: **`clusters/noble/bootstrap/newt/scripts/sync_pangolin_http_resources.py`** (stdlib **Python 3**). Dry run: `python3 clusters/noble/bootstrap/newt/scripts/sync_pangolin_http_resources.py --env-file .env --fqdns auth.example.com,webui.example.com --traefik-ip 192.168.50.211 --dry-run` The script matches each FQDN to the **longest** linked **`baseDomain`** in Pangolin, creates the HTTP resource if missing, then adds a **target** (**`siteId`** + Traefik **`ip`:`port`**, **`method`:** **`http`**) if none matches. Pangolin’s API is still evolving — if a call fails, compare with [Swagger](https://api.pangolin.net/v1/docs) for your deployment version. ### Authentik on a public name Use **`noble_authentik_ingress_extra_hosts`** (see **`ansible/roles/noble_authentik/README.md`**) so the Authentik Ingress (and **cert-manager** SANs) include your public FQDN, then create the Pangolin **HTTP** resource + **target** to the same Traefik **:443** endpoint as other apps. One Newt site can carry many hostnames. ### What to put in Pangolin (resource + target) 1. **Public hostname** — the FQDN users type in the browser (must match **`noble_authentik_ingress_extra_hosts`** and your **CNAME** at the DNS host Pangolin documents for that domain). 2. **Site** — the Pangolin **site** that owns your **Newt** pair (same **`NEWT_ID`** / **`NEWT_SECRET`** as the cluster Secret). In the UI: **Sites** → pick the site connected to this cluster. 3. **Target `ip`** — an address **reachable from inside the tunnel** to **Traefik HTTPS**. On noble this is usually the Traefik **LoadBalancer** IP (repo pins **`192.168.50.211`** in **`clusters/noble/bootstrap/traefik/values.yaml`**). Confirm live: `kubectl -n traefik get svc -l app.kubernetes.io/name=traefik -o wide` Use **`EXTERNAL-IP`** (or **`LOAD_BALANCER_IP`** from the Service status) as **`ip`**. If Newt runs **in** the cluster, that MetalLB/LAN VIP is correct; if you run Newt elsewhere, use whatever L3 path reaches Traefik from that host. 4. **Target `port`** — **`443`** (TLS to Traefik; SNI carries the public hostname). 5. **Target `method`** — **`http`** in the Integration API examples above (TLS is still terminated at Traefik; Pangolin’s field names follow their docs). Discovery in Pangolin’s UI: **Domains** (see required CNAME) → **Resources** → **Add** HTTP resource for the subdomain/FQDN → **Targets** / **Backends** → attach **site** + **ip:port**. Official flow: [Domains](https://docs.pangolin.net/manage/common-api-routes#list-domains), [Integration API](https://docs.pangolin.net/manage/integration-api), and your deployment’s **Swagger** at **`https:///v1/docs`** when enabled. ## LAN vs internet - **LAN / VPN:** point **`*.apps.noble.lab.pcenicni.dev`** at the Traefik **LoadBalancer** (**`192.168.50.211`**) with local or split-horizon DNS if you want direct in-lab access. - **Internet-facing:** use Pangolin **resources** + **targets** to the Newt **site**; public names rely on **CNAME** records at your DNS provider per Pangolin’s domain setup, not on ExternalDNS in the cluster.