Public web-only release: SvxPortal front-end + auth backend, install templates and INSTALL.md
Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr), deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles live in install/ with CHANGEME placeholders. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# SvxPortal web — nginx server block.
|
||||
# Install: sudo cp install/nginx-svxportal.conf /etc/nginx/sites-available/svxportal
|
||||
# sudo ln -s /etc/nginx/sites-available/svxportal /etc/nginx/sites-enabled/
|
||||
# sudo nginx -t && sudo systemctl reload nginx
|
||||
# Replace: svxportal.example.com, the certificate paths, and the PHP version in the socket names.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name svxportal.example.com;
|
||||
|
||||
# Let's Encrypt HTTP challenge (certbot --webroot -w /var/www/letsencrypt)
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
default_type "text/plain";
|
||||
root /var/www/letsencrypt;
|
||||
allow all;
|
||||
}
|
||||
location / { return 301 https://$host$request_uri; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name svxportal.example.com;
|
||||
|
||||
root /var/www/html/svxportal;
|
||||
index index.html index.php;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/svxportal.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/svxportal.example.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
access_log /var/log/nginx/svxportal-access.log;
|
||||
error_log /var/log/nginx/svxportal-error.log;
|
||||
|
||||
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;
|
||||
# The UI loads Bootstrap/icons from cdn.jsdelivr.net; everything else is same-origin.
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; object-src 'none'" always;
|
||||
|
||||
location = /robots.txt { default_type text/plain; alias /var/www/html/svxportal/robots.txt; }
|
||||
location = /sitemap.xml { default_type application/xml; alias /var/www/html/svxportal/sitemap.xml; }
|
||||
location = /privacy { default_type text/plain; alias /var/www/html/svxportal/privacy; }
|
||||
|
||||
# Reflector admin API runs in its own PHP-FPM pool (see install/php-fpm-svxadmin.conf).
|
||||
# It needs exec() for the sudo helpers; the normal pool should keep exec disabled.
|
||||
location = /auth/admin_reflector_api.php {
|
||||
include /etc/nginx/fastcgi.conf;
|
||||
try_files $uri =404;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm-svxadmin.sock;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_read_timeout 30;
|
||||
}
|
||||
|
||||
# Never serve dotfiles (.git, .gitignore ...) or editor/backup files.
|
||||
location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off; }
|
||||
location ~* \.(bak|orig|save|old|swp|inc|sql|dist|tpl)(\.[0-9]+)?$ { deny all; access_log off; log_not_found off; }
|
||||
|
||||
# Server-Sent Events endpoint: must not be buffered.
|
||||
location = /auth/events.php {
|
||||
include /etc/nginx/fastcgi.conf;
|
||||
try_files $uri =404;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||
fastcgi_buffering off;
|
||||
fastcgi_read_timeout 3600;
|
||||
gzip off;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include /etc/nginx/fastcgi.conf;
|
||||
try_files $uri =404;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_read_timeout 25;
|
||||
}
|
||||
|
||||
location = / { try_files /index.html =404; }
|
||||
location / { try_files $uri $uri/ =404; }
|
||||
|
||||
# Live data API (/svxportal/api/status, /stream, /lastheard, /monitor-nodes,
|
||||
# /local-trunks). The web UI expects a JSON/SSE backend here on 127.0.0.1:8098.
|
||||
# That backend is NOT part of this repository — see INSTALL.md, section 8.
|
||||
location ^~ /svxportal/api/ {
|
||||
proxy_pass http://127.0.0.1:8098/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 3600s;
|
||||
gzip off;
|
||||
add_header X-Accel-Buffering no;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
; Separate PHP-FPM pool for /auth/admin_reflector_api.php.
|
||||
; Install: sudo cp install/php-fpm-svxadmin.conf /etc/php/8.4/fpm/pool.d/svxadmin.conf
|
||||
; sudo systemctl restart php8.4-fpm
|
||||
; Adjust "8.4" to your PHP version (also in the nginx config).
|
||||
[svxadmin]
|
||||
user = www-data
|
||||
group = www-data
|
||||
listen = /run/php/php8.4-fpm-svxadmin.sock
|
||||
listen.owner = www-data
|
||||
listen.group = www-data
|
||||
pm = ondemand
|
||||
pm.max_children = 4
|
||||
pm.process_idle_timeout = 30s
|
||||
request_terminate_timeout = 300
|
||||
|
||||
; exec() stays available for the scoped sudo helpers used by admin_reflector_api.php;
|
||||
; everything else that spawns processes is disabled.
|
||||
php_admin_value[disable_functions] = passthru,shell_exec,system,popen,proc_open,proc_close,pcntl_exec,dl,phpinfo,show_source
|
||||
@@ -0,0 +1,21 @@
|
||||
# Dedicated Redis instance for SvxPortal (unix socket only, no TCP port).
|
||||
# Install: sudo cp install/redis-svxportal.conf /etc/redis/redis-svxportal.conf
|
||||
# sudo mkdir -p /var/lib/redis-svxportal && sudo chown redis:redis /var/lib/redis-svxportal
|
||||
# sudo systemctl enable --now redis-server@svxportal
|
||||
# Debian/Ubuntu ship the redis-server@.service template; the instance name maps to
|
||||
# /etc/redis/redis-<name>.conf. Replace CHANGEME with a long random password and put
|
||||
# the same value in auth/config.php (redis.password).
|
||||
port 0
|
||||
unixsocket /run/redis-svxportal/redis.sock
|
||||
unixsocketperm 770
|
||||
daemonize no
|
||||
supervised systemd
|
||||
pidfile /run/redis-svxportal/redis-server.pid
|
||||
logfile /var/log/redis/redis-svxportal.log
|
||||
dir /var/lib/redis-svxportal
|
||||
requirepass CHANGEME
|
||||
maxmemory 512mb
|
||||
maxmemory-policy noeviction
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
@@ -0,0 +1,5 @@
|
||||
# Canonical talkgroup list JSON, owned by the web user so the portal can save it.
|
||||
# Install: sudo cp install/tmpfiles-svxportal-tglist.conf /etc/tmpfiles.d/svxportal-tglist.conf
|
||||
# sudo systemd-tmpfiles --create /etc/tmpfiles.d/svxportal-tglist.conf
|
||||
# 'f' only writes the seed when the file is missing; existing data is kept.
|
||||
f /etc/svxlink/tglist.json 0664 www-data www-data - {"tgs":[],"requests":[]}
|
||||
Reference in New Issue
Block a user