Files
reflector-dashboard/INSTALL.md
T

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.