Skip to content

Commit a88bf5d

Browse files
authored
External WAN speed test: proper data model, Settings-driven deploy (#536) (#537)
* External WAN speed test: proper data model, Settings-driven deploy, interactive script - New ExternalSpeedTestServers table with auto-generated ServerId slug - Migration from key-value SystemSettings to proper table - Settings page shows deploy command with pre-filled URL and ServerId - Deploy script: non-interactive (from Settings), interactive (guided), --update - ServerId ties deployed container to server record and speed test results - Updated DEPLOYMENT.md with Settings-first workflow * Fix deploy command: omit port (default 3005), add HTTPS reverse proxy guidance * Deploy command: include port only for HTTP with non-default port * Remove VPS-specific wording from deploy command instructions * Fix deploy script: download all CSS files and PWA icons * Deploy script: download all OpenSpeedTest assets (fonts, icons, CSS) Consolidated file downloads into shared function used by both fresh install and --update. Added missing fonts, app.css, darkmode.css, PWA icons, and SVG images that index.html references. * Deploy script: use GitHub tarball instead of individual file downloads Downloads the repo tarball and extracts only src/OpenSpeedTest/ and docker/openspeedtest/. No file list to maintain - any new assets are automatically included. * Fix tar extract: add --wildcards flag for GNU tar on Linux * Fix tar compatibility: detect GNU vs BSD tar for --wildcards flag * Update HTTPS messaging: strongly recommended, not required (Firefox works without) * Accurate HTTPS messaging: Chrome/Edge enforce PNA, Firefox/Safari don't; link to NetworkOptimizer-Proxy * Fix script: Optimizer URL prompt doesn't need HTTPS warning * Fix deploy script: Safari doesn't enforce PNA, only Chrome/Edge do * Review cleanup: remove dead BASE_URL variable, fix "most browsers" to "Chrome and Edge" * Deploy command uses saved settings, not live form values * Increase card spacing on Settings and Audit pages to match Dashboard * Card spacing: 1.5rem desktop, 1rem mobile for Settings and Audit
1 parent e390b89 commit a88bf5d

12 files changed

Lines changed: 2426 additions & 142 deletions

docker/DEPLOYMENT.md

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,11 +1083,20 @@ Deploy an OpenSpeedTest instance to a remote server (VPS, cloud VM, etc.) to let
10831083
**Requirements:**
10841084
- A remote server with Docker (any cloud VPS works)
10851085
- Port 3005 (or your chosen port) open on the remote server
1086-
- **HTTPS on the external server** (required - see note below)
1086+
- **HTTPS on the external server** (strongly recommended - see note below)
10871087

1088-
**Why HTTPS?** Modern browsers enforce [Private Network Access](https://developer.chrome.com/blog/private-network-access-update) rules. The speed test page is served from a public IP, but the browser (on your LAN) posts results back to Network Optimizer (a private IP). Browsers block this unless the page origin is HTTPS (a secure context).
1088+
**Why HTTPS?** Chrome and Edge enforce [Private Network Access](https://developer.chrome.com/blog/private-network-access-update) rules. The speed test page is served from a public IP, and the browser posts results back to Network Optimizer on your LAN (a private IP). These browsers block this unless the page origin is HTTPS (a secure context). Firefox and Safari do not currently enforce this restriction, but HTTPS is still strongly recommended.
10891089

1090-
**Quick deploy** (run on the remote server - the script will walk you through the setup):
1090+
**Setup:**
1091+
1092+
1. In Network Optimizer, go to **Settings → External Speed Test Server**
1093+
2. Enter the server name, hostname/IP, port, and scheme (HTTPS)
1094+
3. Save - a **deploy command** will appear with everything pre-filled
1095+
4. SSH to your remote server and run the deploy command
1096+
1097+
The deploy command handles downloading files, building the container, and starting the server. The Server ID is automatically generated from the name you entered and links results back to this server.
1098+
1099+
**Interactive deploy** (if you haven't configured Settings yet, the script will walk you through it):
10911100
```bash
10921101
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/deploy-external-speedtest.sh | bash
10931102
```
@@ -1097,42 +1106,9 @@ curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main
10971106
curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/deploy-external-speedtest.sh | bash -s -- --update
10981107
```
10991108

1100-
Or manually:
1101-
```bash
1102-
git clone --depth 1 https://github.com/Ozark-Connect/NetworkOptimizer.git /opt/netopt-speed-test
1103-
cd /opt/netopt-speed-test
1104-
```
1105-
1106-
Create `docker-compose.yml`:
1107-
```yaml
1108-
services:
1109-
speedtest:
1110-
build:
1111-
context: .
1112-
dockerfile: docker/openspeedtest/Dockerfile
1113-
container_name: netopt-wan-speedtest
1114-
restart: unless-stopped
1115-
ports:
1116-
- "3005:3000"
1117-
environment:
1118-
- REVERSE_PROXIED_HOST_NAME=optimizer.example.com
1119-
- EXTERNAL_SERVER_ID=my-server-name
1120-
```
1121-
1122-
```bash
1123-
docker compose up -d
1124-
```
1125-
1126-
**Then in Network Optimizer Settings:**
1127-
1. Go to Settings → External Speed Test Server
1128-
2. Enter the remote server's hostname, port, and scheme (HTTPS)
1129-
3. Give it a friendly name (e.g., "Chicago VPS")
1130-
4. Save - this enables CORS for the remote server and populates the Client WAN Speed Test page
1131-
1132-
**Setting up HTTPS:** If you already have a reverse proxy for Network Optimizer and its LAN speed test server (e.g., Traefik or Caddy), you can add a route for the external speed test hostname pointing to your VPS - no need to install a separate proxy on the remote server. The reverse proxy must force HTTP/1.1 for accurate speed test results (HTTP/2 multiplexing interferes with throughput measurement).
1109+
**Setting up HTTPS:** If you use [NetworkOptimizer-Proxy](https://github.com/Ozark-Connect/NetworkOptimizer-Proxy) (Traefik), the WAN speed test route is already included in `config.example.yml` - just uncomment the `speedtest-wan` router and service, update the hostname and VPS address, and you're done. The config enforces HTTP/1.1 and strips compression headers automatically.
11331110

1134-
- **Traefik** - supports per-route HTTP/1.1 TLS options. Add a route like `speedtest-wan.example.com` pointing to your VPS on port 3005 with the same HTTP/1.1 config used for your LAN speed test.
1135-
- **Caddy** - automatic Let's Encrypt, simple configuration. Note: Caddy negotiates HTTP/2 by default at the TLS level. For the most accurate speed test results, configure it to use HTTP/1.1 for the speed test hostname.
1111+
If you use a different reverse proxy, add a route for the external speed test hostname pointing to your remote server on port 3005. The reverse proxy must force HTTP/1.1 for accurate speed test results (HTTP/2 multiplexing interferes with throughput measurement).
11361112

11371113
Then update the external server settings in Network Optimizer to use `https` scheme and port `443`.
11381114

scripts/deploy-external-speedtest.sh

Lines changed: 84 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# Fresh install (interactive):
77
# ./deploy-external-speedtest.sh
88
#
9-
# Fresh install (non-interactive):
10-
# ./deploy-external-speedtest.sh <optimizer-url> <server-name> [port]
9+
# Fresh install (from Settings-generated command):
10+
# ./deploy-external-speedtest.sh <optimizer-url> <server-id> [port]
1111
#
1212
# Update existing installation:
1313
# ./deploy-external-speedtest.sh --update
@@ -20,6 +20,44 @@ INSTALL_DIR="/opt/netopt-speed-test"
2020
BRANCH="${BRANCH:-main}"
2121
GITHUB_REPO="Ozark-Connect/NetworkOptimizer"
2222

23+
# --- Slug generation (must match C# ExternalSpeedTestServer.GenerateServerId) ---
24+
generate_server_id() {
25+
echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//'
26+
}
27+
28+
# --- Download all required files from GitHub via tarball ---
29+
# Uses the GitHub API to download a tarball of the repo, then extracts only
30+
# the files needed for the speed test container. No file list to maintain.
31+
download_files() {
32+
local TARBALL_URL="https://github.com/$GITHUB_REPO/archive/refs/heads/$BRANCH.tar.gz"
33+
local TEMP_TAR=$(mktemp)
34+
local TEMP_DIR=$(mktemp -d)
35+
36+
curl -sL "$TARBALL_URL" -o "$TEMP_TAR"
37+
38+
# Extract only the directories we need
39+
# Tarball root is NetworkOptimizer-<branch>/
40+
local STRIP=1 # strip the root directory
41+
# Extract only the directories we need
42+
# --wildcards is needed on GNU tar (Linux), but errors on BSD tar (macOS)
43+
if tar --version 2>&1 | grep -q GNU; then
44+
tar -xzf "$TEMP_TAR" -C "$TEMP_DIR" --strip-components=$STRIP --wildcards \
45+
"*/src/OpenSpeedTest/" \
46+
"*/docker/openspeedtest/"
47+
else
48+
tar -xzf "$TEMP_TAR" -C "$TEMP_DIR" --strip-components=$STRIP \
49+
"*/src/OpenSpeedTest/" \
50+
"*/docker/openspeedtest/"
51+
fi
52+
53+
# Copy into install directory
54+
mkdir -p docker/openspeedtest src/OpenSpeedTest
55+
cp -r "$TEMP_DIR/docker/openspeedtest/"* docker/openspeedtest/
56+
cp -r "$TEMP_DIR/src/OpenSpeedTest/"* src/OpenSpeedTest/
57+
58+
rm -rf "$TEMP_TAR" "$TEMP_DIR"
59+
}
60+
2361
# --- Update mode ---
2462
if [ "${1}" = "--update" ]; then
2563
if [ ! -f "$INSTALL_DIR/docker-compose.yml" ]; then
@@ -29,30 +67,12 @@ if [ "${1}" = "--update" ]; then
2967
fi
3068

3169
cd "$INSTALL_DIR"
32-
BASE_URL="https://raw.githubusercontent.com/$GITHUB_REPO/$BRANCH"
3370

3471
echo "=== Updating External Speed Test Server ==="
3572
echo ""
3673
echo "Downloading latest files..."
3774

38-
# Re-download all source files (same list as fresh install)
39-
curl -sL "$BASE_URL/docker/openspeedtest/Dockerfile" -o docker/openspeedtest/Dockerfile
40-
curl -sL "$BASE_URL/docker/openspeedtest/nginx.conf" -o docker/openspeedtest/nginx.conf
41-
curl -sL "$BASE_URL/docker/openspeedtest/entrypoint.sh" -o docker/openspeedtest/entrypoint.sh
42-
43-
curl -sL "$BASE_URL/src/OpenSpeedTest/index.html" -o src/OpenSpeedTest/index.html
44-
45-
for f in config.js app-2.5.4.js app-2.5.4.min.js geolocation.js darkmode.js; do
46-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/js/$f" -o "src/OpenSpeedTest/assets/js/$f"
47-
done
48-
49-
for f in ozark-overrides.css; do
50-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/css/$f" -o "src/OpenSpeedTest/assets/css/$f"
51-
done
52-
53-
for f in apple-touch-icon.png favicon.ico favicon.png logo-dark.svg logo.svg; do
54-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/images/$f" -o "src/OpenSpeedTest/assets/images/$f" 2>/dev/null || true
55-
done
75+
download_files
5676

5777
echo "Rebuilding container..."
5878
docker compose build
@@ -65,10 +85,21 @@ fi
6585

6686
# --- Fresh install ---
6787

68-
# If args provided, use them (non-interactive / backward compat)
88+
# Check Docker first
89+
if ! command -v docker &> /dev/null; then
90+
echo "Error: Docker is not installed"
91+
exit 1
92+
fi
93+
94+
if ! docker compose version &> /dev/null; then
95+
echo "Error: Docker Compose is not installed"
96+
exit 1
97+
fi
98+
99+
# If args provided, use them (non-interactive / Settings-generated command)
69100
if [ -n "$1" ]; then
70101
OPTIMIZER_URL="$1"
71-
SERVER_NAME="${2:?Usage: $0 <optimizer-url> <server-name> [port]}"
102+
SERVER_ID="${2:-external}"
72103
PORT="${3:-3005}"
73104
else
74105
# Interactive mode
@@ -81,10 +112,8 @@ else
81112

82113
# Optimizer URL
83114
echo "What is the URL of your Network Optimizer instance?"
84-
echo " This is the HTTPS address your browser uses to access Network Optimizer."
85-
echo " It must be HTTPS - browsers block speed test results from being posted"
86-
echo " back to a private network address unless the page is served over HTTPS."
87-
echo " Examples: https://optimizer.example.com, https://192.168.1.100:8042"
115+
echo " This is the address your browser uses to access Network Optimizer."
116+
echo " Examples: https://optimizer.example.com, http://192.168.1.100:8042"
88117
echo ""
89118
read -rp "Optimizer URL: " OPTIMIZER_URL < /dev/tty
90119
if [ -z "$OPTIMIZER_URL" ]; then
@@ -94,17 +123,29 @@ else
94123

95124
echo ""
96125

97-
# Server name
98-
echo "Give this speed test server a name to identify it in Network Optimizer."
99-
echo " Use something short that describes where this server is located."
100-
echo " Examples: vps-chicago, aws-east, hetzner-eu"
126+
# Server ID
127+
echo "If you've already configured this server in Network Optimizer Settings,"
128+
echo "enter the Server ID shown there. Otherwise, enter a friendly name and"
129+
echo "we'll generate the ID for you."
101130
echo ""
102-
read -rp "Server name: " SERVER_NAME < /dev/tty
103-
if [ -z "$SERVER_NAME" ]; then
104-
echo "Error: Server name is required."
131+
read -rp "Server ID or name (e.g. vps-chicago or VPS Chicago): " SERVER_INPUT < /dev/tty
132+
if [ -z "$SERVER_INPUT" ]; then
133+
echo "Error: Server ID or name is required."
105134
exit 1
106135
fi
107136

137+
# Check if it looks like a slug already (lowercase, hyphens, numbers only) or a display name
138+
if echo "$SERVER_INPUT" | grep -qE '^[a-z0-9][a-z0-9-]*[a-z0-9]$'; then
139+
SERVER_ID="$SERVER_INPUT"
140+
else
141+
SERVER_ID=$(generate_server_id "$SERVER_INPUT")
142+
echo ""
143+
echo " Generated Server ID: $SERVER_ID"
144+
echo ""
145+
echo " Important: When you configure this server in Network Optimizer Settings,"
146+
echo " use the name \"$SERVER_INPUT\" so the Server IDs match."
147+
fi
148+
108149
echo ""
109150

110151
# Port
@@ -114,20 +155,9 @@ else
114155
echo ""
115156
fi
116157

117-
# Check Docker
118-
if ! command -v docker &> /dev/null; then
119-
echo "Error: Docker is not installed"
120-
exit 1
121-
fi
122-
123-
if ! docker compose version &> /dev/null; then
124-
echo "Error: Docker Compose is not installed"
125-
exit 1
126-
fi
127-
128158
echo "=== Network Optimizer - External Speed Test Server ==="
129159
echo "Optimizer URL: $OPTIMIZER_URL"
130-
echo "Server Name: $SERVER_NAME"
160+
echo "Server ID: $SERVER_ID"
131161
echo "Port: $PORT"
132162
echo "Install Dir: $INSTALL_DIR"
133163
echo ""
@@ -136,35 +166,8 @@ echo ""
136166
mkdir -p "$INSTALL_DIR"
137167
cd "$INSTALL_DIR"
138168

139-
# Download required files from GitHub
140-
BASE_URL="https://raw.githubusercontent.com/$GITHUB_REPO/$BRANCH"
141-
142169
echo "Downloading speed test files..."
143-
mkdir -p src/OpenSpeedTest/assets/js src/OpenSpeedTest/assets/css src/OpenSpeedTest/assets/images
144-
mkdir -p docker/openspeedtest
145-
146-
# Core files
147-
curl -sL "$BASE_URL/docker/openspeedtest/Dockerfile" -o docker/openspeedtest/Dockerfile
148-
curl -sL "$BASE_URL/docker/openspeedtest/nginx.conf" -o docker/openspeedtest/nginx.conf
149-
curl -sL "$BASE_URL/docker/openspeedtest/entrypoint.sh" -o docker/openspeedtest/entrypoint.sh
150-
151-
# OpenSpeedTest UI files (download all from the assets)
152-
for f in index.html; do
153-
curl -sL "$BASE_URL/src/OpenSpeedTest/$f" -o "src/OpenSpeedTest/$f"
154-
done
155-
156-
for f in config.js app-2.5.4.js app-2.5.4.min.js geolocation.js darkmode.js; do
157-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/js/$f" -o "src/OpenSpeedTest/assets/js/$f"
158-
done
159-
160-
for f in ozark-overrides.css; do
161-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/css/$f" -o "src/OpenSpeedTest/assets/css/$f"
162-
done
163-
164-
# Download all images (favicon, logo, etc.)
165-
for f in apple-touch-icon.png favicon.ico favicon.png logo-dark.svg logo.svg; do
166-
curl -sL "$BASE_URL/src/OpenSpeedTest/assets/images/$f" -o "src/OpenSpeedTest/assets/images/$f" 2>/dev/null || true
167-
done
170+
download_files
168171

169172
# Create .dockerignore
170173
cat > .dockerignore << 'EOF'
@@ -190,7 +193,7 @@ services:
190193
environment:
191194
- TZ=\${TZ:-UTC}
192195
- REVERSE_PROXIED_HOST_NAME=$(echo "$OPTIMIZER_URL" | sed 's|https\?://||' | sed 's|/.*||')
193-
- EXTERNAL_SERVER_ID=${SERVER_NAME}
196+
- EXTERNAL_SERVER_ID=${SERVER_ID}
194197
COMPOSE_EOF
195198

196199
echo "Building and starting speed test server..."
@@ -199,20 +202,19 @@ docker compose up -d
199202

200203
echo ""
201204
echo "=== Deployment Complete ==="
202-
echo "Speed test URL: http://$(hostname -I | awk '{print $1}'):$PORT"
203-
echo ""
204-
echo "IMPORTANT: HTTPS is required for results to post back to Network Optimizer."
205-
echo "Browsers block requests from public HTTP pages to private network addresses."
206-
echo "The reverse proxy must also force HTTP/1.1 (HTTP/2 interferes with speed test accuracy)."
205+
echo "Speed test URL: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):$PORT"
207206
echo ""
208-
echo "Recommended: Traefik or Caddy with HTTP/1.1 and TLS."
207+
echo "IMPORTANT: HTTPS is strongly recommended. Chrome and Edge block speed test"
208+
echo "results from posting back when the page is served over HTTP (Private Network"
209+
echo "Access). Firefox and Safari don't currently enforce this, but HTTPS is still"
210+
echo "recommended. Set up a reverse proxy with TLS and HTTP/1.1."
209211
echo "See DEPLOYMENT.md for setup instructions."
210212
echo ""
211213
echo "Then configure Network Optimizer Settings -> External Speed Test Server:"
214+
echo " - Name: (use the same name you entered here so the Server IDs match)"
212215
echo " - Host: speedtest.yourdomain.com"
213216
echo " - Port: 443"
214217
echo " - Scheme: HTTPS"
215-
echo " - Name: $SERVER_NAME"
216218
echo ""
217219
echo "To update in the future, run:"
218220
echo " curl -fsSL https://raw.githubusercontent.com/$GITHUB_REPO/main/scripts/deploy-external-speedtest.sh | bash -s -- --update"

0 commit comments

Comments
 (0)