Files
reflector-dashboard/INSTALL.md
T

9.6 KiB

SvxPortal web — installation HOWTO

This is the web front-end only for a SvxReflector: node map, monitor, last-heard, talkgroup list, user accounts and the reflector admin pages. It is plain HTML/JavaScript plus a small PHP backend that stores accounts in Redis. No build step, no framework, no composer.

What you need on the server:

Component Why
nginx serves the static files and passes *.php to PHP-FPM
PHP 8.2+ with FPM the auth/ backend (login, accounts, admin API)
PHP extensions: redis, mbstring, openssl, json, session used by auth/lib.php
Redis 6+ stores accounts, sessions, bans, talkgroup requests
A way to send e-mail signup verification and password reset (local MTA or an SMTP relay, see step 5)
TLS certificate the session cookie is Secure; login does not work over plain HTTP

Everything below assumes Debian 12/13 or Ubuntu 24.04, PHP 8.4 and that the site will live at https://svxportal.example.com. Replace the host name everywhere you see it.


1. Packages

sudo apt install nginx php8.4-fpm php8.4-redis php8.4-mbstring php8.4-cli redis-server certbot

Check the PHP version you actually got (php -v) and use that number instead of 8.4 in the file names below.

2. Copy the web files

sudo mkdir -p /var/www/html/svxportal
sudo rsync -a --exclude .git --exclude install --exclude INSTALL.md ./ /var/www/html/svxportal/
sudo chown -R www-data:www-data /var/www/html/svxportal
sudo find /var/www/html/svxportal -type d -exec chmod 2775 {} +
sudo find /var/www/html/svxportal -type f -exec chmod 0644 {} +

3. Redis (dedicated instance, unix socket)

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 /etc/redis/redis-svxportal.conf
sudo chmod 640 /etc/redis/redis-svxportal.conf

Generate a password and put it in the config:

openssl rand -hex 32
sudo sed -i "s/^requirepass .*/requirepass PASTE-THE-VALUE-HERE/" /etc/redis/redis-svxportal.conf
sudo systemctl enable --now redis-server@svxportal

The socket is created with mode 770 and group redis, so the web user must be in that group:

sudo usermod -aG redis www-data

Test:

redis-cli -s /run/redis-svxportal/redis.sock -a 'PASTE-THE-VALUE-HERE' ping   # -> PONG

4. PHP-FPM

Two pools are used. The default pool (www) handles all normal pages. A second small pool handles only auth/admin_reflector_api.php, because that script calls exec() to reach the sudo helpers (step 9) and you do not want exec() enabled for the whole site.

sudo cp install/php-fpm-svxadmin.conf /etc/php/8.4/fpm/pool.d/svxadmin.conf

In the default pool hardening is recommended. Edit /etc/php/8.4/fpm/pool.d/www.conf and add at the end:

php_admin_value[disable_functions] = exec,passthru,shell_exec,system,popen,proc_open,proc_close,pcntl_exec,dl,phpinfo,show_source
php_admin_value[expose_php] = Off

Then:

sudo systemctl restart php8.4-fpm
ls -l /run/php/   # both php8.4-fpm.sock and php8.4-fpm-svxadmin.sock must exist

5. Web app config (auth/config.php)

cd /var/www/html/svxportal
sudo -u www-data cp auth/config.php.example auth/config.php
sudo chmod 640 auth/config.php
sudoedit auth/config.php

Set:

  • redis.unix_socket -> /run/redis-svxportal/redis.sock
  • redis.password -> the value from step 3
  • base_url -> https://svxportal.example.com (used in the e-mail links)
  • mail.from -> the sender address for verification / reset mails

E-mail: with only mail.from set, PHP's mail() is used, which needs a working local MTA (/usr/sbin/sendmail, e.g. msmtp-mta or postfix). If you do not have one, add an SMTP relay instead and no MTA is needed:

'mail' => [
  'from'      => 'no-reply@example.com',
  'from_name' => 'SvxPortal',
  'host'      => 'smtp.example.com',   // SSL SMTP (implicit TLS)
  'port'      => 465,
  'user'      => 'smtp-user',
  'pass'      => 'smtp-password',
],

config.php is listed in .gitignore and is never served by nginx (all *.php go to PHP-FPM, which just executes return [...]). Keep it mode 640.

6. Talkgroup list file

The talkgroup list is a JSON file the portal reads and writes:

sudo cp install/tmpfiles-svxportal-tglist.conf /etc/tmpfiles.d/svxportal-tglist.conf
sudo systemd-tmpfiles --create /etc/tmpfiles.d/svxportal-tglist.conf

