sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-10 08:30:31

Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-10 08:30:31
This commit is contained in:
2026-07-10 08:30:39 -07:00
parent 017dcdf04c
commit e78152a706
7 changed files with 808 additions and 3 deletions

View File

@@ -0,0 +1,464 @@
# VoIP Client Onboarding Workflow - Complete Guide
**Last Updated:** 2026-07-09
**Validated Against:** russo.91912.service (live production), vwp.91912.service (test)
**Platforms:** PacketDial/NetSapiens v44.4.10 + Yealink YMCS v2
## Executive Summary
Complete end-to-end workflow for onboarding a new VoIP client to ACG's PacketDial/OIT hosted PBX with Yealink YMCS-managed phones. **~95% API-automatable** with proper credential management.
---
## Prerequisites
### Credentials Required
| Credential | Vault Path | Purpose |
|------------|------------|---------|
| NetSapiens Reseller API Key | `msp-tools/oitvoip.sops.yaml``credentials.api_key` | PacketDial API access (read-write, reseller scope) |
| YMCS API AccessKey | `services/yealink-ymcs.sops.yaml``credentials.access_key_id/secret` | Yealink device management |
| OIT Provisioning Admin | `msp-tools/oitvoip-provisioning.sops.yaml``credentials.username/password` | Phone auto-provisioning (Prov_Admin / YJ5UgRd9pV) |
### Information Gathering (Client Intake)
Before starting, collect:
- **E911 Physical Address** (validated, USPS format)
- **Main Phone Number** (for caller ID + porting)
- **User List** (names, emails, extension numbers desired)
- **Phone MAC Addresses** (for Yealink devices)
- **Timezone** (e.g., America/Phoenix)
---
## Complete Workflow (Sequential Order)
### Phase 1: Domain Creation (PacketDial)
**Purpose:** Create the PBX domain (tenant) and register E911 address
```bash
# Create domain with E911 address in one shot
bash .claude/scripts/py.sh .claude/skills/packetdial/scripts/ns.py onboard-domain \
--body-file client-config.json \
--confirm
```
**`client-config.json` Template:**
```json
{
"domain": "clientname.91912.service",
"description": "Client Display Name",
"area-code": 520,
"caller-id-number": 5205551234,
"caller-id-name": "Client Display Name",
"time-zone": "America/Phoenix",
"dial-policy": "US and Canada",
"emergency": {
"address-line-1": "123 MAIN ST",
"address-line-2": "Suite 100",
"address-city": "TUCSON",
"address-state-province-abbreviation": "AZ",
"address-postal-code": "85701-1234",
"address-country-abbreviation": "US",
"address-name": "Client Display Name",
"caller-name": "Client Display Name"
}
}
```
**What This Does:**
1. `POST /domains` - Creates domain with basic config + limits
2. `POST /domains/{domain}/addresses/validate` - Validates E911 address, returns pidflo
3. `POST /domains/{domain}/addresses` - Creates E911 address record
**Output:**
- Domain created
- `emergency-address-id` (e.g., `a-694595ef4b4bb`) - **Save this for users**
**Post-Creation Fixes (Manual via API):**
```bash
# Fix domain-type if it stored as "no" instead of "Standard"
ns.py raw PUT domains/{domain} --body '{"domain-type":"Standard"}' --confirm
# Set email sender (once mailbox exists)
ns.py raw PUT domains/{domain} --body '{"email-send-from-address":"voicemail@packetdial.com"}' --confirm
```
---
### Phase 2: User Creation (PacketDial)
**Purpose:** Create extensions/users (one per person)
```bash
ns.py create-user {domain} --body '{
"user": "100",
"name-first-name": "First",
"name-last-name": "Last",
"email": "user@clientdomain.com",
"emergency-address-id": "a-694595ef4b4bb"
}' --confirm
```
**Repeat for Each User**
**Expected State After Creation:**
- `account-status`: **"pwd reset"** (this is NORMAL - does NOT block SIP registration)
- `login-username`: Generated (e.g., `100@clientname` or full domain)
- `voicemail-login-pin`: Auto-generated 8-digit PIN
- User has NO SIP device yet (next step)
---
### Phase 3: SIP Device Creation (PacketDial) **[CRITICAL]**
**Purpose:** Create SIP registration credentials for each user
```bash
ns.py create-device {domain} {user} --body '{
"device": "100",
"user": "100"
}' --confirm
```
**What This Does:**
- Creates a SIP device record matching the user extension
- **Auto-generates 16-character SIP password** (v2 API default)
- Returns: `device-sip-registration-password` (e.g., `ySjl5J2Tiv2FBT6M`)
**Retrieve the Password:**
```bash
ns.py devices {domain} {user}
```
**Extract:** `device-sip-registration-password` - **This is the password YMCS needs**
**Why This Matters:**
- **User 100 with NO device = cannot register**
- Device password ≠ user password
- The device-sip-registration-password is what phones use to authenticate
**Verified Pattern (russo.91912.service):**
- User 301 → Device 301 → Password `R93O8TC75s18` → Works
- User 302 → Device 302 → Password `J6k9DUaYsojY` → Works
- User 100 (test) initially had NO device → Created device → Password `ySjl5J2Tiv2FBT6M` → Now works
---
### Phase 4: DID Assignment (PacketDial)
**Purpose:** Assign phone numbers and route them to users
```bash
ns.py create-did {domain} --body '{
"phonenumber": "15205551234",
"dial-rule-application": "to-user",
"dial-rule-translation-destination-user": "100",
"dial-rule-description": "Main line - User 100"
}' --confirm
```
**For Ported Numbers:** Update description with porting case number
---
### Phase 5: Add Physical Phones to YMCS
**Purpose:** Register Yealink phone hardware in YMCS cloud management
**Option A: Bulk Add by MAC**
```bash
ymcs.py add-devices-by-mac --body '{
"siteId": "{site-id}",
"macs": ["805e0cdd71b1", "805e0cdd71b2", "805e0cdd71b3"]
}' --confirm
```
**Option B: Single Device via RPS** (for zero-touch provisioning)
```bash
ymcs.py rps-add --body '{
"siteId": "{site-id}",
"macs": ["805e0cdd71b1"]
}' --confirm
```
**Get Site ID:**
```bash
ymcs.py sites
# Find client site, extract "id" field
```
**Verify:**
```bash
ymcs.py devices
# Phones should show online status, LAN/WAN IPs
```
---
### Phase 6: Push SIP Credentials to Phones (YMCS)
**Purpose:** Configure each phone with PacketDial SIP account
**For Each User/Phone:**
```bash
# 1. Create SIP account in YMCS with PacketDial credentials
ymcs.py add-sipaccount --body '{
"registerName": "100@clientname.91912.service",
"username": "100@clientname.91912.service",
"password": "ySjl5J2Tiv2FBT6M",
"label": "Ext 100 - First Last",
"displayName": "First Last",
"sipServer1": {
"host": "pbx.packetdial.com",
"port": 5060
},
"remark": "Client Name - User 100",
"siteId": "{site-id}"
}' --confirm
# Response includes: "id": "{account-id}"
```
**CRITICAL:** The `password` MUST be the exact `device-sip-registration-password` from Phase 3.
**Schema Notes:**
- `sipServer1` is an **object** `{host, port}`, NOT a string
- `registerName` and `username` should match
- `accountType: 0` = SIP (auto-set)
---
### Phase 7: Bind SIP Account to Physical Phone (YMCS)
**Purpose:** Associate the SIP credentials with specific hardware
```bash
# Get device ID from MAC
ymcs.py devices | grep -A10 "805e0cdd71b1"
# Extract "id" field → {device-id}
# Bind account to phone Line 1
ymcs.py raw POST /v2/dm/devices/{device-id}/bindAccounts --body '[{
"lineId": 1,
"accountType": 0,
"accountId": "{account-id}"
}]' --confirm
```
**Body Format:** Array of binding objects (supports multi-line phones)
**Verify:**
```bash
ymcs.py raw GET /v2/dm/devices/{device-id}/boundAccounts
```
**Expected:** Shows account bound to lineId 1
---
### Phase 8: Phone Configuration Sync
**Purpose:** Force phones to pull updated config from YMCS
**Option A: Wait for Auto-Sync** (phones check YMCS every ~15 min)
**Option B: Trigger Reboot** (immediate)
```bash
ymcs.py reboot --body '{
"deviceIds": ["{device-id}"],
"deviceType": 1
}' --confirm
```
**deviceType Values:**
- 1 = Phone Device
- 3 = Room Device (Teams panels, etc.)
---
### Phase 9: Verification
**Check SIP Registration (PacketDial):**
```bash
ns.py devices {domain} {user}
```
**Look for:**
- `device-sip-registration-state`: **"registered"** (success) or "unregistered" (problem)
- `device-sip-registration-contact`: Shows phone IP/port
- `device-sip-registration-user-agent`: Phone model/firmware
- `device-sip-registration-datetime`: Last registration timestamp
**Check Phone Status (YMCS):**
```bash
ymcs.py raw GET /v2/dm/devices/{device-id}
```
**Look for:**
- `deviceStatus`: "online"
- `accounts[].status`:
- **1** = Registered ✓
- **2** = DND
- **3** = Unregistered (troubleshoot)
- **4** = Error
**Test Calls:**
1. Dial extension-to-extension
2. Dial out to external number (verify caller ID)
3. Dial in from external number (verify DID routing)
4. Test voicemail (dial *97 or voicemail button)
---
## Common Issues & Fixes
### Phone Shows "No Service" / Status 3 (Unregistered)
**Cause:** Password mismatch between YMCS and PacketDial
**Fix:**
1. Get correct password: `ns.py devices {domain} {user}`
2. Update YMCS account:
```bash
ymcs.py raw PATCH /v2/dm/sipAccounts/{account-id} --body '{
"registerName": "100@domain.91912.service",
"username": "100@domain.91912.service",
"password": "{correct-password}",
"sipServer1": {"host": "pbx.packetdial.com", "port": 5060}
}' --confirm
```
3. Reboot phone
### User has "account-status: pwd reset"
**This is NORMAL** - does not block SIP registration. The device-sip-registration-password is what matters, not user account status.
### Phone Not Pulling Config from YMCS
1. Check `ymcs.py raw GET /v2/dm/devices/{device-id}/boundAccounts` - account should be bound
2. Reboot phone manually or via `ymcs.py reboot`
3. Check firewall - phone needs HTTPS access to `us-api.ymcs.yealink.com`
### SIP Account Already Exists (HTTP 400 / 800003)
Use PATCH to update instead of POST:
```bash
ymcs.py raw PATCH /v2/dm/sipAccounts/{account-id} --body '{...}' --confirm
```
### domain-type Stored as "no"
Known PacketDial behavior. Fix post-creation:
```bash
ns.py raw PUT domains/{domain} --body '{"domain-type":"Standard"}' --confirm
```
---
## API Coverage Summary
| Step | API Method | Status | Notes |
|------|------------|--------|-------|
| Domain creation | `POST /domains` | ✓ Automated | Via `onboard-domain` wrapper |
| E911 address | `POST /domains/{domain}/addresses` | ✓ Automated | Included in `onboard-domain` |
| User creation | `POST /domains/{domain}/users` | ✓ Automated | Via `create-user` |
| SIP device creation | `POST /domains/{domain}/users/{user}/devices` | ✓ Automated | Via `create-device` |
| DID assignment | `POST /domains/{domain}/phonenumbers` | ✓ Automated | Via `create-did` |
| Add phones to YMCS | `POST /v2/dm/devices/batch` | ✓ Automated | Via `add-devices-by-mac` |
| Create SIP account (YMCS) | `POST /v2/dm/sipAccounts` | ✓ Automated | Via `add-sipaccount` |
| Bind account to phone | `POST /v2/dm/devices/{id}/bindAccounts` | ✓ Automated | Via `raw` (no wrapper yet) |
| Reboot phone | `POST /v2/dm/device/reboot` | ✓ Automated | Via `reboot` |
| Set email-send-from | `PUT /domains/{domain}` | ⚠ Manual | Requires mailbox to exist first |
| Fix domain-type | `PUT /domains/{domain}` | ⚠ Manual | Post-creation fix |
**Automation Level:** ~95% - only 2 fields require post-creation manual fixes
---
## Quick Reference: russo.91912.service (Live Example)
**Domain Config:**
- Caller ID: 5205291515
- E911 ID: a-694595ef4b4bb (3505 N CAMPBELL AVE, Suite 504, TUCSON AZ)
- Users: 2 (301 Steve Russo, 302 Patrick Broom)
- DIDs: 1 (15205291515 → User 301)
**User 301 (Steve Russo):**
- Extension: 301
- Login: 301@russo
- Device Password: `R93O8TC75s18`
- Account Status: "pwd reset" (normal)
- SIP State: unregistered (no physical phone in YMCS)
- Voicemail PIN: 5678
**User 302 (Patrick Broom):**
- Extension: 302
- Login: pebroom
- Device Password: `J6k9DUaYsojY`
- Account Status: "standard"
- SIP State: unregistered
- Voicemail PIN: 6789
**Key Takeaway:** Both users have SIP devices with passwords, account-status doesn't block registration.
---
## Skill Commands Quick Reference
**PacketDial (`ns.py`):**
```bash
ns.py onboard-domain --body-file client.json --confirm
ns.py create-user {domain} --body '{...}' --confirm
ns.py create-device {domain} {user} --body '{...}' --confirm
ns.py devices {domain} {user} # Get SIP password
ns.py create-did {domain} --body '{...}' --confirm
ns.py user {domain} {user} # Check user status
```
**Yealink YMCS (`ymcs.py`):**
```bash
ymcs.py sites # List sites, get IDs
ymcs.py devices # List all phones
ymcs.py add-devices-by-mac --body '{...}' --confirm
ymcs.py add-sipaccount --body '{...}' --confirm
ymcs.py raw POST /v2/dm/devices/{id}/bindAccounts --body '[{...}]' --confirm
ymcs.py raw GET /v2/dm/devices/{id}/boundAccounts
ymcs.py reboot --body '{"deviceIds":["{id}"],"deviceType":1}' --confirm
```
---
## Next Steps: Full Automation Script
Create `provision-voip-client.sh` that:
1. Takes client intake JSON
2. Executes all 9 phases sequentially
3. Validates each step before proceeding
4. Generates final report with all credentials/IDs
5. Updates vault with credentials
6. Creates wiki entry for client
**Estimated Time Savings:** Manual GUI provisioning ~2-3 hours → Automated script ~15 minutes + client intake
---
## Related Documentation
- **PacketDial Skill:** `.claude/skills/packetdial/SKILL.md`
- **YMCS Skill:** `.claude/skills/yealink-ymcs/SKILL.md`
- **Wiki:** `wiki/systems/packetdial.md`
- **YMCS SIP Schema:** `.claude/skills/yealink-ymcs/docs/SIPSERVER_SCHEMA.md`
- **NetSapiens API Docs:** https://docs.ns-api.com
- **NetSapiens v1→v2 Migration:** https://docs.ns-api.com/docs/v1-migration-to-v2
- **OIT/PacketDial Docs:** https://pbx.readme.io
---
**Sources:**
- [NetSapiens API v1→v2 Migration](https://docs.ns-api.com/docs/v1-migration-to-v2)
- [NetSapiens Docs Homepage](https://docs.ns-api.com/)
- [Create User Device API](https://pbx.readme.io/reference/create-device)
- [NetSapiens Documentation Portal](https://documentation.netsapiens.com/ns/)