with one click
vm-deploy
// Deploy application files to the App Server VM (192.168.101.98) via SCP and restart the corresponding native NSSM Windows service. Use when pushing code changes to production.
// Deploy application files to the App Server VM (192.168.101.98) via SCP and restart the corresponding native NSSM Windows service. Use when pushing code changes to production.
| name | vm-deploy |
| description | Deploy application files to the App Server VM (192.168.101.98) via SCP and restart the corresponding native NSSM Windows service. Use when pushing code changes to production. |
| user-invokable | true |
Deploy files to App Server VM and restart the native NSSM Windows service.
Since 2026-04-19: all 9 VM apps are native NSSM Windows services, not Docker containers. Source lives at G:\Apps\<AppName>\. Deploy = SCP files + Restart-Service <app>. Docker Engine on VM is Stopped + Disabled.
# 1. Verify SSH access
ssh -o ConnectTimeout=5 QueueSystem@192.168.101.98 "echo OK"
# 2. Snapshot current service states BEFORE changing anything
ssh QueueSystem@192.168.101.98 'powershell -Command "Get-Service doc-center,loa-queue-display,ip-approval,queue-system,edge-collector,his-dashboard,service-request-hub,nphies-claims,purchasing-tracker | ft Name,Status -AutoSize"'
If SSH fails, STOP. Do not proceed.
All 9 apps live under G:\Apps\<AppName>\ with their own .venv\, logs\, secrets\:
| Service | Port | Local Source | VM Path |
|---|---|---|---|
ip-approval | 8501 | 01_Projects/Inpatient_Approval/deploy/ | G:\Apps\IPApproval\ |
loa-queue-display | 8502 | 01_Projects/LOA_Queue_Display/ | G:\Apps\LOAQueueDisplay\ |
queue-system | 8510 | 01_Projects/Queue_System/app/ | G:\Apps\QueueSystem\ |
edge-collector | 8520 | 01_Projects/Edge_Collector/ | G:\Apps\EdgeCollector\ |
his-dashboard | 8525 | 01_Projects/HIS_Dashboard/ | G:\Apps\HISDashboard\ |
doc-center | 8530 | 01_Projects/DocCenter/app/ | G:\Apps\DocCenter\app\ |
service-request-hub | 8535 | 01_Projects/ServiceRequestHub/app/ | G:\Apps\ServiceRequestHub\app\ |
nphies-claims | 8540 | 01_Projects/NPHIES_Claims/app/ | G:\Apps\NPHIES_Claims\app\ |
purchasing-tracker | 8550 | 01_Projects/Purchasing_Tracker/app/ | G:\Apps\PurchasingTracker\ |
Notes:
app/ subdir (doc-center, service-request-hub, nphies-claims): source code under <base>\app\; .env and .venv at the base.<base>\ root.<base>\ root; NSSM launches python -m streamlit run <app>.py … directly. The docker_entrypoint.py wrapper was retired 2026-04-30 (see .memory/03_LongTerm/decisions/2026-04-30_docker-entrypoint-removal.md).<base>\ root (no app/ subdir despite the container heritage); NSSM launches python app.py directly. docker_entrypoint.py wrapper retired same date.# General pattern (use forward slashes in the remote path for SCP reliability)
scp <local-file> QueueSystem@192.168.101.98:'G:/Apps/<AppName>/<dest-path>/<filename>'
Concrete examples:
# Flask with app/ subdir (doc-center)
scp changes.py QueueSystem@192.168.101.98:'G:/Apps/DocCenter/app/app.py'
# Flask flat (edge-collector)
scp changes.py QueueSystem@192.168.101.98:'G:/Apps/EdgeCollector/collector.py'
# Streamlit (ip-approval)
scp changes.py QueueSystem@192.168.101.98:'G:/Apps/IPApproval/ip_approval_app.py'
# Config change via .env
scp my.env QueueSystem@192.168.101.98:'G:/Apps/<AppName>/.env'
# 1. Quick mtime sanity check
ssh QueueSystem@192.168.101.98 "powershell -Command \"(Get-Item 'G:\Apps\<AppName>\<path>\<file>').LastWriteTime\""
# 2. SHA256 byte-identity — CRITICAL. Catches encoding corruption that mtime cannot.
LOCAL_SHA=$(sha256sum <local-file> | awk '{print $1}')
VM_SHA=$(ssh QueueSystem@192.168.101.98 "powershell -Command \"(Get-FileHash -Algorithm SHA256 'G:\Apps\<AppName>\<path>\<file>').Hash.ToLower()\"")
[ "$LOCAL_SHA" = "$VM_SHA" ] && echo "OK: byte-identical" || echo "FAIL: local=$LOCAL_SHA vm=$VM_SHA"
If timestamp isn't recent or SHA256 doesn't match → STOP. Re-SCP.
Why SHA256 (not just mtime): SCP normally preserves bytes faithfully, but if anyone has been editing the file in-place via RDP (Notepad / PowerShell ISE / Notepad++), the file on VM may have UTF-8 corruption invisible to mtime and invisible to "the app is running" (Python parses corrupted-but-still-valid Python fine; only user-facing strings show garbage glyphs). The 2026-04-29 SRH deploy verified SHA256 on every file — that's the discipline. See feedback_vm_files_scp_only_no_rdp_edit.md.
This step requires elevated PowerShell on the VM (RDP or pre-existing admin session). Non-admin SSH cannot restart services.
Restart-Service <service-name>
Start-Sleep 5
Get-Service <service-name>
After code changes, NSSM automatically restarts the Python process (no image rebuild like Docker). For dependency changes (new packages in requirements.txt):
Stop-Service <service-name>
cd G:\Apps\<AppName>
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
Start-Service <service-name>
ssh QueueSystem@192.168.101.98 "sc.exe query <service-name>" | grep STATE
# Expected: STATE : 4 RUNNING
curl -s -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" --max-time 8 http://192.168.101.98:<port>/
Expected: 200 or 302. If timeout or 500, check logs (Step 6).
ssh QueueSystem@192.168.101.98 "powershell -Command \"Get-Content 'G:\Apps\<AppName>\logs\nssm_stderr.log' -Tail 30\""
Look for: Python tracebacks, import errors, connection failures.
For live tail (attach with Ctrl+C):
ssh QueueSystem@192.168.101.98 "powershell -Command \"Get-Content 'G:\Apps\<AppName>\logs\nssm_stderr.log' -Tail 30 -Wait\""
docker service — it's Disabled on this VM since 2026-04-19; starting it causes the HCS issues documented in 2026-04-19_vm-all-apps-to-nssm.md. If absolutely needed for rollback, re-enable first with Set-Service docker -StartupType Manual.localhost\SQLEXPRESS in .env — use 192.168.101.98,1433 (static IP + port; bypasses UDP 1434 / SQL Browser dependency that caused issues on 2026-04-19).docker_entrypoint.py is retired (2026-04-30). All 4 apps that previously used it (ip-approval, loa-queue-display, purchasing-tracker, queue-system) now load DB creds via _load_db_config() in their db.py / connection function — reads SQL_* env vars from .env (via python-dotenv) plus secrets/sql_password.txt directly. No db_config.json should ever exist in any app dir on the VM; if you see one, the entrypoint regression has come back. See decision 2026-04-30_docker-entrypoint-removal.md.secrets/ dir at G:\Apps\<AppName>\secrets\. Changes to secrets require service restart to pick up (since secrets are read at process start)..env changes require service restart — python-dotenv loads at process start only. Don't rely on in-process reload.doc-center uploads: UPLOAD_ROOT=G:\Apps\DocCenter\files in .env. Do not move this directory without also updating .env.Running is success, Stopped means startup crashed. Check logs.— → hyphen -, arrow → → SUB control char (\x1A, displays as □ or ▯), box-drawing ─ → Ä. The corruption is invisible — Python parses the corrupted file fine, the service starts, requests succeed, only user-facing strings show garbage glyphs. Bit on 2026-05-06: 6 of 8 SRH app/ files corrupted by RDP edits during early-May fixes; users had been seeing garbage in bell-notification status changes for a week without anyone noticing. Discipline: always edit local → SCP → SHA256 verify (Step 2). No exceptions for "quick fixes." If SCP is genuinely unavailable, the only safe in-place pattern on the VM is [IO.File]::WriteAllText('path', $content, [System.Text.UTF8Encoding]::new($false)) — never Set-Content (system codepage in Windows PowerShell 5.1), Out-File (UTF-16 LE BOM in WPS 5.1), or any GUI editor. See feedback_vm_files_scp_only_no_rdp_edit.md.# 1. Stop service
Stop-Service <service-name>
# 2. Revert the file (VM has backup from Step 1 SCP if you kept a copy)
# Either SCP the old version back, or use a Docker-era backup if available:
# Copy-Item G:\Apps\<AppName>\<file>.container.bak G:\Apps\<AppName>\<file>
# 3. Start service
Start-Service <service-name>
For complete Docker rollback (if NSSM has fundamental issues), re-enable Docker and use the preserved compose files in Desktop\infrastructure\, Desktop\ServiceRequestHub\, Desktop\NPHIES_Claims\. See decision record for the full rollback plan.
/morning-status skill — checks all 9 NSSM services health/triage skill — quick priority-ranked service health check.memory/03_LongTerm/decisions/2026-04-19_vm-all-apps-to-nssm.md — full migration context[HINT] Download the complete skill directory including SKILL.md and all related files