wiki: compile packetdial (seed) — ACG VoIP system (PacketDial/NetSapiens/OIT + Yealink YMCS)
New systems article covering the full VoIP stack: vendor model (PacketDial brand / NetSapiens platform / OIT wholesaler / YMCS phones), the PBX API + packetdial skill (reads + gated writes + onboard-domain), the Yealink/YMCS side + yealink-ymcs skill (one ACG key -> all client sites, RPS), the onboarding pipeline, vault paths, and known gotchas. Sources: the 3 06-22 session logs + both skill docs + the vendor-stack memory. Indexed under Systems. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,7 @@ Run `/wiki-lint` to check for stale entries and broken backlinks.
|
||||
| [IX Web Hosting Server](systems/ix-server.md) | 172.16.3.10 / 72.194.62.5 — cPanel/WHM 134 on CloudLinux 9.7 (64-core Xeon, 4.4 T /home); **72 cPanel accounts / 185 domains / 101 WordPress** + ACG sites (radio Astro, Flarum community, Matomo analytics); GuruRMM-enrolled; SSH key auth from GURU-5070; behind Cloudflare tunnel `acg-origin`; **backups look unconfigured (gap)**. Live SSH inventory 2026-06-05 — full account→domain map in the article | 2026-06-05 |
|
||||
| [pfsense (ACG Gateway/Firewall)](systems/pfsense.md) | 172.16.0.1 (SSH :2248) — ACG office FreeBSD gateway/firewall + Tailscale subnet router. ALSO the home of the **fleet-wide pfSense management tooling** in the `unifi-wifi` skill: SSH backend (`pfsense-ssh.sh` + `pfsense-gwc.php`) that audits/controls ANY client pfSense — `audit`/`pf-*`/`fw-*`/`block-ips`, DRY-RUN default, cred `clients/<slug>/pfsense-firewall`. Validated on Cascades (Plus 25.07) 2026-06-21 | 2026-06-21 |
|
||||
| [UOS Server (UniFi OS Server)](systems/uos-server.md) | 172.16.3.29 (web/API :11443 via NPM, **not** 8443) — self-hosted UniFi OS controller (~49 sites), virsh "Unifi" VM on Jupiter; UniFi Network `ace` MongoDB in rootless podman; query via `.claude/scripts/uos-mongo.sh` (root SSH key `infrastructure/uos-server-ssh-key`). UniFi half of the `unifi-wifi` skill — pairs with [pfsense](systems/pfsense.md) at UniFi-behind-pfSense sites | 2026-06-21 |
|
||||
| [PacketDial (ACG VoIP / NetSapiens via OIT)](systems/packetdial.md) | ACG's VoIP brand on the NetSapiens PBX (white-labeled via OIT/OITVOIP). PBX API `pbx.packetdial.com/ns-api/v2`, reseller `91912.service` (key vault `msp-tools/oitvoip`); Yealink phones via **YMCS** `us-api.ymcs.yealink.com` (one ACG key → all client sites; vault `services/yealink-ymcs`). Driven by the `packetdial` + `yealink-ymcs` skills; `onboard-domain` pipeline. `vwp.91912.service` onboarded 2026-06-22 | 2026-06-22 |
|
||||
|
||||
## Patterns
|
||||
|
||||
|
||||
308
wiki/systems/packetdial.md
Normal file
308
wiki/systems/packetdial.md
Normal file
@@ -0,0 +1,308 @@
|
||||
---
|
||||
type: system
|
||||
name: packetdial
|
||||
display_name: "PacketDial (ACG VoIP / NetSapiens via OIT)"
|
||||
last_compiled: 2026-06-22
|
||||
compiled_by: GURU-5070/claude-main
|
||||
sources:
|
||||
- .claude/memory/reference_packetdial_oit_netsapiens.md
|
||||
- .claude/skills/packetdial/SKILL.md
|
||||
- .claude/skills/packetdial/references/api.md
|
||||
- .claude/skills/yealink-ymcs/SKILL.md
|
||||
- session-logs/2026-06/2026-06-22-mike-packetdial-buildout-oitvoip-vault.md
|
||||
- session-logs/2026-06/2026-06-22-mike-packetdial-domain-onboarding-vwp.md
|
||||
- session-logs/2026-06/2026-06-22-mike-yealink-ymcs-skill.md
|
||||
- wiki/clients/valleywide.md
|
||||
backlinks:
|
||||
- clients/valleywide
|
||||
---
|
||||
|
||||
# PacketDial (ACG VoIP / NetSapiens via OIT)
|
||||
|
||||
> Two things live under this article: (1) the **PacketDial/NetSapiens PBX platform** — ACG's
|
||||
> hosted VoIP offering, managed via the `packetdial` skill against the NetSapiens API v2; and
|
||||
> (2) the **Yealink YMCS phone-management layer** — the cloud device manager that handles
|
||||
> physical phone provisioning, firmware, and RPS zero-touch, managed via the `yealink-ymcs`
|
||||
> skill. The two skills form the end-to-end onboarding pipeline for new VoIP clients.
|
||||
|
||||
---
|
||||
|
||||
## Vendor Stack
|
||||
|
||||
Do not conflate these four layers:
|
||||
|
||||
| Layer | What it is | Hostname / Identifier |
|
||||
|---|---|---|
|
||||
| **PacketDial** | ACG's own VoIP department / customer-facing brand | `pbx.packetdial.com` (API host); `voip.packetdial.com` (customer portal — no API) |
|
||||
| **NetSapiens** | The underlying PBX/UCaaS software platform (SNAPsolution API v2, v44.4.10) | Same hosts as above |
|
||||
| **OIT / OITVOIP** | White-label wholesale provider that runs NetSapiens and resells to MSPs; ACG's upstream | `api.ucaasnetwork.com` (OIT/NetSapiens white-label host — same platform as `pbx.packetdial.com`) |
|
||||
| **YMCS (Yealink Management Cloud Service)** | Separate Yealink cloud for physical phone device management | `us-api.ymcs.yealink.com` (US region) |
|
||||
|
||||
`pbx.packetdial.com` and `api.ucaasnetwork.com` resolve to the same NetSapiens platform under
|
||||
different white-label hostnames. The `packetdial` skill targets `pbx.` by default; if a future
|
||||
endpoint 403s there, override with `PACKETDIAL_API_BASE_URL=https://api.ucaasnetwork.com/ns-api/v2`.
|
||||
|
||||
ACG's reseller territory on OIT's platform is `91912.service`.
|
||||
|
||||
---
|
||||
|
||||
## PBX Platform (NetSapiens via PacketDial / OIT)
|
||||
|
||||
### API
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Base URL | `https://pbx.packetdial.com/ns-api/v2` |
|
||||
| Token endpoint | `https://pbx.packetdial.com/ns-api/v2/tokens` |
|
||||
| Spec version | NetSapiens API v2 v44.4.10 — 239 paths / 354 operations (GET 139, POST 87, PUT 68, DELETE 50, PATCH 10) |
|
||||
| OpenAPI spec | `https://pbx.packetdial.com/ns-api/webroot/openapi/openapi.json` |
|
||||
| Swagger UI | `https://pbx.packetdial.com/ns-api/openapi` |
|
||||
| Scope | Almost everything nested under `/domains/{domain}/...` |
|
||||
|
||||
**Auth — static API key (in use):** Send the `nsr_` key directly as `Authorization: Bearer <key>` —
|
||||
no exchange step. Key prefix encodes scope: `nsr_` = reseller, `nss_` = system, `nsd_` = domain.
|
||||
OAuth2 password grant (`POST /tokens` form-encoded) is also supported; the client caches and
|
||||
refreshes JWTs ~60s before expiry.
|
||||
|
||||
**Reseller key (live-verified 2026-06-22):**
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Key-ID | `nsr_hSGUB5Wo` |
|
||||
| Scope | Reseller `91912.service`; `user/domain: *` (sees every domain under ACG's territory) |
|
||||
| Permissions | Read-write (`readonly: no`); cannot create sub-keys (`can-create-keys: no`) |
|
||||
| Vault path | `msp-tools/oitvoip.sops.yaml` → `credentials.api_key` |
|
||||
|
||||
### Reseller Domains (as of 2026-06-22)
|
||||
|
||||
| Domain | Purpose | Notes |
|
||||
|---|---|---|
|
||||
| `0000.91912.service` | Reseller default / template | OIT platform default |
|
||||
| `arizonacomputerguru` | ACG's own domain | **Sanctioned test bed — not in production use.** Use for exercising write wrappers safely. |
|
||||
| `russo.91912.service` | Client domain | |
|
||||
| `vwp.91912.service` | Valley Wide Plastering | Created 2026-06-22 via `onboard-domain`. E911 address id `a-6a395c03d4cfe` (301 N 56TH ST, Chandler AZ 85226, USPS VALID). Caller-ID 4807059500. |
|
||||
|
||||
### `packetdial` Skill
|
||||
|
||||
Skill location: `.claude/skills/packetdial/` (scripts: `ns.py`, `ns_client.py`).
|
||||
|
||||
**Reads (read-only by default, no gate):**
|
||||
|
||||
`domains`, `domain`, `users`, `user`, `phones`, `dids`, `devices`, `callqueues`, `timeframes`,
|
||||
`sites`, `autoattendants`, `contacts`, `billing`, `addresses`, `smsnumbers`, `blocked-numbers`,
|
||||
`moh`, `dialrules`, `recording`, `transcriptions`, `cdrs`, `resellers`
|
||||
|
||||
All listed reads are live-verified against the production reseller key (2026-06-22).
|
||||
|
||||
**Writes (every mutating command is `--confirm`-gated; prints `[DRY RUN]` and exits non-zero
|
||||
without the flag):**
|
||||
|
||||
| Resource | Wrappers | Verification |
|
||||
|---|---|---|
|
||||
| Domains | `create-domain`, `onboard-domain` | `onboard-domain` end-to-end verified (vwp.91912.service) |
|
||||
| Users | `create-user`, `update-user`, `delete-user` | [P] |
|
||||
| Call queues | `create/update/delete-callqueue`, `add/update/remove-agent` | [V] full lifecycle on `arizonacomputerguru` |
|
||||
| Time frames | `create/update/delete-timeframe` | [V] full lifecycle on `arizonacomputerguru` |
|
||||
| Contacts | `create/update/delete-contact` | [V] on `arizonacomputerguru` |
|
||||
| DIDs | `create-did`, `update-did`, `delete-did` | [P] |
|
||||
| SIP devices | `create-phone`, `create/update/delete-device` | [P] |
|
||||
| E911 addresses | `create/update/delete-address` | [P] (create requires `addresses/validate` → pidflo first) |
|
||||
| Sites | `create-site`, `update-site` | [P] |
|
||||
| Auto-attendants | `create-autoattendant` | [P] |
|
||||
| SMS numbers | `create/update/delete-smsnumber` | [P] (requires SMS-provisioned domain) |
|
||||
| Blocked numbers | `block-numbers`, `unblock-numbers` | [P] (202'd but didn't persist on empty test domain) |
|
||||
| Music on hold | `create-moh` (TTS), `delete-moh` | [P] (multipart file upload → use `raw`) |
|
||||
|
||||
**[V]** = lifecycle-verified on ACG test domain; **[P]** = plumbed per spec, not lifecycle-verified.
|
||||
|
||||
**Raw escape hatch:** `ns.py raw <METHOD> <path>` reaches any of the 239 v2 paths. Non-GET methods
|
||||
still require `--confirm`.
|
||||
|
||||
### `onboard-domain` Wrapper
|
||||
|
||||
One gated command that runs the OITVOIP "Add a Domain" GUI wizard as 3 API calls:
|
||||
|
||||
1. `POST /domains` — creates the domain (Basic + Defaults + Limitations fields).
|
||||
2. `POST /domains/{domain}/addresses/validate` — validates the E911 address, returns `address-formatted-pidflo` + `emergency-address-id`.
|
||||
3. `POST /domains/{domain}/addresses` — creates the E911 address with the pidflo from step 2.
|
||||
|
||||
Usage: `ns.py onboard-domain --body-file new-client.json --confirm`
|
||||
|
||||
The body is the `POST /domains` schema plus an optional `emergency` sub-object carrying the E911
|
||||
address fields. Omit `limits-max-*` for unlimited seat counts (they default to 0 = uncapped; do not
|
||||
set them to a sentinel). Omit `dial-plan` (auto-generates named after the domain).
|
||||
|
||||
Undo a bad onboard: `ns.py raw DELETE domains/<domain> --confirm`.
|
||||
|
||||
### PBX Gotchas
|
||||
|
||||
- **`domain-type` stores as `"no"` on create** — the field sent as `"Standard"` came back `"no"` on
|
||||
`vwp.91912.service`. Fix post-create: `ns.py raw PUT domains/<domain> --body '{"domain-type":"Standard"}' --confirm`.
|
||||
- **Voicemail user-defaults not in `POST /domains`** — the GUI Defaults tab (Enable VM, Transcription,
|
||||
Message) has no equivalent in the domain-create schema; how the GUI applies them is unresolved (verify).
|
||||
- **`email-send-from-address` left blank on create** — must be set separately once the sender mailbox
|
||||
exists: `ns.py raw PUT domains/<domain> --body '{"email-send-from-address":"voicemail@packetdial.com"}' --confirm`.
|
||||
ACG's own domain uses `notify@oitvoip.com` as the working pattern.
|
||||
- **Unlimited seat counts = omit `limits-max-*`** — do not pass 0 or a sentinel; omitting the fields
|
||||
lets the platform default to uncapped.
|
||||
- **Timeframe writes are body-discriminated** — the same path handles all operations; the server
|
||||
selects create/update/delete based on the request body. Entry-bearing types (days-of-week, etc.)
|
||||
reject creation without their array — create as `always` type first, then convert.
|
||||
- **`voip.packetdial.com` has no API** — this is the customer-facing white-label portal (Cascades fax
|
||||
account 28598 lives here). Hitting `/ns-api/*` on this host returns `errors/not_found`.
|
||||
- **CDR queries are unbounded by default** — always pass `--start`/`--end` and `--limit`.
|
||||
- **This is the live production PBX.** Confirm the target domain with a read command before any write.
|
||||
A bad `create-domain` or `delete-user` affects real customers.
|
||||
|
||||
---
|
||||
|
||||
## Phone Management (Yealink YMCS)
|
||||
|
||||
### API
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Base URL | `https://us-api.ymcs.yealink.com` (US region; EU = `eu-api.ymcs.yealink.com`; AU = `au-api.ymcs.yealink.com`) |
|
||||
| Region env | `YMCS_REGION` (us | eu | au; default us) |
|
||||
| Auth | `POST /v2/token` with HTTP Basic `base64(AccessKeyID:Secret)` + headers `timestamp` (ms) + `nonce` + body `{"grant_type":"client_credentials"}` → bearer token (~24h) |
|
||||
| TLS | **Requires legacy TLS renegotiation** — handled in-client via `OP_LEGACY_SERVER_CONNECT` SSL context |
|
||||
| Vault path | `services/yealink-ymcs.sops.yaml` → `credentials.access_key_id` / `credentials.access_key_secret` |
|
||||
|
||||
### Account Tree (verified 2026-06-22)
|
||||
|
||||
The ACG AccessKey is the **parent account** — one key covers all client sites as children. No
|
||||
per-client API keys are needed.
|
||||
|
||||
```
|
||||
Arizona Computer Guru LLC (parent / ACG account)
|
||||
├── VWP (Valley Wide Plastering)
|
||||
├── GuruHQ (ACG office)
|
||||
└── Ace Pick Up Parks
|
||||
```
|
||||
|
||||
### RPS Provisioning Server
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Server name | `WL - ACG` |
|
||||
| URL | `ftp://p.packetdials.net` |
|
||||
| Purpose | Zero-touch provisioning — phones pull their config from this RPS server on first boot |
|
||||
|
||||
### `yealink-ymcs` Skill
|
||||
|
||||
Skill location: `.claude/skills/yealink-ymcs/` (scripts: `ymcs.py`, `ymcs_client.py`).
|
||||
|
||||
**Reads (live-verified unless noted):**
|
||||
|
||||
`sites`, `devices` (21 total; `--site` filter best-effort [P]), `accounts`, `rps-servers`,
|
||||
`rps-devices`, `device-groups`, `device-configs`, `firmwares`, `models`, `alarms`, `oplogs`
|
||||
|
||||
`official-firmwares` returns a non-standard shape (likely requires a model param) — marked [P].
|
||||
|
||||
List endpoints auto-paginate (`{skip,limit,autoCount}` → `{total,data:[]}`); wrappers return
|
||||
`{total, count, data}` across all pages.
|
||||
|
||||
**Writes (all `--confirm`-gated; not lifecycle-verified — no throwaway device to safely test against):**
|
||||
|
||||
| Command | Effect |
|
||||
|---|---|
|
||||
| `add-devices-by-mac` | Bulk-add phones by MAC address to a site |
|
||||
| `add-sipaccount` | Push a SIP credential onto a device (the PBX↔phone integration glue) |
|
||||
| `reboot` | Reboot device(s) by ID |
|
||||
| `reset` | Factory-reset device(s) — destructive |
|
||||
| `rps-add` | Register device(s) in RPS for zero-touch provisioning |
|
||||
| `rps-del` | Remove device(s) from RPS |
|
||||
|
||||
**Raw passthrough:** `ymcs.py raw POST /v2/dm/listDevices --body '{...}'` reaches any `/v2/dm/*` or
|
||||
`/v2/rps/*` endpoint.
|
||||
|
||||
### YMCS Gotchas
|
||||
|
||||
- **Legacy TLS required.** If a future Python update drops `OP_LEGACY_SERVER_CONNECT`, connections to
|
||||
YMCS will silently fail. The SSL context with that flag is baked into `ymcs_client.py`.
|
||||
- **MSYS path conversion on `raw`.** A leading `/v2/...` argument gets rewritten by Git-Bash to
|
||||
`C:/Program Files/Git/v2/...`. The `raw` command auto-recovers (strips the prefix back to `/v2/`).
|
||||
Do NOT set `MSYS_NO_PATHCONV=1` — it breaks the vault subprocess and causes credential failures.
|
||||
Named wrappers (hardcoded paths) are unaffected.
|
||||
- **`/v2/dm/sipAccounts` is a CREATE endpoint, not a list.** It is wrapped as the gated
|
||||
`add-sipaccount` write. There is no list-SIP-accounts endpoint in the v2 API.
|
||||
- **Per-client portal logins exist** (e.g., vault `clients/valleywide/`) even though the API key
|
||||
covers all clients. The portal admin for the ACG parent account is `admin@azcomputerguru.com`.
|
||||
|
||||
---
|
||||
|
||||
## Onboarding Pipeline
|
||||
|
||||
New VoIP client flow (the two skills together):
|
||||
|
||||
```
|
||||
1. packetdial onboard-domain → PBX domain created + E911 address validated and registered.
|
||||
2. packetdial create-user → SIP extensions created per user.
|
||||
3. packetdial create-did → DIDs attached and routed to users.
|
||||
4. yealink-ymcs add-devices-by-mac → Physical phones added to the client's YMCS site.
|
||||
5. yealink-ymcs add-sipaccount → NetSapiens SIP credential pushed onto each phone.
|
||||
6. yealink-ymcs rps-add → (Optional) Phone registered in RPS WL-ACG for zero-touch
|
||||
on first boot (pulls config from ftp://p.packetdials.net).
|
||||
```
|
||||
|
||||
Result: new client → domain live → phones registered to `pbx.packetdial.com`.
|
||||
|
||||
**VWP is the live pilot** (2026-06-22): `vwp.91912.service` on the PBX, 21 devices in YMCS
|
||||
(fleet partly pending — 16x Yealink T54W plus others). Steps 4–6 not yet exercised on
|
||||
production VWP hardware (writes are [P] for YMCS).
|
||||
|
||||
**Post-onboard manual steps (gaps vs. full GUI wizard):**
|
||||
|
||||
- Fix `domain-type` if it stored as `"no"` (see PBX gotchas above).
|
||||
- Set `email-send-from-address` once the `voicemail@packetdial.com` / `noreply@packetdial.com`
|
||||
mailbox exists (pending infra task as of 2026-06-22).
|
||||
- Apply voicemail user-defaults — mechanism for applying the GUI Defaults tab (Enable VM,
|
||||
Transcription, Message) via the API is unresolved (verify).
|
||||
|
||||
---
|
||||
|
||||
## Access
|
||||
|
||||
| Credential | Vault Path | Field(s) |
|
||||
|---|---|---|
|
||||
| NetSapiens reseller API key | `msp-tools/oitvoip.sops.yaml` | `credentials.api_key` |
|
||||
| YMCS API AccessKey | `services/yealink-ymcs.sops.yaml` | `credentials.access_key_id`, `credentials.access_key_secret` |
|
||||
| Yealink portal admin (`admin@azcomputerguru.com`) | `infrastructure/voip-phones.sops.yaml` | (verify field names) |
|
||||
| VWP per-client YMCS portal | `clients/valleywide/` | (verify entry name) |
|
||||
|
||||
Read a field: `bash .claude/scripts/vault.sh get-field <vault-path> <field>`
|
||||
|
||||
**NEVER inline raw secrets or API key values** — reference vault paths only.
|
||||
|
||||
---
|
||||
|
||||
## Known Issues & Quirks
|
||||
|
||||
- **`domain-type` stored as `"no"` on domain create (observed 2026-06-22).** Re-PUT the field after
|
||||
`onboard-domain`. `onboard-domain` wrapper does NOT auto-correct this — it is a platform behavior.
|
||||
- **Voicemail user-defaults have no `POST /domains` field.** The GUI Defaults tab (Enable Voicemail,
|
||||
Transcription, Message to Email) is not represented in the domain-create schema. How and where the
|
||||
GUI applies these defaults is unresolved; set manually post-onboard (verify the mechanism).
|
||||
- **`email-send-from-address` must be set separately.** Required for voicemail-to-email delivery. The
|
||||
sender mailboxes (`voicemail@packetdial.com` / `noreply@packetdial.com`) were not yet created as of
|
||||
2026-06-22. ACG's own domain uses `notify@oitvoip.com` as the working pattern.
|
||||
- **Blocked-number filters inconclusive on empty domains.** `block-numbers` returned 202 but the
|
||||
number did not persist on the `arizonacomputerguru` test domain (empty). Verify on a live domain
|
||||
with real traffic before relying on this feature.
|
||||
- **YMCS write wrappers are [P] (plumbed, not lifecycle-verified).** No throwaway device was
|
||||
available to safely test `add-devices-by-mac`, `add-sipaccount`, `reboot`, `reset`, or `rps-add/del`.
|
||||
Exercise first on a non-critical device (e.g., a phone already due for reprovisioning).
|
||||
- **Yealink known-bad firmware `96.86.0.20`** is a documented T54W brick-maker. Before any mass
|
||||
firmware push via YMCS, confirm the active firmware policy is NOT targeting this version.
|
||||
See `clients/valleywide/docs/yealink-t54w-recovery-procedure.md` for the TFTP recovery procedure.
|
||||
- **OIT-API.txt plaintext** was present at `C:\Users\guru\Downloads\OIT-API.txt` as of 2026-06-22.
|
||||
Mike to delete; key is in the vault.
|
||||
|
||||
---
|
||||
|
||||
## Backlinks
|
||||
|
||||
- [[clients/valleywide]] — VWP is the live pilot client; 21 Yealink devices in YMCS,
|
||||
`vwp.91912.service` on the PBX, Syncro ticket #32375 (New Phone Install) open.
|
||||
- Skills: `.claude/skills/packetdial/` (`packetdial`) and `.claude/skills/yealink-ymcs/`
|
||||
(`yealink-ymcs`).
|
||||
Reference in New Issue
Block a user