Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
39c45f6
fix(lightning overlay): correct timestamp scaling and more
DigitalFeonix Mar 3, 2026
26e4d54
Ad a hot key (/) to toggle the DE and DX labels
alanhargreaves Mar 3, 2026
f88046e
DE and DX should be in caps
alanhargreaves Mar 3, 2026
16b4095
docs: clarify contributor setup and staging PR workflow, Fixes #663
echo-gravitas Mar 3, 2026
bc398e8
Quick fix to align DX Loc left in DX Target Panel
echo-gravitas Mar 3, 2026
1d00b83
WIP [FEATURE] Adding DXCC Prefix list for selecting DX on Map
echo-gravitas Mar 3, 2026
4e89a1b
Add TCI/SDR plugin to rig-bridge + bump to v1.2.0
ceotjoe Mar 3, 2026
d6e6c2a
feat: add inline text size controls to DX News ticker
ceotjoe Mar 3, 2026
338be70
Tidy up how we display the reference and name. Show spot.ref in the t…
alanhargreaves Mar 4, 2026
dc9af11
Merge pull request #658 from alanhargreaves/toggledxde
accius Mar 4, 2026
dab7dad
Merge pull request #664 from echo-gravitas/echo-gravitas/issue-663
accius Mar 4, 2026
2e08407
Merge pull request #668 from ceotjoe/feature/tci-rig-bridge-plugin
accius Mar 4, 2026
68952a0
Merge pull request #669 from ceotjoe/feature/dxnews-text-resize
accius Mar 4, 2026
279a30b
Merge pull request #672 from alanhargreaves/activate-ref-display
accius Mar 4, 2026
67c987f
Merge branch 'Staging' into echo-gravitas/issue-206
echo-gravitas Mar 4, 2026
6219bf2
Add DXCC target selector and polish DX target UX
echo-gravitas Mar 4, 2026
d66ec4f
Merge pull request #667 from echo-gravitas/echo-gravitas/issue-206
accius Mar 4, 2026
dbcbc13
[FEATURE - Map Widgets Alignment] First pass. Added grid snap, removed
denete Mar 2, 2026
6f7e6f9
[FEATURE - Map Widgets Alignment] refactored grayline, muf map, and
denete Mar 3, 2026
6619ef7
[FEATURE - Map Widgets Alignment] updated remaining layers
denete Mar 3, 2026
92b168b
[FEATURE - Map Widgets Alignment] updated border-radius
denete Mar 3, 2026
12bf987
[FEATURE - Map Widgets Alignment] Updates to address issues from PR #…
denete Mar 4, 2026
c4650b8
Merge pull request #676 from accius/main
accius Mar 5, 2026
05969d3
Merge pull request #655 from denete/feature-634
accius Mar 5, 2026
53d9893
Merge pull request #657 from DigitalFeonix/fix/lightning-date
accius Mar 5, 2026
c7bc0d6
Improve TCI plugin diagnostics and Thetis compatibility
ceotjoe Mar 5, 2026
30d825a
Any time we update the local "grid", we need to update config.locator…
alanhargreaves Mar 6, 2026
6327ef5
fix(setup-pi): skip electron postinstall scripts on ARM to fix 7z.exe…
ceotjoe Mar 6, 2026
14d9290
Merge pull request #681 from alanhargreaves/locator-fix
accius Mar 7, 2026
acc4bc7
Merge pull request #684 from ceotjoe/feature/tci-rig-bridge-plugin
accius Mar 7, 2026
cda8350
fix bug #683
accius Mar 7, 2026
e94c4c0
fix bug #677
accius Mar 7, 2026
757ee89
actual #677 fix.
accius Mar 7, 2026
efc4844
Add multicast support fro wsjt-x
alanhargreaves Mar 7, 2026
abd97f4
Merge branch 'Staging' into multicast-wsjtx
alanhargreaves Mar 7, 2026
6569182
Updated to include references to WSJTX_MULTICAST_ADDRESS
alanhargreaves Mar 7, 2026
648fd4c
Add optional fixed DX cluster spotter coordinates
Mar 7, 2026
8cc2405
fix(setup-pi): install Node.js from nodejs.org on 32-bit ARM (armhf)
ceotjoe Mar 7, 2026
89961fd
fix(setup-pi): download Node.js armv7l to temp file with retry on armhf
ceotjoe Mar 7, 2026
6e91702
Update rig-bridge README with mock and WSJT-X relay plugin docs
ceotjoe Mar 7, 2026
bdf0df5
Clarify when we need to define WSJTX_MULTICAST_ADDRESS.
alanhargreaves Mar 8, 2026
cba12e0
formatting
alanhargreaves Mar 8, 2026
334f71b
Updated so distances are shown in teh globally selected units rather …
alanhargreaves Mar 8, 2026
66f1ac1
Vulnerability Updates
accius Mar 9, 2026
5cfeef3
Update server.js
accius Mar 9, 2026
2dec195
Update Dockerfile
accius Mar 9, 2026
1171446
Update server.js
accius Mar 9, 2026
072d839
Update server.js
accius Mar 9, 2026
fcfb3bd
Update useN3FJPLoggedQSOs.js
accius Mar 9, 2026
3d83728
Update aprs_newsfeed.user.js
accius Mar 9, 2026
4e660a7
Add UDP multicast support to WSJT-X relay plugin
ceotjoe Mar 9, 2026
d1660d5
Merge pull request #686 from alanhargreaves/multicast-wsjtx
accius Mar 9, 2026
320df84
Merge pull request #690 from ceotjoe/fix/setup-pi-electron-winstaller…
accius Mar 9, 2026
acc4074
Merge pull request #700 from ceotjoe/feature/tci-rig-bridge-plugin
accius Mar 9, 2026
6dd1cdc
security fixes
accius Mar 9, 2026
3fdc224
[Feature - Relocate Layout Lock Button] Moved lock button to render in
denete Mar 7, 2026
337aab8
[Feature - Relocate Layout Lock Button] Fixed a couple of CSS errors,
denete Mar 7, 2026
0995b38
security Vulnerability updates
accius Mar 9, 2026
579304d
[Feature - Relocate Layout Lock Button] addressed PR comments
denete Mar 9, 2026
06f8edc
[Feature - Relocate Layout Lock Button] updated to add locked / unlocked
denete Mar 9, 2026
d4c148e
Clarify DX cluster fixed spotter env example
Mar 9, 2026
a3fe54d
solar images fallback 3
accius Mar 9, 2026
4497f4c
Merge pull request #687 from denete/feature-662
accius Mar 9, 2026
2eccf55
Merge pull request #688 from on6zq/feature/dx-cluster-fixed-spotter-env
accius Mar 9, 2026
b3023d3
Merge pull request #697 from alanhargreaves/lightning-units-fix
accius Mar 10, 2026
91d2ba7
vulnerability fixes
accius Mar 10, 2026
bf67beb
Merge commit from fork
accius Mar 10, 2026
52b0281
whats new
accius Mar 10, 2026
ecb7d81
Merge branch 'Staging' of https://github.com/accius/openhamclock into…
accius Mar 10, 2026
9b72793
Update server.js
accius Mar 10, 2026
8e688be
Update server.js
accius Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ HOST=localhost
# API_WRITE_KEY=your-secret-key-here

