257 lines
9.6 KiB
Markdown
257 lines
9.6 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
sudo usermod -aG redis www-data
|
|
```
|
|
|
|
Test:
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```ini
|
|
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:
|
|
|
|
```bash
|
|
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`)
|
|
|
|
```bash
|
|
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:
|
|
|
|
```php
|
|
'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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|