CLI tool for bulk-managing resources, targets, and health checks on a self-hosted Pangolin instance via the Integration API.
- Pangolin with the Integration API enabled (
flags.enable_integration_api: trueinconfig.yml) - The Integration API routed externally (see Pangolin docs)
- An API key with the required permissions (Organization > API Keys in the dashboard)
git clone <repo>
cd pangolin-cli
npm install
cp .env.example .envEdit .env:
PANGOLIN_URL=https://pangolin.your-domain.com
PANGOLIN_API_KEY=your-key-id.your-key-secret
PANGOLIN_ORG_ID=your-org-niceidmake
# or
npx ts-node src/cli.tsWalks you through resource selection, field changes, and health check configuration without needing to know any flags.
Pangolin CLI
? What do you want to do?
❯ Export current config to YAML
List resources
Update resources
Delete resources
Manage health checks
Manage targets
Live health status dashboard
──────────────────
Exit
Space toggles and auto-advances to the next item. a selects all, i inverts selection.
? Select resources to update:
(space=toggle+next, a=all, i=invert, enter=confirm)
◉ ❯ PiAware 01 sso
◉ PiAware 02 sso
◉ PiAware 03 sso
◯ PiAware 04 sso
◯ PiAware 05 sso
◯ PiAware 06 sso
◯ audiobookshelf sso
◯ bazarr sso
(1-20 of 92)
? What do you want to change?
── Resource settings ──
❯ SSO / Authentication
Block access
Enabled / Disabled
Sticky session
Maintenance mode
── Target settings ──
Backend IP address
Backend port
Target enabled / disabled
Health check configuration
? Enable health checks? Yes
? Check type:
❯ HTTP / HTTPS
TCP
? Interval (seconds): 10
? Timeout (seconds): 5
? Healthy after N successes: 2
? Unhealthy after N failures: 3
? Scheme: http
? Path: /health
? Method: GET
? Expected status code: 200
? Follow redirects? No
Will update targets on 6 resource(s):
PiAware 01
PiAware 02
...
? Apply changes? (y/N)
? Health check action:
❯ View status on a resource
──────────────────────────
Configure & enable on selected resources
Configure & enable on ALL resources
──────────────────────────
Disable on selected resources
Disable on ALL resources
Per-resource target management: view, enable/disable individual targets, or change IP/port on a specific target.
? Which resource? PiAware 01
? What do you want to do?
❯ View targets
Enable / disable specific targets
Change IP / port on a target
Selecting Enable / disable specific targets shows a checkbox list with each target's current site and enabled state:
? Select targets to toggle:
(space=toggle+next, a=all, i=invert, enter=confirm)
◉ ❯ piaware-01.bub.lan:80 Lâmôlabs Svcs #1 (docker-host-01) enabled
◯ piaware-01.bub.lan:80 Lamolabs Svcs #2 (rockpi-4cplus) enabled
◯ piaware-01.bub.lan:80 Lamolabs Svcs #3 (orangepi5) enabled
◯ piaware-01.bub.lan:80 Lâmôlabs Svcs #4 (docker-host-03) enabled
Polls hcHealth from the API every 10 seconds. Shows all enabled targets with active health checks. q to quit, r to refresh immediately.
Pangolin — Live Health Status updated 9:32:16 PM q=quit r=refresh
────────────────────────────────────────────────────────────────────────────────
PiAware 01
● healthy piaware-01.bub.lan:80 tcp
PiAware 02
● healthy piaware-02.bub.lan:80 tcp
audiobookshelf
● healthy audiobookshelf.bub.lan:13378 tcp
jellyfinbr3
● unhealthy jellyfinbr3.bub.lan:8096 tcp
traefik-dashbd
● unknown gerbil:8080 tcp
────────────────────────────────────────────────────────────────────────────────
86 healthy 1 unhealthy 1 unknown
Note: Targets with an internal hostname (e.g.
gerbil:8080) will remainunknown— newt cannot reach them via TCP from outside the Docker network.
make export # export all resources + targets to current.yaml
make filters # tabular view: name, niceId, sso, enabled
make list # list all resources
make list ARGS="--filter '*PiAware*'" # filter by name glob
make update FILTER="*PiAware*" SET="sso=false" ARGS="--dry-run"
make update FILTER="sso=true" SET="enabled=false"
make delete FILTER="*old*" ARGS="--dry-run"
make health CMD=enable ARGS="--all-resources --dry-run"
make health CMD=status RESOURCE=bazarr
make dashboard # live health status (polls every 10s)
make docker-interactive # interactive wizard inside container
make run ARGS="targets list --resource audiobookshelf"
make run ARGS="targets retarget --resource bazarr --ip 10.0.1.5 --port 6767 --dry-run"npx ts-node src/cli.ts <command> [options]
Commands:
export Export all resources and targets to YAML or JSON
resources list List resources (--filter, --json)
resources update Bulk update resources (--filter, --set, --dry-run)
resources delete Bulk delete resources (--filter, --dry-run, --yes)
targets list List targets for a resource (--resource)
targets retarget Change IP/port on all targets of a resource
targets update Bulk update target fields (--set hcEnabled=true ...)
targets delete Delete a target by ID
health list List org-level health checks
health enable Enable HC on resource(s) (--resource or --all-resources)
health disable Disable HC on resource(s)
health status Show HC config for a resource's targets
dashboard Live health status dashboard (polls every 10s, q=quit r=refresh)A pre-built image is published to ghcr.io/slmingol/pangolin-cli:main on every push to main.
compose.yaml (production) uses pull_policy: always — always pulls the ghcr image.
compose.override.yaml (dev, auto-loaded) sets pull_policy: missing and adds a build section — pulls ghcr if available locally, otherwise builds from the Dockerfile.
Interactive wizard (dev — ghcr if present, else build):
make docker-interactiveInteractive wizard (prod — always pull fresh from ghcr, no build fallback):
make docker-interactive PROD=1Specific command:
make docker-run ARGS="resources list"
make docker-run ARGS="export --output /out/current.yaml"Force local build:
make docker-buildExports written to /out inside the container are saved to ./exports/ on the host.
hcHostnameandhcPortare auto-set to the target's own IP and port if not specified — required by Pangolin to send HC config to the newt client- Interval and timeout are in seconds (not ms)
- TCP health checks require only interval/timeout/thresholds; HTTP checks additionally require scheme, path, method, and expected status code
hcHealthon the target API response is live status reported by the newt client:healthy,unhealthy, orunknown- Targets with internal Docker hostnames (e.g.
gerbil:8080) will always showunknown— newt can't reach them via TCP from outside the network
- Target update calls require
siteId,ip, andportas base fields even for partial updates — omitting them returns HTTP 400 - Pagination uses
page/pageSizequery params (notlimit/offset); default page size is 20 - Org
niceIdvalues with unicode characters must be URL-encoded in API paths (e.g.lâmôlabs→l%C3%A2m%C3%B4labs) - Resources have one target per tunnel site for redundancy — multiple targets with the same backend IP:port is expected, not a misconfiguration
Enable the Integration API in config/config.yml:
flags:
enable_integration_api: true
server:
integration_port: 3003Add to config/traefik/dynamic_config.yml:
http:
routers:
integration-api-router:
rule: "Host(`pangolin.your-domain.com`) && PathPrefix(`/v1`)"
service: integration-api-service
entryPoints:
- websecure
priority: 200
tls:
certResolver: letsencrypt
services:
integration-api-service:
loadBalancer:
servers:
- url: "http://pangolin:3003"
priority: 200is required. Without it, Traefik's rule-length heuristic causes the existingnext-router(which uses a!PathPrefixnegation) to win over the integration router for/v1/paths.
Replace 3003 with your server.integration_port value if different.
- Export current config as Pangolin blueprint YAML (no native support yet — GH issue #1496)
- Terraform provider alternative: stackopshq/terraform-provider-pangolin
- Dashboard filtering — show only unhealthy, filter by site