# CORS allowed origins (comma-separated)
# If not set, defaults to reflecting the request origin (backward-compatible).
# Set this for cloud deployments to restrict which websites can call your API.
# CORS_ORIGINS=https://yourdomain.com,http://localhost:3000
# By default, only localhost and openhamclock.com/app origins are allowed.
# Add your custom domain if you host OHC behind your own URL.
# CORS_ORIGINS=https://yourdomain.com

# Trust proxy headers (X-Forwarded-For) for rate limiting IP detection.
# Auto-detected: enabled on Railway, disabled on Pi/local installs.
# Set to 'true' if behind nginx/Caddy/etc., 'false' to force off.
# TRUST_PROXY=true

# ===========================================
# AUTO UPDATE (GIT)
Expand Down Expand Up @@ -229,18 +234,19 @@ VITE_AMBIENT_API_KEY=your_api_key_here
# ===========================================
# ROTATOR CONTROL
# ===========================================

ROTATOR_PROVIDER=pstrotator_udp
PSTROTATOR_HOST=192.168.1.43
PSTROTATOR_UDP_PORT=12000
ROTATOR_STALE_MS=5000
# Uncomment and configure ONLY if you have a PSTRotator-compatible rotator.
# Leaving these active on a fresh install sends UDP traffic to 192.168.1.43.
# ROTATOR_PROVIDER=pstrotator_udp
# PSTROTATOR_HOST=192.168.1.43
# PSTROTATOR_UDP_PORT=12000
# ROTATOR_STALE_MS=5000

