diff --git a/wiki/index.md b/wiki/index.md index 97d81533..e5b49b0b 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -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//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 diff --git a/wiki/systems/packetdial.md b/wiki/systems/packetdial.md new file mode 100644 index 00000000..b873640c --- /dev/null +++ b/wiki/systems/packetdial.md @@ -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 ` — +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 ` 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/ --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/ --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/ --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 ` + +**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`).