This creates /etc/svxlink/tglist.json owned by www-data. The path is tglist_file in config.php; change both if you want it elsewhere.

7. nginx + TLS

sudo mkdir -p /var/www/letsencrypt
sudo cp install/nginx-svxportal.conf /etc/nginx/sites-available/svxportal
sudo sed -i 's/svxportal\.example\.com/YOUR.HOST.NAME/g' /etc/nginx/sites-available/svxportal

Get a certificate first (the config references it):

sudo certbot certonly --webroot -w /var/www/letsencrypt -d YOUR.HOST.NAME

If certbot has not created /etc/letsencrypt/options-ssl-nginx.conf and ssl-dhparams.pem, run sudo certbot --nginx once or remove those two lines.

sudo ln -s /etc/nginx/sites-available/svxportal /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Open https://YOUR.HOST.NAME/ — the page should render. Click Sign in and you should get a proper "wrong password" style error, not a 500. A 500 here almost always means Redis (wrong socket/password, or www-data not in group redis).

Also edit these files in the web root for your own site name: robots.txt, sitemap.xml and the <title>/meta tags at the top of index.html.

8. First admin account

Accounts normally self-register and verify by e-mail. The first admin is created from the command line with the bundled script; it reuses the portal's own library, so it writes exactly what signup.php would:

cd /var/www/html/svxportal
sudo -u www-data php svx-mkadmin.php

Answer the prompts (callsign, e-mail, password, admin level). Levels:

Level Meaning
1 normal user
2 admin: accounts, nodes, certificates, talkgroups
3 super admin: may also see reflector/MQTT secrets and manage other admins

Log in on the web page with that callsign. The Admin menu appears.

9. Live data API — what this repository does NOT contain

The dashboard (node list, map, monitor, last-heard, trunks) fetches JSON and Server-Sent Events from /svxportal/api/..., which nginx proxies to 127.0.0.1:8098. That backend service is not included here. Without it the account/admin/talkgroup pages work, but the live node views stay empty (the page shows a connection error banner).

Endpoints the UI expects:

Path Used by
/svxportal/api/status node table, topology
/svxportal/api/stream live updates (SSE)
/svxportal/api/lastheard last-heard list
/svxportal/api/monitor-nodes monitor view
/svxportal/api/local-trunks trunks view

You need to provide something that serves these, for example a small service that reads your svxreflector's HTTP_SRV_PORT status endpoint and republishes it. The static reflector status page in help.html describes the fields.

10. Reflector admin features (optional, need sudo helpers)

The pages Admin → Reflector, Certificates, Node control and MQTT talk to the running svxreflector on the same machine. They do it through small root-owned helper scripts called with sudo from auth/admin_reflector_api.php, auth/node_cmd_api.php and auth/node_auth_api.php:

Helper (expected in /usr/local/sbin/) Purpose
svxreflector-pty writes NODE BLOCK/UNBLOCK and CA commands to the reflector's COMMAND_PTY
svxreflector-systemctl restart/reload the svxreflector service
svxreflector-users add/remove [USERS]/[PASSWORDS] entries in svxreflector.conf
svx-mqtt-provision create/remove per-node MQTT accounts

Those helpers and their /etc/sudoers.d/ rules are not part of this repository; they are specific to how each reflector box is set up. Write your own for your environment (validate the callsign argument strictly, allow www-data to run only that one script with NOPASSWD), or skip this: all other pages work without them and the admin API returns a clear error when a helper is missing.

The certificate pages additionally read /etc/svxlink/svxreflector.conf to find CERT_PKI_DIR and watch pending_csrs/ in it, so www-data needs read access to the conf and the PKI directories (never to the private keys).

install/svxreflector.conf.example shows a complete reflector config as used with this portal: PKI/CA paths, COMMAND_PTY, HTTP_SRV_PORT, the [USERS]/[PASSWORDS] layout the helper edits, MQTT, and trunk/satellite sections. All secrets and hostnames in it are placeholders.

11. Checklist when something does not work

  • 500 on any auth/*.php: sudo tail -f /var/log/nginx/svxportal-error.log and journalctl -u php8.4-fpm. Usually Redis auth or missing php-redis.
  • "Not connected" banner, empty node list: no backend on 127.0.0.1:8098 (step 9).
  • No verification e-mail: mail() without an MTA silently fails. Configure the SMTP relay (step 5) and check journalctl -u php8.4-fpm for smtp: lines.
  • Login works but is lost on next page: site not on HTTPS (cookie is Secure) or base_url wrong.
  • Admin API says "exec disabled": admin_reflector_api.php was served by the default pool. Check the nginx location = block and that php8.4-fpm-svxadmin.sock exists.