# Your app will use the proxy path:
VITE_PSTROTATOR_BASE_URL=/pstrotator
# VITE_PSTROTATOR_BASE_URL=/pstrotator

# Optional: HTTP endpoint for PstRotatorAz web interface (for proxy)
# Set this to the machine running PstRotatorAz (default shown below)
VITE_PSTROTATOR_TARGET=http://192.168.1.43:50004
# VITE_PSTROTATOR_TARGET=http://192.168.1.43:50004

# ===========================================
# N3FJP QSO RETENTION
Expand Down
13 changes: 8 additions & 5 deletions AddOns/APRS-Newsfeed/aprs_newsfeed.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
let apiKey = localStorage.getItem(STORAGE_API_KEY) || '';
let lastUpdateTs = parseInt(localStorage.getItem('ohc_aprs_last_update')) || 0;

// Escape HTML to prevent XSS when interpolating into innerHTML
const esc = (s) => String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');

function getCallsign() {
try {
const config = JSON.parse(localStorage.getItem('openhamclock_config'));
Expand Down Expand Up @@ -386,7 +389,7 @@
<div style="padding: 20px; text-align: center; color: var(--text-muted);">${t('setup_required')}</div>
</div>
<div id="aprs-news-settings">
<input type="password" id="aprs-apikey-input" class="aprs-input" placeholder="${t('placeholder_apikey')}" value="${apiKey}">
<input type="password" id="aprs-apikey-input" class="aprs-input" placeholder="${t('placeholder_apikey')}" value="${esc(apiKey)}">
<div style="display:flex; justify-content: space-between; align-items: center;">
<span id="aprs-status" style="color: var(--text-muted); font-size: 9px;"></span>
<button id="aprs-save-btn" style="padding: 4px 8px; cursor: pointer; background: var(--bg-tertiary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 4px;">${t('save')}</button>
Expand Down Expand Up @@ -552,7 +555,7 @@
}
} else {
document.getElementById('aprs-news-content').innerHTML =
`<div style="padding: 20px; text-align: center; color: var(--accent-red);">${t('error_api')}: ${data.description || ''}</div>`;
`<div style="padding: 20px; text-align: center; color: var(--accent-red);">${t('error_api')}: ${esc(data.description || '')}</div>`;
status.innerText = 'Error';
}
}
Expand Down Expand Up @@ -580,12 +583,12 @@
return `
<div class="aprs-msg-entry">
<div class="aprs-msg-meta">
<span><span class="aprs-msg-call">${entry.srccall}</span>${tag}</span>
<span><span class="aprs-msg-call">${esc(entry.srccall)}</span>${tag}</span>
<span>${timeStr}</span>
</div>
<div class="aprs-msg-text">${entry.message}</div>
<div class="aprs-msg-text">${esc(entry.message)}</div>
<div style="font-size: 9px; color: var(--text-muted); text-align: right; margin-top: 2px;">
${t('to')}: <span style="color: ${isToSSID ? 'var(--accent-amber)' : 'var(--text-secondary)'}">${entry.dst}</span>
${t('to')}: <span style="color: ${isToSSID ? 'var(--accent-amber)' : 'var(--text-secondary)'}">${esc(entry.dst)}</span>
</div>
</div>
`;
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ All notable changes to OpenHamClock will be documented in this file.

> **📅 Schedule Change:** Starting with v15.5.10, OpenHamClock moves to a weekly release cycle. Updates will ship on **Tuesday nights (EST)** — one release per week for better testing and stability.

## [15.6.5] - 2026-03-09

### Security

