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:
464
.claude/skills/packetdial/docs/VOIP_ONBOARDING_WORKFLOW.md
Normal file
464
.claude/skills/packetdial/docs/VOIP_ONBOARDING_WORKFLOW.md
Normal 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/)
|
||||
50
.claude/skills/packetdial/scripts/provision-vwp-devices.sh
Normal file
50
.claude/skills/packetdial/scripts/provision-vwp-devices.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Create SIP devices for VWP extensions 101-118
|
||||
# This auto-generates the device-sip-registration-password for each user
|
||||
|
||||
DOMAIN="vwp.91912.service"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PY_WRAPPER="bash $SCRIPT_DIR/../../../.claude/scripts/py.sh"
|
||||
|
||||
EXTENSIONS=(101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118)
|
||||
|
||||
echo "[INFO] Creating SIP devices for 18 VWP extensions..."
|
||||
echo ""
|
||||
|
||||
SUCCESS=0
|
||||
FAILED=0
|
||||
|
||||
for ext in "${EXTENSIONS[@]}"; do
|
||||
echo "[INFO] Creating device for extension $ext"
|
||||
|
||||
# Create device (device name = user extension)
|
||||
BODY="{\"device\":\"$ext\",\"user\":\"$ext\"}"
|
||||
RESULT=$($PY_WRAPPER "$SCRIPT_DIR/ns.py" create-device "$DOMAIN" "$ext" --body "$BODY" --confirm 2>&1)
|
||||
|
||||
if echo "$RESULT" | grep -q "HTTP 400.*already exists"; then
|
||||
echo " [SKIP] Device $ext already exists"
|
||||
((SUCCESS++))
|
||||
elif echo "$RESULT" | grep -qE "HTTP [45]"; then
|
||||
echo " [ERROR] Failed to create device $ext: $RESULT"
|
||||
((FAILED++))
|
||||
else
|
||||
echo " [OK] Created device $ext"
|
||||
((SUCCESS++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[SUMMARY] Devices: $SUCCESS created/existing, $FAILED failed"
|
||||
echo ""
|
||||
echo "[INFO] Fetching all device passwords..."
|
||||
echo ""
|
||||
|
||||
# Get all passwords
|
||||
for ext in "${EXTENSIONS[@]}"; do
|
||||
PASSWORD=$($PY_WRAPPER "$SCRIPT_DIR/ns.py" devices "$DOMAIN" "$ext" 2>/dev/null | grep -o '"device-sip-registration-password": "[^"]*"' | cut -d'"' -f4)
|
||||
if [ -n "$PASSWORD" ]; then
|
||||
echo "Extension $ext: $PASSWORD"
|
||||
else
|
||||
echo "Extension $ext: [ERROR - Could not retrieve password]"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Provision 11 matched VWP extensions with correct M365 emails
|
||||
# Domain: vwp.91912.service
|
||||
# E911 Address: a-6a395c03d4cfe
|
||||
|
||||
DOMAIN="vwp.91912.service"
|
||||
E911="a-6a395c03d4cfe"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PY_WRAPPER="bash $SCRIPT_DIR/../../../.claude/scripts/py.sh"
|
||||
|
||||
# Matched users: extension|first_name|last_name|email
|
||||
USERS=(
|
||||
"102|Jesse|Guerrero|jesse@valleywideplastering.com"
|
||||
"103|Rose|Guerrero|rose@valleywideplastering.com"
|
||||
"104|Shelly|Dooley|shelly@valleywideplastering.com"
|
||||
"105|JR|Guerrero|j-r@valleywideplastering.com"
|
||||
"107|Payroll|Department|payroll@valleywideplastering.com"
|
||||
"109|Kayla|Guerrero|kayla@valleywideplastering.com"
|
||||
"110|Ron|Winger|ron@valleywideplastering.com"
|
||||
"113|Chris|Guerrero|chris@valleywideplastering.com"
|
||||
"114|Ty|Fetters|Ty@CASARICA.NET"
|
||||
"115|Toni|Billing|billing@valleywideplastering.onmicrosoft.com"
|
||||
"116|Bart|Graffin|estimating@valleywideplastering.com"
|
||||
)
|
||||
|
||||
echo "[INFO] Creating 11 matched VWP users in PacketDial..."
|
||||
echo ""
|
||||
|
||||
SUCCESS=0
|
||||
FAILED=0
|
||||
|
||||
for user_data in "${USERS[@]}"; do
|
||||
IFS='|' read -r ext first last email <<< "$user_data"
|
||||
|
||||
echo "[INFO] Creating extension $ext: $first $last ($email)"
|
||||
|
||||
# Build JSON body (name-last-name is required)
|
||||
BODY="{\"user\":\"$ext\",\"name-first-name\":\"$first\",\"name-last-name\":\"$last\",\"email\":\"$email\",\"emergency-address-id\":\"$E911\"}"
|
||||
|
||||
# Create user
|
||||
RESULT=$($PY_WRAPPER "$SCRIPT_DIR/ns.py" create-user "$DOMAIN" --body "$BODY" --confirm 2>&1)
|
||||
|
||||
if echo "$RESULT" | grep -q "HTTP 400.*already exists"; then
|
||||
echo " [SKIP] Extension $ext already exists"
|
||||
((SUCCESS++))
|
||||
elif echo "$RESULT" | grep -qE "HTTP [45]"; then
|
||||
echo " [ERROR] Failed to create extension $ext: $RESULT"
|
||||
((FAILED++))
|
||||
else
|
||||
echo " [OK] Created extension $ext"
|
||||
((SUCCESS++))
|
||||
fi
|
||||
|
||||
# Small delay to avoid rate limiting
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[SUMMARY] Users: $SUCCESS created/existing, $FAILED failed"
|
||||
echo "[INFO] Waiting 30 seconds for async processing..."
|
||||
sleep 30
|
||||
|
||||
echo ""
|
||||
echo "[INFO] Verifying user creation..."
|
||||
ACTUAL=$($PY_WRAPPER "$SCRIPT_DIR/ns.py" users "$DOMAIN" 2>/dev/null | grep -c '"user"')
|
||||
echo "[INFO] PacketDial reports $ACTUAL users total"
|
||||
64
.claude/skills/packetdial/scripts/provision-vwp-users.sh
Normal file
64
.claude/skills/packetdial/scripts/provision-vwp-users.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Provision VWP extensions 101-118
|
||||
# Domain: vwp.91912.service
|
||||
# E911 Address: a-6a395c03d4cfe
|
||||
|
||||
DOMAIN="vwp.91912.service"
|
||||
E911="a-6a395c03d4cfe"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PY_WRAPPER="bash $SCRIPT_DIR/../../../.claude/scripts/py.sh"
|
||||
|
||||
# User list: extension|first_name|last_name|email
|
||||
USERS=(
|
||||
"101|Natalya||natalya@vwp.com"
|
||||
"102|Jesse||jesse@vwp.com"
|
||||
"103|Rose||rose@vwp.com"
|
||||
"104|Shelly||shelly@vwp.com"
|
||||
"105|J.R.||jr@vwp.com"
|
||||
"106|Tammy||tammy@vwp.com"
|
||||
"107|Payroll|Department|payroll@vwp.com"
|
||||
"108|Shannon||shannon@vwp.com"
|
||||
"109|Kayla||kayla@vwp.com"
|
||||
"110|Ron||ron@vwp.com"
|
||||
"111|Kitchen|Phone|kitchen@vwp.com"
|
||||
"112|Conference|Room|conferenceroom@vwp.com"
|
||||
"113|Chris||chris@vwp.com"
|
||||
"114|TY||ty@vwp.com"
|
||||
"115|Toni||toni@vwp.com"
|
||||
"116|Bart||bart@vwp.com"
|
||||
"117|Jesse|III|jesse3@vwp.com"
|
||||
"118|Warehouse|Phone|warehouse@vwp.com"
|
||||
)
|
||||
|
||||
echo "[INFO] Creating 18 VWP users in PacketDial..."
|
||||
echo ""
|
||||
|
||||
SUCCESS=0
|
||||
FAILED=0
|
||||
|
||||
for user_data in "${USERS[@]}"; do
|
||||
IFS='|' read -r ext first last email <<< "$user_data"
|
||||
|
||||
echo "[INFO] Creating extension $ext: $first $last"
|
||||
|
||||
# Build JSON body (name-last-name is required even if empty)
|
||||
LAST_NAME="${last:-.}"
|
||||
BODY="{\"user\":\"$ext\",\"name-first-name\":\"$first\",\"name-last-name\":\"$LAST_NAME\",\"email\":\"$email\",\"emergency-address-id\":\"$E911\"}"
|
||||
|
||||
# Create user
|
||||
RESULT=$($PY_WRAPPER "$SCRIPT_DIR/ns.py" create-user "$DOMAIN" --body "$BODY" --confirm 2>&1)
|
||||
|
||||
if echo "$RESULT" | grep -q "HTTP 400.*already exists"; then
|
||||
echo " [SKIP] Extension $ext already exists"
|
||||
((SUCCESS++))
|
||||
elif echo "$RESULT" | grep -qE "HTTP [45]"; then
|
||||
echo " [ERROR] Failed to create extension $ext: $RESULT"
|
||||
((FAILED++))
|
||||
else
|
||||
echo " [OK] Created extension $ext"
|
||||
((SUCCESS++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "[SUMMARY] Users: $SUCCESS created/existing, $FAILED failed"
|
||||
@@ -0,0 +1,132 @@
|
||||
# Add Global Username and Password to Yealink RPS Server Profile.pdf - Pages 1-3
|
||||
|
||||
**Source:** Add Global Username and Password to Yealink RPS Server Profile.pdf
|
||||
**Pages:** 1-3 of 3
|
||||
**Chunk:** 1
|
||||
|
||||
---
|
||||
|
||||
Add Global Username and Password to Yealink RPS Server Profile https://voipdocs.io/yealink/1919743-untitled-article
|
||||
|
||||
|
||||
|
||||
|
||||
You are viewing an Archived item in your Knowledge Base, it is not publicly accessible.
|
||||
|
||||
|
||||
|
||||
|
||||
Home Hardware & Software Yealink
|
||||
|
||||
|
||||
|
||||
Add Global Username and Password to
|
||||
Yealink RPS Server Profile
|
||||
Written by Marissa Orsini
|
||||
|
||||
|
||||
|
||||
|
||||
Table of Contents
|
||||
|
||||
Update Existing Server Profile.
|
||||
|
||||
|
||||
|
||||
|
||||
Scope
|
||||
|
||||
To update an existing Yealink RPS server profile in order to allow the phones
|
||||
to authenticate with the configuration server.
|
||||
|
||||
|
||||
|
||||
Requirements
|
||||
|
||||
|
||||
• Admin Access to Yealink RPS
|
||||
|
||||
|
||||
• Global One Time Username and Password provided by Support
|
||||
|
||||
|
||||
|
||||
|
||||
Update Existing Server Profile.
|
||||
|
||||
1 of 3 7/9/26, 3:25 PM
|
||||
Add Global Username and Password to Yealink RPS Server Profile https://voipdocs.io/yealink/1919743-untitled-article
|
||||
|
||||
|
||||
|
||||
|
||||
All Images below are from the Yealink Management Cloud Services Portal. Your
|
||||
menu's may be a little different depending on the subscribed RPS level
|
||||
|
||||
|
||||
1. Log in to the Yealink DM Console
|
||||
2. Once logged in locate the RPS Management section.
|
||||
|
||||
|
||||
|
||||
|
||||
3. Select "Server Management" from the top menu.
|
||||
|
||||
|
||||
|
||||
|
||||
4. Select the Server Profile you wish to update.
|
||||
|
||||
|
||||
|
||||
|
||||
5. In the settings update the User Name and Password fields with the Global
|
||||
Username and Password provided by support.
|
||||
|
||||
|
||||
|
||||
|
||||
6. Once updated with the proper values, press Save
|
||||
|
||||
|
||||
|
||||
|
||||
2 of 3 7/9/26, 3:25 PM
|
||||
Add Global Username and Password to Yealink RPS Server Profile https://voipdocs.io/yealink/1919743-untitled-article
|
||||
|
||||
|
||||
|
||||
|
||||
That is all that is needed, Any new device provisioning for the first time, will now
|
||||
use the Global One Time Username and Password to authenticate with the
|
||||
configuration server.
|
||||
|
||||
|
||||
|
||||
|
||||
global auth yealink rps
|
||||
|
||||
|
||||
|
||||
|
||||
Was this article helpful?
|
||||
|
||||
|
||||
Yes No
|
||||
|
||||
|
||||
Give feedback about this article
|
||||
|
||||
|
||||
|
||||
|
||||
Related Articles
|
||||
|
||||
|
||||
1. Provisioning Extra Lines to a Yealink Base Station
|
||||
2. Yealink: Screen Capture and Access
|
||||
|
||||
|
||||
|
||||
|
||||
3 of 3 7/9/26, 3:25 PM
|
||||
28
.claude/skills/yealink-ymcs/scripts/.phone-status.json
Normal file
28
.claude/skills/yealink-ymcs/scripts/.phone-status.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "10d6e39239cd411c8e7db51e730a7561",
|
||||
"mac": "805e0cdd71b1",
|
||||
"sn": "201081E061215051",
|
||||
"name": "Reception",
|
||||
"modelId": "a62b22794b5440a3818e6207bccdf4f1",
|
||||
"siteId": "1e7578a6fe0e41cfb5a3e8b40933ffee",
|
||||
"programVersion": "96.86.0.70",
|
||||
"modelName": "SIP-T54W",
|
||||
"siteName": "VWP",
|
||||
"wanIp": "4.18.160.106",
|
||||
"lanIp": "172.16.9.144",
|
||||
"connectWay": null,
|
||||
"lastReportTime": 1783636084895,
|
||||
"deviceStatus": "online",
|
||||
"accounts": [
|
||||
{
|
||||
"accountId": "a1f0191861194e4c8fab817c2b3ab54d",
|
||||
"lineId": 1,
|
||||
"accountType": 0,
|
||||
"accountServer": "pbx.packetdial.com",
|
||||
"registerName": "100@vwp.91912.service",
|
||||
"username": "100@vwp.91912.service",
|
||||
"status": 3
|
||||
}
|
||||
],
|
||||
"wifi": null
|
||||
}
|
||||
@@ -62,8 +62,8 @@ Plastering / stucco subcontractor based in Arizona. Active ACG client. Primary w
|
||||
| VWP_ADSRVR | 192.168.0.25 | Domain Controller for `VWP.US` (secondary DC / SSH entry point) | Windows Server 2019 Standard (build 17763) | VM on HP ProLiant DL360 Gen10. SSH enabled, key auth working for `vwp\guru` (ed25519, added 2026-04-13). Default shell is cmd.exe — use `powershell -NoProfile -Command` wrappers. Old Net (VLAN 2). |
|
||||
| VWP-DC1 | 172.16.9.2 | PDC emulator for `VWP.US`, NPS/RADIUS | Windows Server 2019 | FQDN `VWP-DC1.VWP.US`. Confirmed up through all sessions. ADWS on this host not reachable over the SSH double-hop from ADSRVR (use LDAP cmdlets instead). |
|
||||
| VWP-QBS | 172.16.9.169 | QuickBooks server + RDS/RemoteApp host | Windows Server 2022 Standard | **Physical Dell server** (NOT a VM). Has DRAC. Runs IIS (RD Web Access). WinRM on 5985. Reach from ADSRVR via `Invoke-Command -ComputerName VWP-QBS -Credential` with `vwp\sysadmin` PSCredential. |
|
||||
| Dell DRAC (VWP-QBS) | [undocumented] | Out-of-band management for VWP-QBS Dell | — | DRAC functional as of 2026-04-22. IP not yet documented. Vault: `clients/valleywide/quickbooks-server-idrac`. |
|
||||
| VWP-HYPERV1 | 172.16.9.184 | Hyper-V host — primary VM host for new infrastructure | Windows Server 2025 | Dell R740, 112 vCPU / 255 GB RAM, C: 10.7 TB. One external vSwitch on Intel 10G NIC. VHDs in `C:\VHD`. GuruRMM agent `bdc3e142-...`. Added 2026-06-13. |
|
||||
| Dell DRAC (VWP-QBS) | 192.168.3.189 | Out-of-band management for VWP-QBS Dell | — | iDRAC 9, PowerEdge R640, Service Tag C84TTQ2. HTTPS console. Vault: `clients/vwp/quickbooks-server-idrac`. Documented 2026-07-07. |
|
||||
| VWP-HYPERV1 | 172.16.9.184; iDRAC **172.16.9.151** | Hyper-V host — primary VM host for new infrastructure | Windows Server 2025 | Dell R740, 112 vCPU / 255 GB RAM, C: 10.7 TB. One external vSwitch on Intel 10G NIC. VHDs in `C:\VHD`. GuruRMM agent `bdc3e142-...`. Added 2026-06-13. **iDRAC 172.16.9.151** (credentials: vault `clients/vwp/quickbooks-server-idrac` — same password). Documented 2026-07-10 during CRITICAL_PROCESS_DIED recovery. |
|
||||
| VWP-FILES | 192.168.0.20 (single-homed, VLAN 2; gw 192.168.0.1) | G: file share server (19 SMB shares) | Windows Server 2019 Gen2 VM on VWP-HYPERV1 | Block-migrated from SERVER3 G: VDI (100 GB, ~88 GB used). **Single-homed on 192.168.0.20 since 2026-06-15** — the former 172.16.9.132 vNIC was disconnected at the Hyper-V host to fix cross-VLAN scan-to-folder (the Brother copier hard-codes `\\192.168.0.20`; the multi-homed config had a gateway only on the .132 NIC, so replies to off-subnet clients were dropped — see Patterns). The .132 vNIC is DISCONNECTED at the host (reversible), not removed. DNS registers .20 only. GuruRMM enrolled (site Main Office, agent `8e02fbbc-...`). MSP360 backup running green. **SMB1 server ENABLED 2026-06-23** (#32448) so the legacy XP Orders VM (V-XP) can map `\\VWP-FILES\G-drive` — Server 2019 defaults SMB1-off and XP speaks only SMB1; security tech-debt, remove once Orders is off XP. |
|
||||
| XenServer | 192.168.0.104 | VM hypervisor — hosts remaining VMs | XenServer 7.6 (PowerEdge R720) | SERVER3 VM (the old "server 2003", upgraded in-place to 2008) is now **powered off and retired**; snapshots retained for rollback. Vault: `clients/vwp/xenserver`. |
|
||||
| WINFileSvr | 192.168.0.35 | File server — serves **O:** (`Office_Archive`, ~570 GB / 138K files) + **P:** (`Estimating Archive` = F: root, ~545 GB / 142K files), both GPO-mapped to all staff; actively used daily | Windows Server 2019 | Old Net (VLAN 2). **VMware VM on the ESXi host (VMID 11, `WINFilrSrvr`)** — see ESXi inventory. ~1.1 TB live data. Holds `F:\Darv\Darv.rar` (51 GB Darv dev-machine backup) + `F:\Darv\Darv-rar` (extract, trimmed 135→26 GB on 2026-06-14). GuruRMM `62db0264-...`. Candidate to consolidate into VWP-FILES (retire the VM). Do not delete `Darv.rar` until VB6 source verified to compile. |
|
||||
@@ -303,12 +303,13 @@ After VWP-QBS was force-rebooted via iDRAC (192.168.3.189) following a hung OS,
|
||||
| 2026-06-15 | **VWP-FILES scan-to-folder fix.** Copier scan-to-`\\192.168.0.20` broke after the 2026-06-13 cutover — root cause was the dual-homed server having a default gateway only on the 172.16.9.132 NIC, so replies on the .20 NIC to off-subnet clients were dropped (not a VLAN-routing limit; the UDM routes all VLANs). Fix: single-homed VWP-FILES on 192.168.0.20 (gw 192.168.0.1) by disconnecting the .132 vNIC host-side via `Disconnect-VMNetworkAdapter` on VWP-HYPERV1 (in-guest change dropped the RMM agent + auto-rolled-back). .132 vNIC left disconnected (reversible), not removed. Scanner = Brother MFC-L3780CDW (vault `clients/vwp/brother-mfc-l3780cdw`). |
|
||||
| 2026-06-23 | **SMB1 enabled on VWP-FILES** to restore G:/Orders access for the legacy Windows XP app VM (V-XP) after the 6/13 migration — Server 2019 defaults SMB1-off; XP speaks only SMB1. Diagnosed via GuruRMM (payroll desktop G: was fine; Orders runs on V-XP). Ticket #32448, 1.5 hr emergency block deduction (prepay 19.0→17.5 at close). |
|
||||
| 2026-07-07 | **VWP-QBS stuck in orphaned Datto EDR / Infocyte network isolation** after an iDRAC force-reboot. All off-subnet traffic "General failure" (own IP too) while loopback + DHCP worked — a persistent `Infocyte blocking traffic` WFP filter survived the EDR uninstall. Cleared via WFPExplorer (delivered by DRAC virtual media ISO) → deleted the Infocyte filters; back on 172.16.9.169. **~3 hr remote.** See Security Posture 2026-07-07. |
|
||||
| 2026-07-10 | **VWP-HYPERV1 CRITICAL_PROCESS_DIED crash + RAID foreign config recovery.** Dell R740 Hyper-V host (172.16.9.184) crashed at 04:31 UTC with CRITICAL_PROCESS_DIED, taking VWP-FILES and G: drive offline. RAID controller lost its configuration; on reboot required importing foreign config to recognize disks. Recovered remotely via iDRAC 172.16.9.151 (ForceRestart via Redfish API). Server + VWP-FILES + G: drive restored. iDRAC IP documented (was missing from wiki). Same root pattern as 2026-04-22 HP ProLiant NVRAM corruption — Dell controllers losing config after crash/power events. **<1 hr remote.** |
|
||||
|
||||
---
|
||||
|
||||
## Compilation Notes
|
||||
|
||||
**Date range covered:** 2026-04-13 through 2026-07-07.
|
||||
**Date range covered:** 2026-04-13 through 2026-07-10.
|
||||
|
||||
**Items flagged [unverified]:**
|
||||
- M365 MFA and mail flow configuration — never investigated
|
||||
|
||||
Reference in New Issue
Block a user