# auto-port-forward

Keeps **qBittorrent**'s listening port in sync with the port **Proton VPN**
forwards, on Windows. Runs every 5 minutes via Task Scheduler.

## Why this exists

Proton VPN assigns a forwarded port and **rotates it on every reconnect**.
qBittorrent has to listen on exactly that port or no peer can reach you — your
tracker (e.g. MAM) reports *"client can not accept incoming connections / wrong
port"* and you seed/leech nothing.

The original script read the port by scraping Windows toast notifications
(`wpndatabase.db`). The current Proton client **stopped writing those toasts**
(~May 2026) and shows the port in-app instead, so that approach silently went
stale: it kept the last port it ever saw and missed every rotation after a
reconnect. An internet blip → Proton reconnect → new port → qBit stuck on the
old one → tracker connectivity dead.

## How it works now

`ProtonPortUpdate.ps1` asks Proton's gateway for the live port directly over
**NAT-PMP** (RFC 6886) — the same mechanism the Proton client itself uses:

1. Find the Proton tunnel adapter; derive the gateway (`10.2.0.2` → `10.2.0.1`).
2. Send a NAT-PMP mapping request to `gateway:5351`; the reply carries the
   assigned public port.
3. Read qBittorrent's current `listen_port` via its Web API.
4. If they differ, push the new port into qBittorrent.
5. Notify on a drift-fix or a failure (see below), debounced so the same
   condition doesn't alert every 5 minutes.

This has **no dependency on notifications** and is always current.

## Files

| File | Purpose |
|------|---------|
| `ProtonPortUpdate.ps1` | The sync worker. |
| `RunPortSync.vbs` | Silent launcher (no console window). Resolves the `.ps1` path relative to itself, so the folder can live anywhere. |
| `Debug.ps1` | 9-check diagnostic (PSSQLite, adapter, qBit, Web UI, auth, notification DB, Proton logs). Legacy — kept for reference. |
| `push.config.json` | *(optional, you create it)* Command Center push config — see below. **Not committed; contains a token.** |
| `port-sync.log` | Rolling log (auto-trimmed to ~500 lines). |
| `port-sync.state.json` | Debounce state so notifications fire on transitions only. |

## Scheduled task

Task name **`ProtonVPN Port Sync`** runs `wscript.exe "<this folder>\RunPortSync.vbs"`
every 5 minutes.

> **Run it non-elevated.** The task's `RunLevel` must be **Limited**, not
> Highest — Windows refuses to show toast notifications from elevated processes
> ("notification platform is unavailable"). The script needs no admin rights.

To repoint/inspect the task:

```powershell
Get-ScheduledTask -TaskName 'ProtonVPN Port Sync' | Select-Object -Expand Actions
Start-ScheduledTask  -TaskName 'ProtonVPN Port Sync'   # run once now
```

## One-time setup

1. **qBittorrent Web UI** enabled (Tools → Options → Web UI); note user/pass.
2. **Store the qBit password** encrypted (DPAPI, current-user only):
   ```powershell
   $s = ConvertTo-SecureString "YourPassword" -AsPlainText -Force
   $s | ConvertFrom-SecureString | Out-File "$env:LOCALAPPDATA\qBittorrentPassword.txt"
   ```
3. In qBittorrent → Options → Connection: set a fixed **Listening Port** and
   **uncheck** *"Use UPnP / NAT-PMP port forwarding from my router"* (it fights
   the VPN-assigned port).
4. In Proton VPN: **Port forwarding ON**.

## Notifications

Two channels, both fire on the same events:

- **Desktop toast** — non-modal Windows toast. Requires the task to be
  non-elevated (see above). Failures fall back to a `msg.exe` popup.
- **Phone push (Command Center)** — *optional*. Create `push.config.json` here:
  ```json
  {
    "url": "https://adampowell.pro/command-center/api/portsync/report",
    "token": "<CC_PORTSYNC_TOKEN>"
  }
  ```
  The script POSTs `{title, body, severity, data}` with a bearer token to the CC
  `/api/portsync/report` endpoint, which fans it out as a Web Push to your
  phone/PWA (same pipeline as the disk-space alerts). The token must match the
  droplet's `CC_PORTSYNC_TOKEN` env var.

Events that notify (each once, until the condition changes):

| Event | Severity |
|-------|----------|
| Port drifted and was corrected | info |
| Proton not connected | warning |
| qBittorrent not running | warning |
| NAT-PMP query failed (port forwarding off?) | critical |
| qBit auth/read failed, or rejected the port update | critical |
| Recovered back into sync after a failure | info |

## Manual run / troubleshooting

```powershell
# Run the worker directly and watch output:
powershell -NoProfile -ExecutionPolicy Bypass -File ".\ProtonPortUpdate.ps1"

# What port is Proton forwarding right now (NAT-PMP)?  Compare to the Proton app.
# What port is qBittorrent on?  Options → Connection, or qBittorrent.ini Session\Port.
```

If the tracker still says "wrong port" after a sync, confirm in the Proton app
that **Port forwarding** is ON and qBittorrent's *Use UPnP/NAT-PMP* box is
**off**.
