| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A real-time amateur radio dashboard for the modern operator.
OpenHamClock brings DX cluster spots, space weather, propagation predictions, POTA activations, SOTA activations, WWFF activations, WWBOTA activations, PSKReporter, satellite tracking, WSJT-X integration, direct rig control, and more into a single browser-based interface. Run it locally on a Raspberry Pi, on your desktop, or access it from anywhere via a cloud deployment.
🌐 Live Site: openhamclock.com
📧 Contact: Chris, K0CJH — chris@cjhlighting.com
☕ Support the Project: buymeacoffee.com/k0cjh — Running openhamclock.com comes with real hosting costs including network egress, memory, CPU, and the time spent maintaining and improving the project. There is absolutely no obligation to donate — OpenHamClock is and always will be free. But if you find it useful and want to chip in, your donations are greatly appreciated and go directly toward keeping the site running and funding future development.
🔧 Get Involved: This is an open-source project and the amateur radio community is encouraged to dig into the code, fork it, and build the features you want to see. Whether it's a new panel, a data source integration, or a bug fix — PRs are welcome. See Contributing below.
📝 License: MIT — See LICENSE
Container images are available from the github container registry at ghcr.io/accius/openhamclock Follow the quick-start steps
Node.js v20.19 or later (v22.12+ also supported) — required by Vite and the Express backend. The version of Node.js shipped by default in most Linux distributions (including Ubuntu 24.04 via apt install nodejs) is too old and will not work.
Install a current LTS release using one of these methods:
nvm (Linux / macOS — recommended):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
nvm install --ltsNodeSource (Ubuntu / Debian — system-wide):
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejsVolta (Windows / macOS / Linux):
curl https://get.volta.sh | bash
volta install nodeWindows: Download the LTS installer from nodejs.org.
npm v9 or later — ships with Node 20+ by default. Verify with npm --version.
⚠️ Ubuntu / Debian users: Do not use apt install nodejs — the packaged version is v18 which is below the minimum required. Use NodeSource or nvm as shown above.
git clone https://github.com/accius/openhamclock.git
cd openhamclock
npm ci
npm startOpen http://localhost:3000 in your browser. On first run, the server creates a .env file from .env.example and builds the React frontend automatically. A setup wizard will walk you through entering your callsign and grid locator.
In development mode, the frontend runs on port 3000 and proxies API requests to the backend on port 3001.
You can also configure your station by editing .env directly:
# .env — the only two lines you need to change
CALLSIGN=K0CJH
LOCATOR=EN10Restart with npm start after editing .env to apply changes.
For development with hot reload:
# Terminal 1 — Backend (http://localhost:3001)
node server.js
# Terminal 2 — Frontend (hot reload on http://localhost:3000)
npm run devFrom the local server to enable clients to use p533 modules for the client side propagation calculation rather than very rough estimates (which are based on band and time-of-day), then (must have write access to repository) run the following from the top-level distribution directory, then restart the server.
node scripts/fetch-wasm.jsOpenHamClock is built from independent modules, each focused on a specific data source or function. Every module fetches its own data, manages its own refresh cycle, and can be toggled on or off. Below is a detailed walkthrough of each one.
The central interactive map is the heart of the dashboard. It ties every other module together visually — DX spots, POTA/WWFF/SOTA/WWBOTA activators, satellite orbits, signal paths, and your own station location all appear here.
What it shows:
How to use it:
How it works under the hood: The WorldMap.jsx component uses Leaflet.js for rendering. DX spot coordinates are resolved in order of precision: callsign database lookup → DXCC prefix table → Maidenhead grid square extracted from spot comments. Great-circle paths are calculated using the Haversine formula and rendered as polylines with intermediate waypoints for visual accuracy. The day/night terminator is computed from the current solar declination and hour angle. The map tile style adapts to your selected theme (dark tiles for dark themes, light tiles for light themes).
Live DX spots streamed from the worldwide DX Spider cluster network. This is the primary data feed for most operators — every spot that comes through the cluster appears here in real time.
What it shows:
How to use it:
Filtering (DX Filter Manager):
The filter manager has six tabs, each with its own set of filters. Filters are AND-combined (a spot must pass all active filters to be shown):
All filter settings are saved to your browser's localStorage and persist across sessions.
DX cluster source options (configurable in Settings → DX Cluster tab or in .env):
| Source | Description |
|---|---|
| DX Spider Proxy (default) | Persistent telnet connection to the DX Spider network via a proxy microservice. Most reliable, best spot volume. |
| HamQTH | Spots from HamQTH's DX cluster HTTP feed. No telnet required. |
| DXWatch | Spots from the DXWatch cluster. |
| Auto | Tries all sources in order and uses the first one that responds. |
Spot retention: Spots remain in the list for 30 minutes by default. Change this via the SPOT_RETENTION_MINUTES variable in .env (range: 5–30 minutes) or in the DX Filter Manager settings tab.
How it works under the hood: The backend (server.js) connects to the DX Spider cluster network through the DX Spider Proxy microservice (a separate Node.js process that maintains a persistent telnet connection). The proxy parses incoming spot lines, and the main server resolves station locations through a built-in DXCC prefix table (340+ prefixes covering all DXCC entities). The frontend polls /api/dxcluster/spots every 5 seconds. Coordinates are resolved in priority order: direct callsign database → DXCC prefix → Maidenhead grid square extracted from the spot comment field.
Real-time digital mode reception reports from the PSKReporter network. Shows who is hearing whom across all digital modes worldwide — an invaluable tool for checking your signal reach on FT8, FT4, JS8, and other digital modes.
What it shows:
How to use it:
PSK Filter Manager options:
Data sources (automatic, no configuration needed):
The current connection method is shown in the panel footer. You don't need to configure anything — the system handles failover automatically.
How it works under the hood: The usePSKReporter hook in the frontend connects to the PSKReporter MQTT broker over a WebSocket connection, subscribes to spot topics matching your callsign and active filters, and parses the compact binary message format (sender call, receiver call, locators, frequency, mode, SNR). Grid squares are converted to latitude/longitude for map display using the Maidenhead locator system. The server provides an HTTP fallback via /api/pskreporter/http/:call with a 2-minute cache and automatic backoff on 403/429 responses.
Shows all currently active POTA activators worldwide with their park references, frequencies, and map locations. If you're a POTA hunter, this panel tells you exactly who is on the air right now and where.
What it shows:
How to use it:
Smart filtering (automatic, no configuration needed):
How it works under the hood: The server proxies the POTA API (api.pota.app/spot/activator) with a 1-minute cache to reduce load on the upstream service. The usePOTASpots hook fetches spots every 60 seconds, filters out QRT/expired entries, sorts by recency, and resolves coordinates from the API's latitude/longitude fields. For the rare case where the API returns a spot without coordinates, the hook falls back to Maidenhead grid square conversion (grid6 → grid4 → center of grid).
Shows all currently active WWFF activators worldwide with their park references, frequencies, and map locations. If you're a WWFF chaser, this panel tells you exactly who is on the air right now and where.
What it shows:
How to use it:
Smart filtering (automatic, no configuration needed):
How it works under the hood: The server proxies the WWFF API (spots.wwff.co/static/spots.json) with a 90 second cache to reduce load on the upstream service. The useWWFFSpots hook fetches spots every 60 seconds, filters out QRT/expired entries, sorts by recency, and resolves coordinates from the API's latitude/longitude fields.
Shows all currently active WWBOTA activators worldwide with their bunker references, frequencies, and map locations. If you're a WWBOTA chaser, this panel tells you exactly who is on the air right now and where.
What it shows:
How to use it:
Smart filtering (automatic, no configuration needed):
How it works under the hood: The browser connects directly to WWBOTA API via server-sent events, fetching the initial last 60 minutes of spots, and then has new and updated spots pushed directly to the browser. QRT spots are filtered out, and where multiple bunkers are being activated at once, the first bunker's name and location are used.
Displays the three key solar indices that affect HF radio propagation. If you operate HF, these numbers directly determine what bands will be open.
What it shows:
How to use it: Check these numbers before getting on the air. High SFI + low Kp = good day for DX. The space weather values are also displayed in the header bar for quick reference without scrolling.
Data source: NOAA Space Weather Prediction Center (SWPC) JSON feeds. Updates every 5 minutes.
A multi-view panel that cycles through four displays. Click the toggle button in the panel header to switch between them. Your selection persists across sessions.
View 1 — Solar Image: Live solar imagery showing current sunspot activity and coronal features. Useful for spotting active regions that may produce flares.
View 2 — Solar Indices: Detailed SFI, K-index, and SSN values with 30-day history sparkline charts. This gives you a trend view — is the SFI trending up or down this month? Are geomagnetic storms becoming more frequent?
View 3 — X-Ray Flux: Real-time GOES satellite X-ray flux chart showing solar flare activity over the past 6 hours. Flares are classified by peak flux: A (quiet) → B → C → M → X (intense). M-class and X-class flares can cause HF radio blackouts on the sunlit side of Earth. The current flare class is displayed prominently.
View 4 — Lunar Phase: Current moon phase with a visual SVG rendering, illumination percentage, and calculated dates for the next full and new moon. Useful for EME (Earth-Moon-Earth) operators who need to know moon position and illumination for moonbounce contacts.
How to use it: Click the cycle button (◀ ▶ arrows or the view label) in the panel header to rotate through all four views. The panel remembers which view you last used.
Data sources: Solar indices from NOAA SWPC. X-ray flux from GOES satellite primary sensor feed. Lunar phase calculated astronomically from the current date using the synodic month (29.53059 days).
Real-time HF and VHF band conditions from the N0NBH solar conditions feed, sourced from NOAA data. This is your at-a-glance guide for which bands are worth tuning to right now.
What it shows:
Each HF band from 80m through 10m with day and night condition indicators:
Additional information in the panel:
How to use it: Before tuning to a band, check its condition indicator here. Hover over any band tile to see the full day/night breakdown in a tooltip. The VHF section is especially useful for 6m operators watching for E-skip openings.
How it works under the hood: The server fetches N0NBH's XML solar data feed (hamqsl.com/solarxml.php) via the /api/n0nbh endpoint with a 1-hour server-side cache (N0NBH updates every 3 hours). The useBandConditions hook maps N0NBH's grouped band ranges (80m-40m, 30m-20m, 17m-15m, 12m-10m) to individual bands, with separate day and night conditions. The current condition displayed is based on UTC time (day = 06:00-18:00 UTC).
HF propagation reliability predictions between your station (DE) and whatever DX target you've selected on the map.
Standard mode: Uses a built-in propagation model based on current SFI, SSN, Kp, great-circle path distance, solar zenith angle, geomagnetic latitude, and estimated MUF (Maximum Usable Frequency) for each band.
ITU-R P.533-14 predictions: By default, all installs use the public OpenHamClock ITURHFProp prediction service for ITU-R P.533-14 propagation calculations — the international standard for HF propagation prediction. If you prefer to self-host, deploy the optional ITURHFProp microservice (documentation and deployment examples in iturhfprop-service/README.md)
Hybrid correction: When ionosonde data is available from prop.kc2g.com, the system applies real-time corrections based on actual measured ionospheric conditions rather than just modeled values. This can catch unusual propagation events that models miss.
Data refresh: Predictions update every 10 minutes.
Data gathered from prop.kc2g.com originates from the Global Ionospheric Radio Observatory who have graciously offered free access for that purpose. Thanks are given to all of the ionosonde stations participating in that network.
Real-time tracking of amateur radio satellites with orbital visualization on the map.
What it shows:
How to use it:
Satellite catalog: Includes 40+ amateur radio satellites from CelesTrak's amateur radio TLE catalog: ISS, RS-44, AO-91, AO-92, QO-100, FO-99, CAS-4A/B, and many more.
How it works under the hood: The server fetches Two-Line Element (TLE) data from CelesTrak every 6 hours. The frontend uses the satellite.js library to run SGP4 orbital mechanics calculations in the browser, predicting each satellite's position at the current time. Position updates run every 5 seconds for smooth motion on the map.
Upcoming and currently active amateur radio contests from the global contest calendar.
What it shows:
How to use it: Check this panel when planning your weekend operating. If a major contest is active, expect the bands to be packed — great for quick QSOs, less great for ragchewing.
Data source: Contest Calendar RSS feed from contestcalendar.com, updated every 30 minutes.
Active and upcoming DXpeditions to rare DXCC entities. If you're chasing new countries for DXCC, this is where you find out who's operating from where.
What it shows:
Data source: Parsed from NG3K's DXpedition listing, updated every 30 minutes.
A scrolling ticker across the top of the dashboard showing the latest DX news headlines. Stay informed about upcoming activations, band openings, and amateur radio events without leaving the dashboard.
Data source: Scraped from DXNews.com, updated every 30 minutes.
Live decoded FT8, FT4, JT65, JT9, and WSPR messages from WSJT-X, JTDX, or any compatible digital mode software. See decoded stations on the map in real time as they come in.
What it shows:
Local setup (WSJT-X on the same machine):
Network setup (WSJT-X on a different machine on your LAN):
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 section below.
How it works under the hood: The Node.js server (server.js) opens a UDP listener on port 2237 (configurable via WSJTX_UDP_PORT). It parses the WSJT-X binary protocol (QDataStream format) to extract decoded messages, heartbeat status, and QSO logs. The frontend polls /api/wsjtx/decodes every 2 seconds. For cloud deployments, the relay agent captures UDP packets locally and forwards them to the server over HTTPS using a shared secret key.
Current weather conditions at your station location, displayed in the header bar and the DE location panel and, optionally, weather at the currently selected DX target station.
In the header:
Below the DE and optionally DX Location panel:
How to use it: The weather is shown automatically based on your configured station coordinates and, optionally, at the currently selected DX target station. Click the weather line in the DE/DX panel to expand full details or collapse to a one-line summary.
Data sources:
No configuration needed — weather works automatically based on your station coordinates.
Refresh interval: Every 2 hours (weather data is cached server-side).
Information panels for your station (DE) and the currently selected DX target station.
DE Panel (left sidebar) shows:
DX Panel (right sidebar) shows:
How to use it: The DX panel updates whenever you click a spot in the DX cluster, click a location on the map, or manually enter a callsign/grid in the DX panel. The bearing shown is useful for rotating a directional antenna.
The persistent bar across the top of the dashboard provides at-a-glance information and quick controls.
From left to right:
A classic analog clock display showing local time with additional station information.
What it shows:
How to use it: The clock automatically sizes to fit whatever panel size you give it. In the dockable layout, you can add it via the "+" button and resize the panel as desired. A larger panel gives you a bigger, more readable clock face.
Availability: Always available in the dockable layout via the "Add Panel" menu. In the classic layout, enable it by setting CLASSIC_ANALOG_CLOCK=true in your .env file (disabled by default to keep the classic layout compact).
Four visual themes, selectable in Settings or via THEME in .env:
| Theme | Description |
|---|---|
| Dark | Modern dark interface with amber and cyan accents on a charcoal background. Easy on the eyes for late-night operating. This is the default. |
| Light | Light background with darker text. Best for daytime use or brightly lit environments where a dark screen causes too much contrast. |
| Legacy | Classic green-on-black terminal aesthetic reminiscent of vintage station monitors. Monochrome green text with a true-black background. |
| Retro | 90s-era GUI style with teal backgrounds, silver beveled panels, and shadow effects. A nostalgic throwback to early Windows and OS/2 interfaces. |
All themes use CSS custom properties defined in src/styles/main.css. To create your own theme, add a new set of CSS variables following the existing pattern.
Three layout modes, selectable in Settings or via LAYOUT in .env:
| Layout | Description |
|---|---|
| Modern | Responsive 3-column grid layout. The map fills the center column, with sidebar panels on the left and right. Designed for widescreen monitors (1920×1080 and above). Panels reflow on smaller screens. |
| Classic | Inspired by the original HamClock by Elwood Downey, WB0OEW (SK). Features a black background, large colored numeric displays for callsign and frequency, a rainbow frequency bar, and a full-width map. Optimized for dedicated displays and Raspberry Pi kiosk mode. |
| EmComm | Emergency Communications dashboard for ARES/RACES operations. Full-screen map with range rings, NWS alerts, FEMA disaster declarations, nearby shelters, and APRS emergency stations with resource tracking. See EmComm Layout below. |
The EmComm (Emergency Communications) layout is a purpose-built dashboard for ARES, RACES, SKYWARN, and served agency operations. It replaces the standard sidebar panels with emergency-focused data including real-time APRS station tracking, net operations, point-to-point messaging, and resource aggregation. All features are designed to work over RF alone when internet is unavailable.
For the full guide including API endpoints, configuration, and architecture details, see docs/emcomm-roadmap.md.
Sidebar panels:
| Panel | Description |
|---|---|
| Resource Summary | Aggregated resource dashboard showing totals across all APRS stations reporting resource tokens. Capacity bars with color coding. |
| NWS Alerts | Active weather watches, warnings, and advisories from the National Weather Service, color-coded by severity with countdown to expiry. |
| Disaster Declarations | Recent FEMA disaster declarations for your state, auto-resolved from your station coordinates via reverse geocoding. |
| Nearby Shelters | Open shelters within 200 km, sorted by distance, with capacity bars, wheelchair (♿) and pet-friendly (🐾) indicators. Click to pan map. |
| EmComm Stations | APRS stations using emergency symbols (EOC, Shelter, ARES, Skywarn, Red Cross, Emergency), with resource token pills and RF source badges. |
| Net Roster | Live net operations with operator check-ins, status messages, last-heard time, resource tokens, and MSG button for point-to-point messaging. |
APRS integration:
The EmComm layout receives APRS data from two sources:
An APRS Source Selector lets you filter between All Sources, RF Only, or Internet Only.
Net operations:
Operators check in to emergency nets via APRS message:
APRS messaging:
Click the MSG button on any net roster entry to compose a point-to-point APRS message (67-character limit). Messages are sent via your local TNC. Incoming messages, bulletins, and shelter reports are displayed in the feed.
APRS resource tokens:
Operators at shelters or EOCs can encode structured resource data in their APRS beacon comments using bracket notation. OpenHamClock parses these tokens and displays them as visual resource cards.
| Token | Content |
|---|---|
| [Key Value] | quantity (e.g., [Food 50]) |
| [Key Curr/Max] | capacity (e.g., [Beds 30/100]) |
| [Key -Value] | resource NEEDED (e.g., [Water -100]) |
| [Key OK] | status nominal |
| [Key !] | critical alert |
Built-in token keys: Beds, Water, Food, Power, Fuel, Med, Staff, Evac, Comms, Gen. The parser accepts any key.
Example: [Beds 30/100][Power OK][Water -50] Shelter Alpha renders as three color-coded pills and feeds the Resource Summary dashboard.
Winlink gateway integration:
A rig-bridge plugin for Winlink RMS gateway discovery and Pat client messaging is built but waiting on API key approval from the Winlink team. See issue #297. Pat client integration (local messaging) works independently without the API key.
Map features:
OpenHamClock can play audible tones when new items appear in data feeds — useful for monitoring while doing other tasks in the shack.
Supported feeds: POTA Spots, SOTA Spots, WWFF Spots, WWBOTA Spots, DX Cluster, DXpeditions, Contests.
How to enable:
All feeds default to OFF. Settings persist in localStorage.
Behavior notes:
OpenHamClock has a plugin system for adding custom map overlays without modifying the core code. Plugins are self-contained React hooks that handle their own data fetching, rendering, and lifecycle.
Built-in plugins:
| Layer | Description |
|---|---|
| Aurora | Real-time auroral oval overlay from the NOAA OVATION model. Shows the current extent of the aurora borealis/australis. Useful for VHF operators (aurora can enable 2m and 6m contacts) and HF operators (aurora degrades polar HF paths). |
| Earthquakes | Recent seismic activity markers from USGS. Large earthquakes can temporarily affect HF propagation through atmospheric acoustic-gravity waves. |
| Weather Radar | Precipitation overlay showing rain, snow, and storms. |
How to enable/disable layers:
Layer preferences persist in localStorage.
Creating your own plugin: See src/plugins/OpenHamClock-Plugin-Guide.md for the complete developer guide. In short: create a new React hook in src/plugins/layers/, register it in layerRegistry.js, and it appears automatically in the Settings panel. No modifications to WorldMap.jsx are needed.
The interface is available in 10 languages, selectable in Settings:
🇬🇧 English · 🇫🇷 Français · 🇪🇸 Español · 🇩🇪 Deutsch · 🇳🇱 Nederlands · 🇧🇷 Português · 🇯🇵 日本語 · 🇰🇷 한국어 · 🇮🇹 Italiano · 🇸🇮 Slovenščina
Language files are in src/lang/. Each is a JSON file with translation keys. Contributions of new translations are welcome — just copy en.json, translate the values, and submit a PR.
Save and switch between named configuration profiles. Useful when multiple operators share a single HamClock, or when you want to quickly toggle between different personal setups (contest mode, field day, everyday).
What a profile captures: Everything — your callsign, location, theme, layout, dock arrangement, map layers, DX filters, PSK filters, satellite filters, VOACAP preferences, temperature unit, time format, and all other openhamclock_* settings.
How to use it:
Profile actions:
Profiles are stored in your browser's localStorage. The currently active profile is shown with a green indicator.
When the server is updated with a new version (e.g., via git pull + restart, or a Railway deployment), all connected browsers automatically detect the change and reload. There is nothing to configure.
How it works: The frontend polls /api/version every 60 seconds. When the returned version number changes, a toast notification appears at the bottom of the screen ("🔄 OpenHamClock Updated — v15.0.0 → v15.1.0 — Reloading...") and the page reloads after 3 seconds. The version is read from package.json as the single source of truth.
Visit /api/health in your browser for a real-time server status dashboard. The page auto-refreshes every 30 seconds.
What it shows:
The JSON API (/api/health?format=json) returns all the same data in structured JSON, including a 24-hour recentTrend array and the full sessions object.
Visitor stats persist across restarts via file-based storage. Configure the storage location with STATS_FILE in .env (defaults to ./data/stats.json locally or /data/stats.json on Railway volumes).
All configuration is done through the .env file. On first run, this file is auto-created from .env.example. You can also change most settings through the browser-based Settings panel.
Can't find the .env file? Files starting with a dot are hidden by default on Linux, Mac, and Raspberry Pi.
- Terminal: ls -la to see hidden files, or nano .env to edit directly
- File manager (Pi/Linux): Press Ctrl+H to toggle hidden files
- Mac Finder: Press Cmd+Shift+. to toggle hidden files
- If the file doesn't exist yet: Run npm start once and it will be auto-created, or copy it manually: cp .env.example .env
The .env file is located in the root of your OpenHamClock directory (same folder as server.js and package.json).
| Variable | Default | Description |
|---|---|---|
| CALLSIGN | N0CALL | Your amateur radio callsign. This is shown in the header bar and used for DX cluster login, PSKReporter queries, and "My Spots" tracking. |
| LOCATOR | FN31 | Your Maidenhead grid locator (4 or 6 characters). Used to calculate your station coordinates if LATITUDE/LONGITUDE aren't set. |
| LATITUDE | (from locator) | Station latitude in decimal degrees. Overrides the latitude calculated from LOCATOR. |
| LONGITUDE | (from locator) | Station longitude in decimal degrees. Overrides the longitude calculated from LOCATOR. |
| Variable | Default | Description |
|---|---|---|
| PORT | 3001 | Backend API server port. In development, the Vite frontend runs on 3000 and proxies /api to this port. |
| HOST | localhost | Bind address. Set to 0.0.0.0 to make OpenHamClock accessible from other devices on your LAN (tablets, phones, other PCs). |
| LOG_LEVEL | warn | Server log verbosity: debug (everything), info (operational), warn (problems), error (failures only). Use warn for production. |
| Variable | Default | Description |
|---|---|---|
| UNITS | imperial | imperial This setting is deprecated and no longer used. |
| DISTUNITS | imperial. | imperial (miles, inches, ...) or `metric (km, m, ...) For displaying anything involving a distance or speed. |
| TEMPUNITS | imperial. | imperial (°F) or metric (°C). For displaying Temperatures. |
| PRESSUNITS. | imperial | imperial (inHg) or metric (hPa) |
| TIME_FORMAT | 12 | 12 or 24 hour clock format. Can also be toggled by clicking the local clock in the header. |
| THEME | dark | dark, light, legacy, or retro. See Themes and Layouts. |
| LAYOUT | modern | modern or classic. See Themes and Layouts. |
| TZ | (browser) | IANA timezone identifier (e.g., America/New_York, Europe/London). Only needed if your browser spoofs the timezone (common with privacy browsers like Librewolf). |
| Variable | Default | Description |
|---|---|---|
| SHOW_POTA | true | Show POTA activator markers on the map. |
| SHOW_SATELLITES | true | Show satellite tracks on the map. |
| SHOW_DX_PATHS | true | Show great-circle DX signal paths on the map. |
| SHOW_DX_WEATHER | true | Show weather for the selected DX location. |
| CLASSIC_ANALOG_CLOCK | false | Show analog clock panel in the classic layout. Always available in dockable layout. |
| Variable | Default | Description |
|---|---|---|
| OPENWEATHER_API_KEY | (none) | OpenWeatherMap API key. Only needed for the Cloud Layer map overlay — weather data uses Open-Meteo directly from each user's browser with no key. Get a free key at openweathermap.org/api. Also set VITE_OPENWEATHER_API_KEY to the same value. |
| ITURHFPROP_URL | Public service | URL for ITU-R P.533 propagation predictions. Defaults to the public OpenHamClock service. Override only if self-hosting the iturhfprop-service/. |
| DXSPIDER_PROXY_URL | (none) | URL of your DX Spider proxy. A default proxy is provided, so you only need this if you're running your own. |
| 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. |
| Variable | Default | Description |
|---|---|---|
| DX_CLUSTER_CALLSIGN | (CALLSIGN-56) | Callsign used for DX cluster login. Defaults to your callsign with SSID suffix -56. Use -57 for a staging/test instance to avoid conflicts. |
| SPOT_RETENTION_MINUTES | 30 | How long DX spots stay in the list before aging out. Range: 5–30 minutes. |
Settings are loaded in this order (first match wins):
Your .env file is never overwritten by updates, so your configuration is always safe.
Works on Linux, macOS, and Windows. Requires Node.js 18+ (22 LTS recommended) and Git.
One-line install (Linux / macOS):
curl -fsSL https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-linux.sh | bashThis clones the repo, installs dependencies, builds the frontend, creates a .env config file, and generates a run.sh launcher. After install, edit ~/openhamclock/.env to set your CALLSIGN and LOCATOR, then start with ~/openhamclock/run.sh.
Auto-start on boot (Linux with systemd — Ubuntu, Debian, Fedora, etc.):
curl -fsSL https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-linux.sh | bash -s -- --serviceThis does everything above plus creates a systemd service that starts OpenHamClock automatically on boot. Manage it with:
sudo systemctl restart openhamclock # Restart (after .env changes)
sudo systemctl status openhamclock # Check status
sudo journalctl -u openhamclock -f # View logsmacOS note: macOS does not use systemd, so the --service flag is not supported. Use ~/openhamclock/run.sh to start manually, or run npm run electron for a native desktop window.
Windows:
Set-ExecutionPolicy Bypass -Scope Process -Force
iex (iwr https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-windows.ps1).ContentThis clones the repo to %USERPROFILE%\openhamclock, installs dependencies, builds the frontend, creates a start.bat launcher, and adds a desktop shortcut. After install, edit %USERPROFILE%\openhamclock\.env to set your CALLSIGN and LOCATOR, then start with start.bat or npm start.
Manual install (all platforms):
git clone https://github.com/accius/openhamclock.git
cd openhamclock
npm install
npm run build
npm startOpen http://localhost:3000.
Access from other devices on your LAN (phone, tablet, another PC):
To find your local IP: run ipconfig (Windows) or ifconfig / ip addr (Mac/Linux).
One-line install for Raspberry Pi (3B, 3B+, 4, 5). Supports both graphical and headless operation.
Supported operating systems:
| OS | Debian | Status | Display |
|---|---|---|---|
| Raspberry Pi OS Bookworm | 12 | ✅ Recommended | X11 (openbox / LXDE) |
| Raspberry Pi OS Trixie | 13 | ✅ Supported | Wayland (labwc) |
| Raspberry Pi OS Bullseye | 11 | ⚠️ Legacy, best-effort | X11 |
Standard install (kiosk mode — auto-starts fullscreen on boot):
curl -fsSL https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-pi.sh | bash -s -- --kioskThis is the recommended option for a dedicated shack display. The Pi boots directly into a fullscreen Chromium browser showing OpenHamClock. The kiosk launcher automatically detects Wayland (Trixie/labwc) or X11 (Bookworm/LXDE) and adjusts accordingly — no manual configuration needed.
Server-only install (headless, no GUI):
curl -fsSL https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-pi.sh | bash -s -- --serverRuns OpenHamClock as a web server only. Access it from any browser on your LAN.
Standard install (manual browser launch):
curl -fsSL https://raw.githubusercontent.com/accius/openhamclock/main/scripts/setup-pi.sh | bashAfter installation, configure your station:
nano ~/openhamclock/.env # Set CALLSIGN and LOCATOR
sudo systemctl restart openhamclock # Apply changesThe setup script creates .env automatically from the built-in template and enables server-side settings sync (SETTINGS_SYNC=true) for Pi installs. This means your CALLSIGN and LOCATOR values from .env appear on screen as soon as the service restarts — no manual UI configuration step required.
The Pi setup script installs Node.js 22 LTS, clones the repository, builds the frontend, creates a systemd service (openhamclock.service) for automatic startup, and optionally configures Chromium in kiosk mode. It also installs fonts-noto-color-emoji so that all emoji icons display correctly in Chromium.
Follow the docker documentation
Railway deployment files (railway.toml, railway.json) are included for one-click cloud deployment.
Deploy from CLI:
railway upDeploy from GitHub: Connect your GitHub repository to Railway for automatic deploys on every push.
Environment variables to set in Railway's dashboard:
| Variable | Value | Notes |
|---|---|---|
| CALLSIGN | Your callsign | Required |
| LOCATOR | Your grid | Required |
| HOST | 0.0.0.0 | Required for Railway |
| LOG_LEVEL | warn | Recommended to stay under Railway's 500 logs/sec limit |
| WSJTX_RELAY_KEY | (random string) | Only if using the WSJT-X relay agent |
Cost: Railway's free tier is usually sufficient for a single-user instance.
The DX Spider Proxy is a standalone microservice (in the dxspider-proxy/ directory) that maintains a persistent telnet connection to the DX Spider cluster network and serves spots over HTTP.
Why a separate proxy? DX Spider uses telnet (a raw TCP protocol), which browsers cannot speak directly. The proxy solves three problems:
Cluster nodes (tried in order of priority):
If the primary node is down, the proxy automatically tries the next one.
SSID management: Every DX Spider connection requires a unique callsign-SSID combination. OpenHamClock uses:
The original HamClock uses -55, so there's no conflict between OpenHamClock and HamClock.
Deploying your own proxy: The proxy is typically deployed separately (e.g., on Railway) and connected to the main app via the DXSPIDER_PROXY_URL environment variable. A default shared proxy is provided out of the box, so most users don't need to run their own.
The relay agent (wsjtx-relay/relay.js) bridges WSJT-X on your local machine to a cloud-hosted OpenHamClock instance.
The problem: WSJT-X sends decoded messages over UDP, which only works on a local network. If your OpenHamClock is running in the cloud (e.g., on Railway or at openhamclock.com), UDP packets can't reach it.
The solution: The relay agent runs on your local machine, captures WSJT-X UDP packets, and forwards them to your remote server over HTTPS.
Setup:
On your server (or Railway dashboard), set WSJTX_RELAY_KEY to any strong random string:
WSJTX_RELAY_KEY=my-super-secret-relay-key-2024On your local machine (where WSJT-X runs), start the relay:
cd wsjtx-relay
node relay.js --url https://openhamclock.com --key my-super-secret-relay-key-2024Or with environment variables:
OPENHAMCLOCK_URL=https://openhamclock.com RELAY_KEY=my-super-secret-relay-key-2024 node relay.jsIn WSJT-X: Settings → Reporting → UDP Server → 127.0.0.1:2237
The relay agent has zero npm dependencies (uses only Node.js built-ins), batches messages for efficiency (sends every 2 seconds instead of per-decode), and automatically reconnects if the connection drops. Each browser session gets its own relay data stream, so multiple operators can share the same server.
cd ~/openhamclock
./scripts/update.shThe update script: backs up your .env → pulls latest code → installs new dependencies → rebuilds the frontend → restores your .env. Then restart:
sudo systemctl restart openhamclock
# or
./restart.shRun the bundled update script from your openhamclock directory:
cd C:\Users\YourName\openhamclock
Set-ExecutionPolicy Bypass -Scope Process -Force
.\scripts\update.ps1The script backs up your .env → pulls latest code → installs any new dependencies → rebuilds the frontend → restores your .env. Then restart:
npm startOr double-click start.bat if you created it during setup.
Enable automatic updates by setting the following in .env:
AUTO_UPDATE_ENABLED=true
AUTO_UPDATE_INTERVAL_MINUTES=60
AUTO_UPDATE_ON_START=false
AUTO_UPDATE_EXIT_AFTER=true
When enabled, OpenHamClock periodically checks GitHub for updates and runs ./scripts/update.sh --auto. After a successful update it exits so a supervisor (systemd/pm2) can restart it. If you're running in a terminal, you'll need to restart manually.
On local installs, you can also click the UPDATE button in the header to start the update process on demand.
Docker documentation on updating
Push to your connected GitHub repo, or run railway up. Railway redeploys automatically.
OpenHamClock is a React + Node.js application. The Node.js backend acts as an API proxy and data aggregator — all external API calls go through it, cached to reduce load on upstream services. The React frontend handles all rendering and user interaction.
openhamclock/
├── server.js # Node.js backend — API proxy, data aggregation, WSJT-X listener
├── config.js # Server configuration loader (.env → runtime config)
├── src/
│ ├── App.jsx # Main React application — state management, layout, component wiring
│ ├── main.jsx # React entry point
│ ├── components/ # UI components (one per panel/feature)
│ │ ├── WorldMap.jsx # Leaflet map with all overlays (DX, POTA, WWFF, satellites, paths)
│ │ ├── Header.jsx # Top bar — callsign, clocks, weather, SFI/K/SSN, controls
│ │ ├── DXClusterPanel.jsx # DX spot list with band coloring and hover highlighting
│ │ ├── DXFilterManager.jsx # DX cluster filter modal (zones, bands, modes, watchlist, exclude)
│ │ ├── PSKReporterPanel.jsx # PSKReporter TX/RX tabs with signal reports
│ │ ├── PSKFilterManager.jsx # PSKReporter filter modal (bands, modes, time window)
│ │ ├── POTAPanel.jsx # POTA activators scrollable list with map toggle
│ │ ├── WWFFPanel.jsx # WWFF activators scrollable list with map toggle
│ │ ├── WWBOTAPanel.jsx # WWBOTA activators scrollable list with map toggle
│ │ ├── SpaceWeatherPanel.jsx # SFI / K-index / SSN gauges
│ │ ├── SolarPanel.jsx # 4-view cycling: solar image, indices, x-ray flux, lunar phase
│ │ ├── BandConditionsPanel.jsx # HF band open/closed indicators
│ │ ├── PropagationPanel.jsx # Per-band propagation reliability predictions
│ │ ├── ContestPanel.jsx # Upcoming/active contest calendar
│ │ ├── DXpeditionPanel.jsx # Active DXpedition list
│ │ ├── DXNewsTicker.jsx # Scrolling DX news headline bar
│ │ ├── LocationPanel.jsx # DE/DX station info panels
│ │ ├── SettingsPanel.jsx # Settings modal (station, theme, layout, DX source, map layers)
│ │ ├── Icons.jsx # SVG icon components
│ │ └── PluginLayer.jsx # Plugin system mount point for map overlays
│ ├── hooks/ # Data fetching hooks — one per data source, each manages its own polling
│ │ ├── useDXCluster.js # DX Spider spots — polls every 5 seconds
│ │ ├── usePSKReporter.js # PSKReporter MQTT + HTTP fallback — real-time
│ │ ├── usePOTASpots.js # POTA activators — polls every 60 seconds
│ │ ├── useWWFFSpots.js # WWFF activators — polls every 60 seconds
│ │ ├── useWWBOTASpots.js # WWBOTA activators — opens live SSE connection from browser
│ │ ├── useSpaceWeather.js # NOAA SFI/Kp/SSN — polls every 5 minutes
│ │ ├── useSolarIndices.js # Extended solar data with history — polls every 15 minutes
│ │ ├── useBandConditions.js # Band conditions — recalculates when SFI/Kp change
│ │ ├── usePropagation.js # Propagation model — polls every 10 minutes
│ │ ├── useSatellites.js # Satellite tracking — SGP4 position every 5 seconds
│ │ ├── useContests.js # Contest calendar — polls every 30 minutes
│ │ ├── useDXpeditions.js # DXpedition list — polls every 30 minutes
│ │ ├── useDXPaths.js # DX spot paths for map — polls every 10 seconds
│ │ ├── useMySpots.js # Your callsign spotted by others — polls every 30 seconds
│ │ ├── useWeather.js # Weather — polls every 15 minutes
│ │ └── useWSJTX.js # WSJT-X decoded messages — polls every 2 seconds
│ ├── utils/
│ │ ├── config.js # App configuration (localStorage read/write, theme application)
│ │ ├── geo.js # Grid square conversion, bearings, distances, sun/moon calculations
│ │ └── callsign.js # Band detection, mode detection, DXCC prefix lookup
│ ├── plugins/ # Map layer plugin system
│ │ ├── layerRegistry.js # Central plugin registration
│ │ └── layers/ # Built-in plugins (aurora, earthquakes, weather radar)
│ ├── lang/ # i18n translation files (8 languages)
│ └── styles/
│ └── main.css # Theme CSS variables, base styles, responsive breakpoints
├── dxspider-proxy/ # DX Spider telnet proxy microservice
├── iturhfprop-service/ # ITU-R P.533 propagation prediction microservice (self-host alternative)
├── wsjtx-relay/ # WSJT-X UDP → HTTPS relay agent
├── electron/ # Electron desktop app wrapper (experimental)
├── scripts/ # Setup and update scripts
│ ├── setup-pi.sh # Raspberry Pi one-line installer
│ ├── setup-linux.sh # Linux / macOS installer (--service for systemd)
│ ├── setup-windows.ps1 # Windows PowerShell installer
│ ├── update.ps1 # Windows update script (backup → pull → rebuild → restore)
│ └── update.sh # Linux/Pi update script (backup → pull → rebuild → restore)
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose configuration
├── railway.toml # Railway deployment configuration
├── railway.json # Railway build settings
└── .env.example # Configuration template (auto-copied to .env on first run)
All external API calls go through the Node.js backend, which caches responses to reduce load on upstream services. The frontend never contacts external APIs directly (except PSKReporter MQTT and WWBOTA, which use WebSocket and Server-Sent Events connection from the browser respectivley).
NOAA SWPC ──┐
POTA API ───┤
WWFF API ───┤
SOTA API ───┤ ┌─ WorldMap
DX Spider ──┼──► Node.js Server ──► React ─┼─ DX Cluster Panel
CelesTrak ──┤ (API proxy + ├─ Space Weather Panel
N0NBH ──────┤ data cache) ├─ Band Conditions (N0NBH)
HamQTH ─────┤ ├─ Propagation Panel
Contest Cal ┤ └─ ... all other panels
Ionosonde ──┘
WSJT-X UDP ──► Server listener ──► React ──► WSJT-X Panel
(or Relay Agent)
PSKReporter MQTT ──────────────► React ──► PSKReporter Panel
(direct WebSocket)
WWBOTA API ──────────────► React ──► Panel
(direct Server-Sent Events)
The backend exposes these REST endpoints. All data endpoints return JSON. Cache durations shown are server-side; the frontend may poll at different intervals.
| Endpoint | Description | Cache |
|---|---|---|
| GET /api/config | Server configuration (callsign, location, features, version) | — |
| GET /api/version | Lightweight version check (for auto-refresh polling) | no-cache |
| GET /api/health | Health dashboard with uptime, visitors, concurrent users, session analytics, API traffic | — |
| GET /api/n0nbh | N0NBH band conditions (SFI, K, bands, VHF, geomag, signal noise, MUF) | 1 hr |
| (weather) | Weather is fetched directly from Open-Meteo by each user's browser — no server endpoint needed | — |
| GET /api/dxcluster/spots | Current DX cluster spots (array of spot objects) | 5 sec |
| GET /api/dxcluster/paths | DX spots with resolved coordinates for map display | 5 sec |
| GET /api/dxcluster/sources | Available DX cluster source backends | — |
| GET /api/solar-indices | SFI, Kp, SSN with 30-day history arrays | 15 min |
| GET /api/noaa/flux | Raw 10.7 cm solar flux from NOAA | 15 min |
| GET /api/noaa/kindex | Raw planetary K-index from NOAA | 15 min |
| GET /api/noaa/sunspots | Raw sunspot number from NOAA | 15 min |
| GET /api/noaa/xray | GOES X-ray flux (6-hour dataset) | 15 min |
| GET /api/noaa/aurora | Aurora oval boundary data from OVATION model | 15 min |
| GET /api/hamqsl/conditions | HamQSL band conditions XML (parsed to JSON) | 30 min |
| GET /api/propagation | HF propagation predictions (per-band reliability %) | 10 min |
| GET /api/pota/spots | POTA activator spots from api.pota.app | 1 min |
| GET /api/wwff/spots | WWFF activator spots from spots.wwff.co | 90 sec |
| GET /api/sota/spots | SOTA activator spots from api2.sota.org.uk | 2 min |
| GET /api/satellites/tle | Satellite TLE data from CelesTrak | 6 hr |
| GET /api/contests | Contest calendar from contestcalendar.com | 30 min |
| GET /api/dxpeditions | Active DXpeditions from NG3K | 30 min |
| GET /api/dxnews | DX news headlines from DXNews.com | 30 min |
| GET /api/callsign/:call | Callsign lookup (DXCC entity, grid, country, continent) | — |
| GET /api/myspots/:callsign | Recent spots of a specific callsign | 30 sec |
| GET /api/ionosonde | Ionospheric sounding data from prop.kc2g.com | 5 min |
| GET /api/pskreporter/config | PSKReporter MQTT connection configuration | — |
| GET /api/pskreporter/http/:call | PSKReporter HTTP API fallback | 2 min |
| GET /api/wsjtx | WSJT-X connection status and active client list | — |
| GET /api/wsjtx/decodes | WSJT-X decoded messages (latest batch) | — |
| POST /api/wsjtx/relay | WSJT-X relay agent data ingest endpoint | — |
| GET /api/qrz/lookup/:callsign | QRZ.com callsign lookup | — |
OpenHamClock exposes a prometheus compatible endpoint at /metrics. By default it is open without authentication, but you can always set the METRICS_AUTH_KEY env var which serves as bearer token authentication. See .env.example for details.
Q: Do I need an amateur radio license to use OpenHamClock? A: No. OpenHamClock is a receive-only dashboard. Anyone can view DX spots, space weather, and POTA/WWFF activations. However, a callsign is needed for PSKReporter data (which tracks your transmitted signals) and for DX cluster login.
Q: How much bandwidth does OpenHamClock use? A: Very little. All external API calls are cached server-side, and the backend serves compressed (gzip) responses. Typical usage is under 1 MB/minute. Most data sources update every 5–30 minutes.
Q: Can I run this on a Raspberry Pi 3? A: Yes. The Pi 3B/3B+ works fine, though initial build time is slower (~5 minutes). A Pi 4 or 5 is recommended for the best experience. The server uses about 100–150 MB of RAM.
Q: Why don't I see any DX spots? A: Check that: (1) Your callsign is set in .env — the DX Spider proxy uses it to log in. (2) Your internet connection is working. (3) If using a custom DXSPIDER_PROXY_URL, make sure it's reachable. Check the server console for error messages.
Q: Why does PSKReporter show no data? A: PSKReporter requires your callsign to be set correctly. If MQTT fails (some corporate firewalls block WebSocket connections), the system falls back to the HTTP API automatically. Check the panel footer to see which connection method is active.
Q: Can multiple people use the same server? A: Yes. The web interface is stateless — each browser session gets its own filter settings, theme preferences, and DX target. The server caches all API responses, so additional users add zero extra load on upstream services. For shared stations where operators want different layouts and configurations, use the Profiles feature (Settings → Profiles tab) to save and switch between named profiles.
Q: How do I change the DX cluster source? A: Open Settings → Station tab → DX Cluster Source dropdown. Or set dxClusterSource in the browser settings. The four options are: DX Spider Proxy (recommended), HamQTH, DXWatch, and Auto.
Q: Can I use this with JTDX instead of WSJT-X? A: Yes. JTDX uses the same UDP protocol as WSJT-X. Configure JTDX's UDP settings the same way (address: 127.0.0.1, port: 2237).
Q: Why are some POTA spots not on the map? A: A spot will appear in the panel list but not on the map if its coordinates couldn't be resolved. This is rare — the POTA API provides coordinates for almost all parks, and OpenHamClock has a grid-square fallback for the rest.
Q: How do I get the Classic layout to look like the original HamClock? A: Set LAYOUT=classic in .env (or select it in Settings). The Classic layout uses a black background with large colored number displays, matching the style of the original HamClock by WB0OEW.
Q: I can't find the .env file — where is it? A: The .env file is in the root OpenHamClock directory (same folder as server.js). Files starting with . are hidden by default — use ls -la in a terminal to see it, or Ctrl+H in a Linux file manager. If it doesn't exist yet, run npm start once (it's auto-created from .env.example) or copy it manually: cp .env.example .env. Most settings can also be changed through the browser Settings panel without editing .env at all.
Q: Icons and emoji are showing as boxes or missing on Linux / Raspberry Pi — how do I fix it? A: OpenHamClock uses emoji for icons throughout the interface (weather symbols, band indicators, satellite markers, and more). On Linux, emoji rendering depends on a color emoji font being installed on the machine running the browser — not the server. Install the Noto Color Emoji font to fix it:
sudo apt install fonts-noto-color-emojiThen restart your browser (or reboot if running in kiosk mode). The Raspberry Pi setup script now installs this automatically. On Windows and macOS, emoji fonts are bundled with the OS and no action is needed.
Q: Chromium shows a "keyring" unlock prompt on every boot in kiosk mode — how do I prevent it? A: This happens when Chromium tries to use the system keyring (gnome-keyring / kwallet) to protect its internal credential store, but the desktop session manager hasn't unlocked it yet. The Pi setup script already passes --password-store=basic to Chromium, which tells it to use a plain local store instead and avoids the prompt entirely. If you installed OpenHamClock before this fix was included, update your kiosk.sh by re-running the setup script, or add --password-store=basic manually to the Chromium launch line in ~/openhamclock/kiosk.sh.
OpenHamClock is built by the ham radio community. We have 28+ contributors and growing — whether it's a bug fix, a new panel, a map layer plugin, or better docs, PRs are welcome.
Getting started:
git clone https://github.com/accius/openhamclock.git
cd openhamclock
git checkout Staging
npm ci
node server.js # Terminal 1 — Backend on :3001
npm run dev # Terminal 2 — Frontend on :3000Open pull requests against Staging, not main.
Read first:
Community:
Thank you to everyone who has contributed code, features, bug fixes, and ideas:
creinemann · ceotjoe · alanhargreaves · dmazan · Delerius · rfreedman · SebFox2011 · infopcgood · thomas-schreck · echo-gravitas · yuryja · Holyszewski · trancen · ThePangel · w8mej · JoshuaNewport · denete · kmanwar89 · KentenRoth · s53zo · theodeurne76 · m1dst · brianbruff · agocs · kwirk · Oukagen · ftl · phether
MIT License — See LICENSE file.
73 de K0CJH openhamclock.com · chris@cjhlighting.com
| Back | FazBrowse Home | New Git URL |