9.8 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 |
| A running SvxReflector | we use GeuReflector (SvxReflector fork with trunking); install/svxreflector.conf.example is for that build |
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.sockredis.password-> the value from step 3base_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 reflector's HTTP_SRV_PORT status endpoint
(GeuReflector serves /status there)
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.logandjournalctl -u php8.4-fpm. Usually Redis auth or missingphp-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 checkjournalctl -u php8.4-fpmforsmtp:lines. - Login works but is lost on next page: site not on HTTPS (cookie is
Secure) orbase_urlwrong. - Admin API says "exec disabled":
admin_reflector_api.phpwas served by the default pool. Check the nginxlocation =block and thatphp8.4-fpm-svxadmin.sockexists.