- **CORS lockdown**: Replaced wildcard `origin: true` with explicit origin allowlist (localhost, openhamclock.com/app). Prevents malicious websites from accessing the API via the user's browser. Custom origins configurable via `CORS_ORIGINS` env var.
- **SSRF elimination**: Custom DX cluster hosts are now DNS-resolved to IPv4, validated against private/reserved ranges, and the connection uses the validated IP (not hostname) to prevent DNS rebinding. IPv6 fallback removed to eliminate representation bypass attacks.
- **Rotator & QRZ auth**: `/api/rotator/turn`, `/api/rotator/stop`, `/api/qrz/configure`, `/api/qrz/remove` now require `API_WRITE_KEY` authentication.
- **Trust proxy auto-detect**: `trust proxy` enabled only on Railway (auto-detected), disabled on Pi/local installs to prevent rate-limit bypass via spoofed `X-Forwarded-For` headers. Override with `TRUST_PROXY` env var.
- **SSE connection limiter**: Per-IP cap on concurrent SSE streams (default 10, configurable via `MAX_SSE_PER_IP`) to prevent resource exhaustion.
- **Telnet command injection**: Control characters stripped from DX cluster login callsigns.
- **DOM XSS fixes**: `sanitizeColor()` for N3FJP logged QSO line colors; `esc()` helper for APRS Newsfeed userscript.
- **ReDoS fix**: Replaced `/\d+$/` regex with `substring()` for IP anonymization.
- **URL encoding**: `encodeURIComponent()` applied to callsign parameters in localhost fetch calls.
- **RBN callsign validation**: Input sanitized and length-checked on `/api/rbn/location/:callsign`.
- **Health endpoint**: Session details (partial IPs, user agents) gated behind `API_WRITE_KEY` auth.
- **Dockerfile**: Application now runs as non-root user (`nodejs`, UID 1001).
- **Startup warning**: Server prints visible warning when `API_WRITE_KEY` is not set.
- **Rig-bridge CORS**: Restricted to explicit origin allowlist (was wildcard `*`).
- **Rig-bridge localhost binding**: HTTP server binds to `127.0.0.1` by default (was `0.0.0.0`).
- **Rig-bridge serial port validation**: Paths validated against OS-specific patterns (COM*, /dev/tty*, /dev/cu.*).
- **Rig-bridge relay SSRF**: Relay URL validated to reject private/reserved addresses.

### Added

- **LMSAL solar image fallback**: Three-source failover for solar imagery: SDO direct → LMSAL Sun Today (Lockheed Martin) → Helioviewer API. Independent of NASA Goddard infrastructure.
- **Lightning unit preferences**: Proximity panel distances respect km/miles setting from allUnits.
- **DXCC entity selector**: Browse/search DXCC entities to set DX target in Modern and Dockable layouts.
- **DX News text scale**: Adjustable font size (0.7x–2.0x) with A-/A+ buttons. Persists in localStorage.
- **Layout lock border panel**: Lock/unlock toggle in dedicated FlexLayout border tab (Dockable layout).
- **Rig-bridge multicast**: WSJT-X relay supports UDP multicast for multi-app packet sharing.
- **Rig-bridge simulated radio**: Mock plugin for testing without hardware (`radio.type = "mock"`).
- **DX cluster TCP keepalive**: Persistent telnet sessions use OS-level keepalive and auto-reconnect after 5 min silence.
- **DX cluster SSID**: Callsign SSID (-56) appended automatically when not provided.

### Fixed

- **Rotator enabled by default**: `.env.example` had `ROTATOR_PROVIDER=pstrotator_udp` uncommented, causing fresh installs to send UDP to a hardcoded IP. All rotator lines now commented out.
- **Pi setup (armhf)**: NodeSource dropped 32-bit ARM support for Node 20+. Setup script now downloads armv7l binaries directly from nodejs.org with retry support.
- **Pi setup (electron)**: `npm install --ignore-scripts` prevents electron-winstaller postinstall failures on ARM. `ELECTRON_SKIP_BINARY_DOWNLOAD=1` skips useless Electron download. `npm prune --omit=dev` frees ~500MB after build.

## [15.5.10] - 2026-02-20

### Fixed
Expand Down
11 changes: 5 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ Thank you for helping build OpenHamClock! Whether you're fixing a bug, adding a
# 1. Fork and clone
git clone https://github.com/YOUR_USERNAME/openhamclock.git
cd openhamclock
npm ci
git checkout Staging

# 2. Install dependencies
npm install

# 3. Start the backend (Terminal 1)
# 2. Start the backend (Terminal 1)
node server.js
# → Server running on http://localhost:3001

