Merge branch 'main' into 95-send-spots-to-xota

# Conflicts:
#	README.md
#	config-example.yml
#	core/config.py
#	server/handlers/api/addspot.py
#	server/handlers/api/options.py
#	static/js/add-spot.js
#	static/js/bands.js
#	static/js/map.js
This commit is contained in:
Ian Renton
2026-08-02 07:47:40 +01:00
509 changed files with 1072 additions and 609 deletions
+45 -60
View File
@@ -1,4 +1,4 @@
# ![Spothole](/webassets/img/logo.png)
# ![Spothole](/static/img/logo.png)
Spothole is a utility to aggregate "spots" from amateur radio DX clusters and xOTA spotting sites, and provide an open
JSON API as well as a website to browse the data.
@@ -89,15 +89,15 @@ callsign and park data.
Various approaches exist to writing your own client, but in general:
* Refer to the API docs. These are built on an OpenAPI definition file (`/webassets/apidocs/openapi.yml`), which you can
* Refer to the API docs. These are built on an OpenAPI definition file (`/static/apidocs/openapi.yml`), which you can
automatically use to generate a client skeleton using various software.
* Call the main "spots" or "alerts" API endpoints to get the data you want. For example, your app could call
`https://spothole.app/api/v2/spots` once every few minutes. Apply filters if necessary.
* Call the "options" API to get an idea of which bands, modes etc. the server knows about. You might want to do that
first before calling the spots/alerts APIs, to allow you to populate your filters correctly.
* Refer to the provided HTML/JS interface for a reference on different approaches. For example, the "map" and "bands"
pages simply query the main spot API on a timer, whereas the main/spots page combines this approach with using the
Server-Sent Events (SSE) endpoint to update live.
* Refer to the provided HTML/JS interface for a reference on different approaches. For example, the "alerts"/"upcoming"
page simply query the main spot API on a timer, whereas the spots, map and bands pages combine this approach with
using the Server-Sent Events (SSE) endpoint to update live.
* Let me know if you get stuck, I'm happy to help.
Please don't hammer the API with an unnecessarily high request rate. For example, Spothole only queries the POTA API
@@ -318,93 +318,78 @@ To set up nginx as a reverse proxy that sits in front of Spothole, first ensure
Create a file at `/etc/nginx/sites-available/` called `spothole`. Give it the following contents, replacing
`spothole.app` with the domain name on which you want to run Spothole. If you changed the port on which Spothole runs,
update that on the "proxy_pass" line too.
update that on the "proxy_pass" line, and if you installed Spothole somewhere other than `/home/spothole/spothole`,
adjust the alias location for serving static files.
(The latter section, configuring the nginx server to serve static files directly, improves efficiency because it saves
Spothole itself from serving JS, CSS etc. files. If you can't do this for some reason, e.g. your nginx and spothole are
on different computers, you can omit the `location /static/ {}` block.)
```nginx
server {
server_name spothole.app;
# Global proxy settings
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 10s;
proxy_buffering on;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
# Wellknown area for Lets Encrypt
location /.well-known/ {
alias /var/www/html/.well-known/;
}
# Load static assets directly from the Spothole static directory
location /static/ {
alias /home/spothole/spothole/static/;
expires 1h;
add_header Cache-Control "public, max-age=3600, must-revalidate";
}
# SSE endpoints
location ~ ^/api/v2/(spots|alerts)/stream {
location ~ ^/api/v2/(spots|alerts)/stream/? {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set correct content type for SSE API calls
add_header Content-Type text/event-stream always;
# Set remove buffering, remove caching, add suitable timeouts for SSE API calls
# Remove buffering, remove caching, add suitable timeouts for SSE API calls
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
proxy_connect_timeout 10s;
proxy_send_timeout 24h;
proxy_set_header X-Accel-Buffering no;
add_header Cache-Control no-store always;
# Allow cross-origin requests to API
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin * always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Access-Control-Allow-Origin * always;
}
# Other API endpoints
location /api/ {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set up buffering, remove caching, add suitable timeouts for API calls
proxy_buffering on;
# Remove buffering, remove caching, add suitable timeouts for API calls
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
add_header Cache-Control no-store always;
# Allow cross-origin requests to API
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin * always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Static assets
# Templated pages
location / {
proxy_pass http://127.0.0.1:8080;
# Allow keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set up buffering and caching, add suitable timeouts for static asset requests
proxy_buffering on;
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
add_header Cache-Control "public, max-age=3600, must-revalidate" always;
# Pass on IP address and host information to Spothole, in case logging this information is required
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Cache-Control "no-cache, must-revalidate" always;
}
}
```
@@ -467,12 +452,12 @@ To navigate your way around the source code, this list may help.
*HTML/JS/CSS front-end code*
* `/webassets` - Root for static files served by the web server
* `/webassets/apidocs` - Contains the OpenAPI spec (`openapi.yml`)
* `/webassets/css` - CSS files used by the web front-end
* `/webassets/img` - image files used by the web front-end
* `/webassets/js` - JavaScript used by the web front-end
* `/webassets/vendor` - Third-party libraries (CSS, JS, fonts and images)
* `/static` - Root for static files served by the web server. These are all served from a path starting `/static/`.
* `/static/apidocs` - Contains the OpenAPI spec (`openapi.yml`)
* `/static/css` - CSS files used by the web front-end
* `/static/img` - image files used by the web front-end
* `/static/js` - JavaScript used by the web front-end
* `/static/vendor` - Third-party libraries (CSS, JS, fonts and images)
*Miscellaneous*
@@ -524,14 +509,14 @@ my knowledge, created by HA8TKS for his CQ and ITU zone layers for Leaflet. `/da
maps of conference centres.
The project contains a set of flag icons generated using the "Noto Color Emoji" font on a Debian system, in the
`/webassets/img/flags/` directory.
`/static/img/flags/` directory.
The software uses a number of Python libraries as listed in `requirements.txt`, and a number of JavaScript libraries.
This project would not have been possible without these libraries, so many thanks to their developers.
### Third Party Libraries
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to
A number of third-party libraries are self-hosted in the `/static/vendor/` directory. These files are subject to
their own licences and are not covered by the overall licence declared in the `LICENSE` file.
A number of third-party libraries are self-hosted in the `/webassets/vendor/` directory. These files are subject to