OpenVPN companion relay for Windows¶
Status: Installed on all eight cities | Last reviewed: 2026-09-21
Use this path when the WireGuard client cannot be installed or operated on a developer's Windows laptop. Shimla was the first validated target. This is a separate OpenVPN server beside the existing WireGuard relay; an OpenVPN client cannot use a WireGuard profile.
The September 18 fleet rollout passed full Dev/QA network checks on every city. See the rollout report for the installed revision, preserved services, laptop acceptance and Grafana token status. Following the shared Dev token rotation, authenticated Dev and QA log queries pass from all eight cities and all six active devcontainers.
The complete laptop-only guide, also included in each private bundle, is Windows OpenVPN setup. It explains installation, import, connection, testing, switching and cleanup. Require OpenVPN Community 2.7+, OpenVPN GUI, Interactive Service and DCO. Device policy may still require IT installation. The separate WireGuard PowerShell helper is not used for OpenVPN DNS.
For periodic host DNS refresh, laptop startup/recovery and 30-minute health checks, see Automatic laptop access maintenance. This optional pilot requires updated client helpers; existing manual profiles retain their snapshot behavior.
Shimla design¶
| Setting | Value |
|---|---|
| Private endpoint | 10.120.206.38:51830/udp |
| OpenVPN subnet | 10.254.248.0/24 |
| Scoped DNS | 10.254.248.1 |
| Container | trueserve-openvpn |
| Private state | /var/lib/trueserve-openvpn |
| Existing WireGuard | Remains on UDP 51828 and 10.254.245.0/24 |
The laptop first connects its existing core VPC VPN. OpenVPN then carries the
explicit TrueServe IPv4 destinations through Shimla's existing egress path.
Dev and QA work in one both profile. UDP is supported, so WebRTC does not
depend on an HTTP proxy. Actual browser audio still needs a laptop test.
The container has its own network namespace, forwarding firewall and NAT. Forwarding is limited to the endpoint snapshot and explicitly reviewed extra media routes. Clients can reach its DNS service but not other container listeners or one another. Private-address destinations are allowed for the official management services. The host's routes, application branch, application containers and WireGuard state must remain unchanged.
No default route, global DNS server, block-outside-dns, or IPv6 tunnel route
is installed. The scoped resolver serves exact known names and filters AAAA
responses. Traffic to destinations outside the bundle, including new media
addresses, is not automatically proxied.
Prepare a city host¶
For fleet installations, transfer the committed scripts/studio-access/
runtime and related guides into /opt/trueserve-access/releases/<commit>.
Use the city values in cities.json, then run on that city host:
release=/opt/trueserve-access/releases/FULL_COMMIT_SHA
sudo python3 "$release/scripts/studio-access/install-city.py" \
--city CITY --endpoint CITY_PRIVATE_IP --network CITY_WIREGUARD_SUBNET \
--revision FULL_COMMIT_SHA --files-only
sudo python3 "$release/scripts/studio-access/install-openvpn-city.py" \
--city CITY --revision FULL_COMMIT_SHA
The first command updates tools, laptop guides and managed agent instructions
on an existing WireGuard relay without rebuilding or restarting it. The second
creates the OpenVPN companion if needed; an existing running OpenVPN container
keeps its process, image, credentials and connected clients. Neither command
changes the application branch or restarts application services. The two
current symlinks may point to the same committed tools release, while private
state remains separate. Later deliberate runtime/image upgrades need a planned
relay restart.
The files-only update checks the managed container and probes upstreams without
requiring an optional wg show process inside it. Keep development tools and AI
assistants in the normal city devcontainer, not attached to a VPN container;
the relay containers have small process budgets for networking services.
| City | Private OpenVPN endpoint | OpenVPN subnet |
|---|---|---|
| Richmond | 10.120.206.52:51830/udp |
10.253.240.0/24 |
| Rome | 10.120.206.10:51830/udp |
10.253.241.0/24 |
| Kyiv | 10.120.206.42:51830/udp |
10.253.242.0/24 |
| Istanbul | 10.120.206.24:51830/udp |
10.253.243.0/24 |
| Macau | 10.120.206.58:51830/udp |
10.253.244.0/24 |
| Shimla | 10.120.206.38:51830/udp |
10.254.248.0/24 |
| Seattle | 10.120.206.47:51830/udp |
10.253.246.0/24 |
| Athens | 10.120.206.59:51830/udp |
10.253.247.0/24 |
DNS is .1 in each subnet. To enroll an actual laptop after validation:
sudo trueserve-openvpn add-peer alex-windows
Deliver /var/lib/trueserve-openvpn/exports/alex-windows.zip privately.
No developer certificate is shared across cities or laptops. The application
checkout also receives the scripts and guides so city assistants can find them,
but privileged manager commands must run on the host.
Manual single-city setup¶
Run on the EC2 host, with sudo, not inside its devcontainer. For a remote
city, use SSM Run Command to execute these host operations. Copy only
openvpn.py, relay.py, OpenVPN.Dockerfile, openvpn-gateway.sh, .dockerignore,
probe.py, environments.json and OPENVPN-WINDOWS.md to a versioned directory
under /opt/trueserve-openvpn/releases/. Keep that release separate from the
application checkout and existing WireGuard release. Do not move its symlink.
Before installation, record application HEAD, running container IDs and startup
times. Check host routes for subnet overlap, UDP listeners for a free port,
/dev/net/tun, and the city security group's existing rules. The private UDP
endpoint must be reachable through the core VPN. Do not expose a new public
listener or broaden security groups automatically.
Example after placing the release at /opt/trueserve-openvpn/current:
manager=/opt/trueserve-openvpn/current/scripts/studio-access/openvpn.py
sudo python3 "$manager" build
sudo python3 "$manager" init --city shimla --network 10.254.248.0/24 --port 51830
sudo python3 "$manager" add-peer jyoti-windows
sudo python3 "$manager" start
sudo python3 "$manager" status
Initialization uses the already-reviewed /var/lib/trueserve-access/state.json
endpoint and DNS snapshot. It validates that the new subnet differs from
WireGuard and host routes, and rejects recursive/metadata/broad routes. Resolve
missing/stale endpoint records in the existing inventory before initialization.
Do not delete state and rerun init to update DNS: that would replace the CA.
This pilot does not automatically track DNS changes. When addresses change,
first update and verify the existing WireGuard inventory using its operator
workflow. Then schedule a brief OpenVPN interruption and run:
sudo python3 "$manager" stop
sudo python3 "$manager" refresh
sudo python3 "$manager" start
sudo python3 "$manager" export jyoti-windows
refresh preserves the CA and peers, imports the reviewed records, and refuses
to run while an OpenVPN container exists. Verify the relay again and distribute
replacement bundles to every enrolled laptop so client routes match the server.
Each city needs a distinct nonoverlapping OpenVPN subnet and a checked port. The example above is for Shimla, not a command to run unchanged on every city.
Stage new addresses before replacing client profiles¶
If the published application addresses change while the old ones still work, verify the new addresses with normal TLS/SNI and stage additional routes before changing the shared DNS snapshot. For the September 21 QA address change:
sudo trueserve-openvpn stage-routes --environment qa 52.40.176.56/32 54.185.47.193/32
sudo trueserve-openvpn export jyoti-windows
This adds only the reviewed forwarding destinations, persists them in the
server configuration and includes them in newly exported qa and both
profiles. It preserves DNS answers, existing routes, certificates and active
sessions, and does not restart the container. The dev profile does not gain
these QA-only routes. This is a controlled transition, not automatic DNS
discovery.
Deliver the refreshed private bundle. The Windows user keeps the core VPN up,
disconnects only the TrueServe OpenVPN profile, replaces the imported profile
with its refreshed .ovpn file, then reconnects. After all affected clients
have the new routes, the operator can move the shared DNS snapshot using the
reviewed refresh workflow above. While the old addresses remain usable, avoid
switching DNS first: an old client could receive an address it does not route
into its tunnel.
On September 21, Shimla's old QA application addresses stopped working: one
timed out and the other failed hostname certificate validation. The OpenVPN
relay now serves 52.40.176.56 and 54.185.47.193 for the QA application names.
The routes were staged before this controlled DNS update; existing client
identities, container process and earlier destinations were preserved.
Jyoti must import the refreshed private both profile and reconnect. Reconnecting
her previous profile alone does not install the new routes. Keep the core VPN
connected and leave the previous TrueServe profile disconnected.
A separate temporary OpenVPN client verified both new addresses with valid TLS/SNI, QA Studio HTTPS, Janus WebSocket upgrade, TURN UDP binding/TLS, and QA management HTTPS. Dev Studio, Janus and TURN checks also passed. Dev management initially timed out because its private addresses had changed too. Shimla's automatic refresh pilot subsequently updated those records, and a fresh OpenVPN client passed all Dev/QA network checks, including ArgoCD, Dex and Grafana. The OpenVPN refresh timer is active on Shimla; laptop automatic startup still requires the one-time helper installation and native acceptance. These checks do not replace Jyoti's Windows browser and two-way audio test.
Credentials, delivery and revocation¶
The manager creates a private local CA, a server certificate and a separate certificate plus TLS control-channel key for each laptop. Client certificates expire after 30 days, server certificates after 365 days, and the CA after 730 days. Track those dates; certificate renewal is not automatic in this pilot. For a replacement client, revoke the old device and issue a new device name. Back up state through an approved encrypted host backup, not Git.
add-peer exports a private ZIP at:
/var/lib/trueserve-openvpn/exports/jyoti-windows.zip
Deliver it through an authenticated private channel. Do not print its contents
in SSM output, commit it, or publish it on a city web server. It includes
both, dev, qa profiles and START-HERE.md. The both profile needs only
one connection to support both environments. The environment variants limit
routes, not authorization: normal application access controls still apply.
sudo python3 "$manager" export jyoti-windows
sudo python3 "$manager" revoke jyoti-windows
Revocation records the certificate serial, removes its allowed client entry, disconnects its active session and removes exported private keys. Do not reuse a revoked device name. Other peers keep working.
To stop/remove only the OpenVPN container:
sudo python3 "$manager" stop
State and the existing WireGuard relay remain. start creates the container
again. It deliberately refuses to replace an existing named container.
Validation¶
Shimla was tested on 2026-09-18 using a separate authenticated OpenVPN client
through 10.120.206.38:51830. Full Dev and QA checks passed: Studio HTTP 200,
Janus WebSocket 101, UDP STUN, TLS TURN, ArgoCD, Dex, Grafana and shared SSO.
The client's default route was preserved; scoped DNS returned the expected
addresses and no AAAA answers; a non-inventory destination was blocked.
The relay recovered after a container restart and QA passed again. Revoking
the temporary identity disconnected its live session and the server rejected
its cached certificate on a fresh connection attempt.
Windows driver/NRPT and a browser call remain the laptop acceptance checks.
The user subsequently confirmed that Jyoti's Windows OpenVPN setup on Shimla
worked. That confirms her laptop path; it does not prove browser media on every
other city or device.
Issue a temporary device and connect an isolated OpenVPN client through the
published private endpoint. Check its assigned address, scoped DNS, preserved
default route, rejection of destinations outside the inventory, and execute
probe.py --environments qa and probe.py --environments both --transport-only.
The probe checks HTTPS, WebSockets, UDP STUN, TLS TURN and management endpoints.
Report application health separately from network transport. Revoke the test
device and verify it cannot reconnect. Verify original containers and application
HEAD still match the preflight snapshot.
Linux probe containers require their DNS explicitly pointed at the tunnel resolver: the Windows profile's native NRPT handling is not a Linux DNS script. A Linux test proves relay transport, not Windows driver installation or NRPT. Only the Windows laptop test can complete those checks.