# 4. Start the frontend dev server (Terminal 2)
# 3. Start the frontend dev server (Terminal 2)
npm run dev
# → App running on http://localhost:3000 (proxies API to :3001)
```
Expand Down Expand Up @@ -113,7 +111,7 @@ docs/update-readme

We use **Prettier** to enforce consistent formatting across the codebase. This eliminates quote style, indentation, and whitespace noise from PRs so code review can focus on logic.

**It happens automatically:** If you run `npm install`, a git pre-commit hook (via Husky + lint-staged) will auto-format any staged files before each commit. You don't need to think about it.
**It happens automatically:** After you run `npm ci`, a git pre-commit hook (via Husky + lint-staged) will auto-format any staged files before each commit. You don't need to think about it.

**Manual commands:**

Expand Down Expand Up @@ -227,6 +225,7 @@ This repository uses shared formatting and dependency lock conventions so contri

```bash
npm ci
git checkout Staging
npm run dev
```

Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ ENV NODE_ENV=production
ENV PORT=3000
ENV NODE_OPTIONS="--max-old-space-size=2048 --expose-gc"

# Create non-root user for running the application
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 -G nodejs

WORKDIR /app

# Create /data directory for persistent stats (Railway volume mount point)
Expand Down Expand Up @@ -66,6 +70,12 @@ COPY --from=builder /app/public ./public
# Create local data directory as fallback
RUN mkdir -p /app/data

# Set ownership so non-root user can write to data directories and .git (auto-update)
RUN chown -R nodejs:nodejs /app /data

# Run as non-root user
USER nodejs

# Expose ports (3000 = web, 2237 = WSJT-X UDP, 12060 = N1MM/DXLog)
EXPOSE 3000
EXPOSE 2237/udp
Expand Down
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ OpenHamClock brings DX cluster spots, space weather, propagation predictions, PO
```bash
git clone https://github.com/accius/openhamclock.git
cd openhamclock
npm install
npm ci
npm start
```

Expand Down Expand Up @@ -498,6 +498,14 @@ Live decoded FT8, FT4, JT65, JT9, and WSPR messages from WSJT-X, JTDX, or any co
2. In WSJT-X: set the UDP Server address to your OpenHamClock machine's IP (e.g., `192.168.1.100`) and port `2237`.
3. Make sure UDP port 2237 is not blocked by a firewall.

**Network setup (WSJT-X using Multicast):**

While the above configuration works just fine in a majority of cases, if you are running more than one multicast listener on a host (e.g. OpenHamClock and something like GridTracker2), then OpenHamClock needs to configure itself properly as a multicast listener.

Uncomment the `WSJTX_MULTICAST_ADDRESS` line in `.env`, and make sure that the multicast address there matches what you have set in WSJT-X. e.g. `224.0.0.1`

You will need to restart OpenHamCLock after this change.

**Cloud setup (OpenHamClock on a remote server):**

WSJT-X sends data over UDP, which only works on a local network. For cloud deployments (like Railway or openhamclock.com), you need the WSJT-X Relay Agent to bridge the gap. See the [WSJT-X Relay Agent](#wsjt-x-relay-agent) section below.
Expand Down Expand Up @@ -773,11 +781,12 @@ All configuration is done through the `.env` file. On first run, this file is au

### WSJT-X Integration

| Variable | Default | Description |
| ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WSJTX_ENABLED` | `true` | Enable the WSJT-X UDP listener on the server. |
| `WSJTX_UDP_PORT` | `2237` | UDP port for receiving WSJT-X decoded messages. Must match the port configured in WSJT-X Settings → Reporting → UDP Server. |
| `WSJTX_RELAY_KEY` | _(none)_ | Shared secret key for the WSJT-X relay agent. Required only for cloud deployments where WSJT-X can't reach the server directly over UDP. Pick any strong random string. |
| Variable | Default | Description |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WSJTX_ENABLED` | `true` | Enable the WSJT-X UDP listener on the server. |
| `WSJTX_MULTICAST_ADDRESS` | _(none)_ | Multicast address to listen for messages |
| `WSJTX_UDP_PORT` | `2237` | UDP port for receiving WSJT-X decoded messages. Must match the port configured in WSJT-X Settings → Reporting → UDP Server. |
| `WSJTX_RELAY_KEY` | _(none)_ | Shared secret key for the WSJT-X relay agent. Required only for cloud deployments where WSJT-X can't reach the server directly over UDP. Pick any strong random string. |

### DX Cluster

Expand Down Expand Up @@ -1266,11 +1275,15 @@ OpenHamClock is built by the ham radio community. We have 28+ contributors and g

```bash
git clone https://github.com/accius/openhamclock.git
cd openhamclock && npm install
cd openhamclock
git checkout Staging
npm ci
node server.js # Terminal 1 — Backend on :3001
npm run dev # Terminal 2 — Frontend on :3000
```

Open pull requests against `Staging`, not `main`.

**Read first:**

- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — Full codebase map and key patterns
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openhamclock",
"version": "15.6.4",
"version": "15.6.5",
"description": "Amateur Radio Dashboard - A modern web-based HamClock alternative",
"main": "electron/main.js",
"scripts": {
Expand Down
Loading
Loading