Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c049778
Focus site on Oregon: index.html = Oregon table, abbreviation nav, pl…
mousebrains Mar 6, 2026
f014f78
Narrow nav to adjacent states (WA, ID, NV, CA) and remove All Reaches…
mousebrains Mar 6, 2026
233c217
Add state links pages with curated resources for OR, WA, ID, NV, CA
mousebrains Mar 6, 2026
13d70f8
Add robots.txt to public_html
mousebrains Mar 6, 2026
5574e83
Merge main into oregon_only (resolve robots.txt conflict)
mousebrains Mar 6, 2026
ecb6c6e
Add NWRFC textplot parser, enhance reach search, improve merge algorithm
mousebrains Mar 6, 2026
4022b0e
Track dreamflows_cache.json in version control
mousebrains Mar 6, 2026
eeff7c1
Dual-axis plots, parser fixes, systemd path updates, and misc improve…
mousebrains Mar 12, 2026
f3e9fcd
Fix pipeline timeout: skip single-source gauges in merge step
mousebrains Mar 13, 2026
450da08
Sticky header, letter nav, clickable rows, and minor HTML fixes
mousebrains Mar 24, 2026
50a88c7
Add inflow sparklines, SRI hashes for Leaflet, and privacy policy link
mousebrains Mar 24, 2026
d92a8f6
Add inflow plot type and SRI hashes to description page
mousebrains Mar 24, 2026
c43f37d
Security hardening: auth, CSRF, rate limiting, session cookies, priva…
mousebrains Mar 24, 2026
2fa225d
Add script to fetch reach geometry from AW vector tiles
mousebrains Mar 24, 2026
78a84c8
Add PHP style.css, update header CSS path, block edit.php in robots.txt
mousebrains Mar 24, 2026
cb59b8b
Tighten CSP (object-src, base-uri) and remove Server header
mousebrains Mar 24, 2026
93e59b6
Atomic writes
mousebrains Mar 24, 2026
6892521
Fix atomic write file permissions (0o600 → 0o644)
mousebrains Mar 27, 2026
49f2ed5
Move inline scripts to external levels.js for CSP compliance
mousebrains Mar 27, 2026
9410d35
Move inline map scripts to external JS files for CSP compliance
mousebrains Apr 5, 2026
ed8ba31
Extract remaining inline scripts, add sparklines and clickable rows t…
mousebrains Apr 5, 2026
9228d40
Add cache-bust query string to picker.js
mousebrains Apr 5, 2026
a3b7f0f
Fix OOM on custom page with many reaches
mousebrains Apr 5, 2026
b5b5ee0
Compact flow levels table in reach.php, update reaches GeoJSON
mousebrains Apr 5, 2026
9e4c94d
Add river traces to search map, connect markers to traces
mousebrains Apr 6, 2026
360dd98
Remove reaches.geojson from repo, add to .gitignore
mousebrains Apr 6, 2026
2e6ea36
Reduce DB lock contention and harden pipeline steps
mousebrains Apr 8, 2026
c027104
Add no_show review UI and enhance source/reach PHP pages
mousebrains Apr 8, 2026
4d14708
Migrate gauge metadata to SQLite cache, disable bulk USGS fetches
mousebrains Apr 8, 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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ obj
*.o
*.d
tpw.master.2*
tkw.*
tpw.*

# Python
__pycache__/
Expand All @@ -23,3 +25,8 @@ build/
public_html/*.html
public_html/*.csv
public_html/*.text
static/reaches.geojson

# OSM data cache
OSM-cache/

120 changes: 120 additions & 0 deletions conf/levels.nginx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Redirect HTTP → HTTPS
server {
listen 80;
listen [::]:80;
server_name levels.mousebrains.com levels-test.wkcc.org; # levels.wkcc.org;
return 301 https://$host$request_uri;
}

server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
http2 on;

ssl_certificate /etc/letsencrypt/live/levels.mousebrains.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/levels.mousebrains.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

server_name levels.mousebrains.com levels-test.wkcc.org; # levels.wkcc.org;

# Hide server identity completely (requires libnginx-mod-http-headers-more-filter)
server_tokens off;
more_clear_headers Server;

# Security headers (server-level — apply when no location overrides)
include /etc/nginx/snippets/security-headers.conf;

# Document root — where build output + PHP symlinks live
root /home/pat/public_html;
index index.html;

# Request size limit — no file uploads expected
client_max_body_size 16k;

# Gzip — big win for the inline-SVG HTML pages
gzip on;
gzip_types text/plain text/css text/csv
application/json application/javascript
image/svg+xml;
gzip_min_length 256;
gzip_vary on;

# Block known malicious scanners and exploit tools
if ($http_user_agent ~* (zgrab|masscan|scanner/1\.0|libredtail|xfa1|Gh0st|^Hello|nvdorz|FreePBX-Scanner)) {
return 444;
}

# Block dotfiles (.git, .env, .htaccess, etc.)
location ~ /\. {
deny all;
return 404;
}

# Block access to PHP includes (db.php, svg_plot.php, etc.)
# Use regex to override the \.php$ location (regex precedence rules)
location ~ ^/includes/ {
deny all;
return 404;
}

# Static assets — long cache, immutable content
location /static/ {
expires 7d;
add_header Cache-Control "public, immutable";
include /etc/nginx/snippets/security-headers.conf;
}

# Generated HTML/CSV/text pages — short cache, regenerated hourly
location / {
try_files $uri $uri/ =404;
expires 5m;
add_header Cache-Control "public";
include /etc/nginx/snippets/security-headers.conf;
}

# PHP endpoints via php-fpm
location ~ \.php$ {
limit_req zone=php burst=20 nodelay;
try_files $uri =404;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

# Pass environment to PHP
fastcgi_param SQLITE_PATH /home/pat/DB/kayak.db;

# Cache PHP responses (5 min for reads, no cache for edits)
add_header Cache-Control "public, max-age=300";
include /etc/nginx/snippets/security-headers.conf;
}

# Edit endpoint — rate-limited, no cache
location = /edit.php {
limit_req zone=edit burst=2 nodelay;
try_files $uri =404;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param SQLITE_PATH /home/pat/DB/kayak.db;
include /etc/nginx/snippets/edit-password.conf;
add_header Cache-Control "no-cache, no-store";
include /etc/nginx/snippets/security-headers.conf;
}

# Let's Encrypt ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

# Custom error page
error_page 404 /404.html;
location = /404.html {
internal;
}

# Logging
access_log /var/log/nginx/kayak-access.log;
error_log /var/log/nginx/kayak-error.log;
}
8 changes: 8 additions & 0 deletions conf/security-headers.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security headers — included in every location block that uses add_header,
# because nginx's add_header in a child block replaces (not extends) the parent.
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://unpkg.com; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https://*.tile.opentopomap.org https://tile.openstreetmap.org https://*.tile.openstreetmap.org https://server.arcgisonline.com https://unpkg.com; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
Loading