From f693d7231ad1d2610fe0d6075066e225fd15fc62 Mon Sep 17 00:00:00 2001 From: lorentz Date: Mon, 23 Feb 2026 17:23:32 +0000 Subject: [PATCH] Add newt playbook --- playbooks/newt.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++ playbooks/newt.yml | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 playbooks/newt.md create mode 100644 playbooks/newt.yml diff --git a/playbooks/newt.md b/playbooks/newt.md new file mode 100644 index 0000000..8e0b529 --- /dev/null +++ b/playbooks/newt.md @@ -0,0 +1,75 @@ +# Newt (Pangolin Tunnel Agent) Playbook + +## Overview + +`newt.yml` deploys [Newt](https://github.com/fosrl/newt), the tunnel agent for the Pangolin reverse proxy platform. Newt establishes an outbound tunnel from a Docker host back to your Pangolin server, allowing Pangolin to route traffic to services on that host without opening inbound ports. + +## Architecture + +``` +┌─────────────────────────┐ ┌──────────────────────────┐ +│ Target Docker Host │ │ Pangolin Server │ +│ │ │ pangolin.wulfconsulting │ +│ ┌───────────────────┐ │ tunnel │ .cloud │ +│ │ newt container │──────────▶│ │ +│ │ (fosrl/newt) │ │ │ Routes traffic to sites │ +│ └───────────────────┘ │ │ configured in dashboard │ +│ │ └──────────────────────────┘ +│ /opt/stacks/newt/ │ +│ └── compose.yaml │ +└─────────────────────────┘ +``` + +### Playbook structure + +| Section | Purpose | +|---------|---------| +| **vars** | Sets `newt_stack_dir` (`/opt/stacks/newt`) and the default `pangolin_endpoint` | +| **pre_tasks** | Validates that `newt_id`, `newt_secret`, and `pangolin_endpoint` are provided; checks Docker is running | +| **tasks** | Creates the stack directory, writes `compose.yaml`, and deploys via `docker_compose_v2` | + +The compose file defines a single service — the `fosrl/newt` image configured with three environment variables that authenticate it to the Pangolin server. + +## Usage + +### Required extra vars + +| Variable | Source | Description | +|----------|--------|-------------| +| `newt_id` | Pangolin dashboard (per-site) | Tunnel identity | +| `newt_secret` | Pangolin dashboard (per-site) | Tunnel secret | + +### Optional extra vars + +| Variable | Default | Description | +|----------|---------|-------------| +| `pangolin_endpoint` | `https://pangolin.wulfconsulting.cloud` | Pangolin server URL | + +### Deploy to a host + +```bash +ansible-playbook playbooks/newt.yml --limit kaercher02 \ + -e newt_id=kigig6jtds5qr3a \ + -e newt_secret=9qznq1wasaapy0cwpxzzi8sji6z5r4nkukqpkt4k8lyy6jx3 +``` + +### Override the Pangolin endpoint + +```bash +ansible-playbook playbooks/newt.yml --limit somehost \ + -e newt_id=... \ + -e newt_secret=... \ + -e pangolin_endpoint=https://pangolin.other.domain +``` + +### Verify the container is running + +```bash +ansible kaercher02 -a "docker ps --filter name=newt" +``` + +## Notes + +- **Credentials are runtime-only.** `newt_id` and `newt_secret` are passed as extra vars and written into the compose file on the target host. They are not stored in the Ansible repo. +- **One Newt per host.** The container name is fixed to `newt`, so each host runs a single instance. If you need multiple tunnels on one host, duplicate the playbook with a different container name and stack directory. +- **`--limit` is required.** The playbook targets `all` hosts, so always use `--limit` to select which host to deploy to. diff --git a/playbooks/newt.yml b/playbooks/newt.yml new file mode 100644 index 0000000..2e3e8b0 --- /dev/null +++ b/playbooks/newt.yml @@ -0,0 +1,66 @@ +- name: Deploy Newt (Pangolin tunnel agent) + hosts: all + become: true + + vars: + newt_stack_dir: "/opt/stacks/newt" + pangolin_endpoint: "https://pangolin.wulfconsulting.cloud" + + pre_tasks: + - name: Fail if newt_id is not set + fail: + msg: "newt_id is required. Pass it via -e newt_id=..." + when: newt_id is not defined or newt_id | length == 0 + + - name: Fail if newt_secret is not set + fail: + msg: "newt_secret is required. Pass it via -e newt_secret=..." + when: newt_secret is not defined or newt_secret | length == 0 + + - name: Fail if pangolin_endpoint is not set + fail: + msg: "pangolin_endpoint is required. Pass it via -e pangolin_endpoint=..." + when: pangolin_endpoint is not defined or pangolin_endpoint | length == 0 + + - name: Verify Docker is running + service_facts: + + - name: Fail if Docker service is not running + fail: + msg: "Docker is not running on this host." + when: "'docker.service' not in ansible_facts.services or ansible_facts.services['docker.service'].state != 'running'" + + tasks: + - name: Create stack directory + file: + path: "{{ newt_stack_dir }}" + state: directory + mode: "0755" + + - name: Write compose.yaml + copy: + dest: "{{ newt_stack_dir }}/compose.yaml" + mode: "0644" + content: | + networks: + shared: + external: true + + services: + newt: + image: fosrl/newt + container_name: newt + restart: unless-stopped + networks: + - shared + environment: + PANGOLIN_ENDPOINT: {{ pangolin_endpoint }} + NEWT_ID: {{ newt_id }} + NEWT_SECRET: {{ newt_secret }} + + - name: Deploy Newt stack + community.docker.docker_compose_v2: + project_src: "{{ newt_stack_dir }}" + pull: always + build: never + state: present