vhost-manager: add CLI helper and docs
This commit is contained in:
parent
8134e6b852
commit
baab096876
@ -213,6 +213,11 @@ Follow this template when creating or revising documentation:
|
||||
|
||||
Pointer: see `docs/embedded/docker-zigbee2mqtt.md` for the canonical version.
|
||||
|
||||
#### **embedded/vhost-manager.md** 🌐
|
||||
*How to publish services through nginx using the vhost manager and CLI helper.*
|
||||
|
||||
Pointer: see `docs/embedded/vhost-manager.md` for the canonical version.
|
||||
|
||||
---
|
||||
|
||||
### 5. Tools & Scripts Documentation
|
||||
|
||||
102
DOCS/embedded/vhost-manager.md
Normal file
102
DOCS/embedded/vhost-manager.md
Normal file
@ -0,0 +1,102 @@
|
||||
# VHost Manager & Reverse Proxy Notes
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Last Updated:** 2025-12-28
|
||||
**Status:** Active
|
||||
|
||||
SecuBox ships `luci-app-vhost-manager` (LuCI dashboard + RPC backend) and now the `scripts/vhostctl.sh` helper so apps, wizards, and profiles can declaratively publish HTTP services behind nginx with optional TLS and HTTP auth.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Packages**: `luci-app-vhost-manager` installed (installs RPCD script + LuCI UI) and nginx with SSL (`nginx-ssl`).
|
||||
2. **Certificates**: ACME via `acme.sh` (auto) or manual PEM files for `tls manual`.
|
||||
3. **Apps**: Ensure the upstream service listens on localhost or LAN (e.g., Zigbee2MQTT UI on `http://127.0.0.1:8080`).
|
||||
4. **Firewall**: Allow inbound 80/443 on the WAN interface.
|
||||
|
||||
---
|
||||
|
||||
## CLI (`scripts/vhostctl.sh`)
|
||||
|
||||
This helper manipulates `/etc/config/vhosts` and can be invoked by future wizards/App Store installers.
|
||||
|
||||
```sh
|
||||
# List existing mappings
|
||||
scripts/vhostctl.sh list
|
||||
|
||||
# Add HTTPS reverse proxy for Zigbee2MQTT UI
|
||||
scripts/vhostctl.sh add \
|
||||
--domain zigbee.home.lab \
|
||||
--upstream http://127.0.0.1:8080 \
|
||||
--tls acme \
|
||||
--websocket \
|
||||
--enable
|
||||
|
||||
# Enable/disable or remove later
|
||||
scripts/vhostctl.sh disable --domain zigbee.home.lab
|
||||
scripts/vhostctl.sh remove --domain zigbee.home.lab
|
||||
|
||||
# Reload nginx after edits
|
||||
scripts/vhostctl.sh reload
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
| Option | Purpose |
|
||||
|--------|---------|
|
||||
| `--domain` | Public hostname (required). |
|
||||
| `--upstream` | Local service URL (`http://127.0.0.1:8080`). |
|
||||
| `--tls off|acme|manual` | TLS strategy. Use `manual` + `--cert/--key` for custom certs. |
|
||||
| `--auth-user/--auth-pass` | Enable HTTP basic auth. |
|
||||
| `--websocket` | Add `Upgrade` headers for WebSocket apps. |
|
||||
| `--enable` / `--disable` | Toggle without deleting. |
|
||||
|
||||
The script is idempotent: running `add` with an existing domain updates the entry.
|
||||
|
||||
---
|
||||
|
||||
## LuCI Dashboard
|
||||
|
||||
Navigate to **Services → SecuBox → VHost Manager** to:
|
||||
- View active/disabled vhosts, TLS status, certificate expirations.
|
||||
- Edit or delete entries, request ACME certificates, tail access logs.
|
||||
- Use the form to create entries (domain, upstream, TLS, auth, WebSocket).
|
||||
|
||||
The LuCI backend writes to the same `/etc/config/vhosts` file, so changes from `vhostctl.sh` appear immediately.
|
||||
|
||||
---
|
||||
|
||||
## Example: Publish Zigbee2MQTT
|
||||
|
||||
1. Install Zigbee2MQTT (Docker) and confirm the UI listens on port 8080 (see `docs/embedded/zigbee2mqtt-docker.md`).
|
||||
2. Map it behind HTTPS:
|
||||
```sh
|
||||
scripts/vhostctl.sh add \
|
||||
--domain zigbee.secubox.local \
|
||||
--upstream http://127.0.0.1:8080 \
|
||||
--tls acme \
|
||||
--websocket
|
||||
scripts/vhostctl.sh reload
|
||||
```
|
||||
3. (Optional) Use LuCI to request certificates and monitor logs.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `scripts/vhostctl.sh add ...` errors “Unknown option” | Ensure busybox `sh` is used (`/bin/sh`). |
|
||||
| ACME cert missing | Confirm `acme.sh` installed, domain resolves to router, 80/443 reachable. |
|
||||
| 502/504 errors | Check upstream service, firewall, or change `--upstream` to LAN IP. |
|
||||
| TLS manual mode fails | Provide full paths to PEM files and verify permissions. |
|
||||
| Changes not visible | Run `scripts/vhostctl.sh reload` or `ubus call luci.vhost-manager reload_nginx`. |
|
||||
|
||||
---
|
||||
|
||||
## Automation Notes
|
||||
|
||||
- Wizards/App Store can shell out to `scripts/vhostctl.sh` to register services as they are installed.
|
||||
- Profiles can keep declarative manifests (domain → upstream) and call `vhostctl.sh add/remove` when switching modes.
|
||||
- `/etc/config/vhosts` remains the single source of truth, consumed by the LuCI app and the RPC backend.
|
||||
@ -221,6 +221,19 @@ Follow this template when creating or revising documentation:
|
||||
|
||||
**Size:** Short (~100 lines)
|
||||
|
||||
#### **embedded/vhost-manager.md** 🌐
|
||||
*How to publish local services behind nginx with vhost manager + CLI helper.*
|
||||
|
||||
**Contents:**
|
||||
- Overview of `luci-app-vhost-manager` UI
|
||||
- CLI usage for `scripts/vhostctl.sh` (list/add/remove/reload)
|
||||
- Example mapping for Zigbee2MQTT
|
||||
- Troubleshooting tips (ACME, manual certs, upstream errors)
|
||||
|
||||
**When to use:** Exposing Docker/LXC apps through HTTPS or integrating profiles/wizards.
|
||||
|
||||
**Size:** Short (~120 lines)
|
||||
|
||||
---
|
||||
|
||||
### 5. Tools & Scripts Documentation
|
||||
|
||||
102
docs/embedded/vhost-manager.md
Normal file
102
docs/embedded/vhost-manager.md
Normal file
@ -0,0 +1,102 @@
|
||||
# VHost Manager & Reverse Proxy Notes
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Last Updated:** 2025-12-28
|
||||
**Status:** Active
|
||||
|
||||
SecuBox ships `luci-app-vhost-manager` (LuCI dashboard + RPC backend) and now the `scripts/vhostctl.sh` helper so apps, wizards, and profiles can declaratively publish HTTP services behind nginx with optional TLS and HTTP auth.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Packages**: `luci-app-vhost-manager` installed (installs RPCD script + LuCI UI) and nginx with SSL (`nginx-ssl`).
|
||||
2. **Certificates**: ACME via `acme.sh` (auto) or manual PEM files for `tls manual`.
|
||||
3. **Apps**: Ensure the upstream service listens on localhost or LAN (e.g., Zigbee2MQTT UI on `http://127.0.0.1:8080`).
|
||||
4. **Firewall**: Allow inbound 80/443 on the WAN interface.
|
||||
|
||||
---
|
||||
|
||||
## CLI (`scripts/vhostctl.sh`)
|
||||
|
||||
This helper manipulates `/etc/config/vhosts` and can be invoked by future wizards/App Store installers.
|
||||
|
||||
```sh
|
||||
# List existing mappings
|
||||
scripts/vhostctl.sh list
|
||||
|
||||
# Add HTTPS reverse proxy for Zigbee2MQTT UI
|
||||
scripts/vhostctl.sh add \
|
||||
--domain zigbee.home.lab \
|
||||
--upstream http://127.0.0.1:8080 \
|
||||
--tls acme \
|
||||
--websocket \
|
||||
--enable
|
||||
|
||||
# Enable/disable or remove later
|
||||
scripts/vhostctl.sh disable --domain zigbee.home.lab
|
||||
scripts/vhostctl.sh remove --domain zigbee.home.lab
|
||||
|
||||
# Reload nginx after edits
|
||||
scripts/vhostctl.sh reload
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
| Option | Purpose |
|
||||
|--------|---------|
|
||||
| `--domain` | Public hostname (required). |
|
||||
| `--upstream` | Local service URL (`http://127.0.0.1:8080`). |
|
||||
| `--tls off|acme|manual` | TLS strategy. Use `manual` + `--cert/--key` for custom certs. |
|
||||
| `--auth-user/--auth-pass` | Enable HTTP basic auth. |
|
||||
| `--websocket` | Add `Upgrade` headers for WebSocket apps. |
|
||||
| `--enable` / `--disable` | Toggle without deleting. |
|
||||
|
||||
The script is idempotent: running `add` with an existing domain updates the entry.
|
||||
|
||||
---
|
||||
|
||||
## LuCI Dashboard
|
||||
|
||||
Navigate to **Services → SecuBox → VHost Manager** to:
|
||||
- View active/disabled vhosts, TLS status, certificate expirations.
|
||||
- Edit or delete entries, request ACME certificates, tail access logs.
|
||||
- Use the form to create entries (domain, upstream, TLS, auth, WebSocket).
|
||||
|
||||
The LuCI backend writes to the same `/etc/config/vhosts` file, so changes from `vhostctl.sh` appear immediately.
|
||||
|
||||
---
|
||||
|
||||
## Example: Publish Zigbee2MQTT
|
||||
|
||||
1. Install Zigbee2MQTT (Docker) and confirm the UI listens on port 8080 (see `docs/embedded/zigbee2mqtt-docker.md`).
|
||||
2. Map it behind HTTPS:
|
||||
```sh
|
||||
scripts/vhostctl.sh add \
|
||||
--domain zigbee.secubox.local \
|
||||
--upstream http://127.0.0.1:8080 \
|
||||
--tls acme \
|
||||
--websocket
|
||||
scripts/vhostctl.sh reload
|
||||
```
|
||||
3. (Optional) Use LuCI to request certificates and monitor logs.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `scripts/vhostctl.sh add ...` errors “Unknown option” | Ensure busybox `sh` is used (`/bin/sh`). |
|
||||
| ACME cert missing | Confirm `acme.sh` installed, domain resolves to router, 80/443 reachable. |
|
||||
| 502/504 errors | Check upstream service, firewall, or change `--upstream` to LAN IP. |
|
||||
| TLS manual mode fails | Provide full paths to PEM files and verify permissions. |
|
||||
| Changes not visible | Run `scripts/vhostctl.sh reload` or `ubus call luci.vhost-manager reload_nginx`. |
|
||||
|
||||
---
|
||||
|
||||
## Automation Notes
|
||||
|
||||
- Wizards/App Store can shell out to `scripts/vhostctl.sh` to register services as they are installed.
|
||||
- Profiles can keep declarative manifests (domain → upstream) and call `vhostctl.sh add/remove` when switching modes.
|
||||
- `/etc/config/vhosts` remains the single source of truth, consumed by the LuCI app and the RPC backend.
|
||||
240
scripts/vhostctl.sh
Executable file
240
scripts/vhostctl.sh
Executable file
@ -0,0 +1,240 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox VHost helper
|
||||
# Manipulates /etc/config/vhosts from the CLI so wizards/app store can add reverse proxies.
|
||||
|
||||
set -eu
|
||||
|
||||
CONFIG="/etc/config/vhosts"
|
||||
|
||||
info() { printf '[INFO] %s\n' "$*"; }
|
||||
warn() { printf '[WARN] %s\n' "$*" >&2; }
|
||||
err() { printf '[ERROR] %s\n' "$*" >&2; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: vhostctl.sh <command> [options]
|
||||
|
||||
Commands:
|
||||
list List all configured vhosts
|
||||
show --domain example.com Show raw UCI entries for a vhost
|
||||
add --domain example.com --upstream http://127.0.0.1:8080 [options]
|
||||
remove --domain example.com Delete a vhost
|
||||
enable --domain example.com Set enabled=1
|
||||
disable --domain example.com Set enabled=0
|
||||
reload Trigger nginx reload via luci.vhost-manager
|
||||
|
||||
Options for add/show:
|
||||
--tls off|acme|manual
|
||||
--cert /path/to/fullchain.pem (required when --tls manual)
|
||||
--key /path/to/privkey.pem (required when --tls manual)
|
||||
--auth-user USER --auth-pass PASS
|
||||
--websocket Enable websocket headers
|
||||
--no-websocket Disable websocket headers
|
||||
|
||||
Examples:
|
||||
vhostctl.sh add --domain zigbee.local --upstream http://127.0.0.1:8080 --tls acme
|
||||
vhostctl.sh add --domain media.lab --upstream http://10.10.6.20:32400 --tls manual \
|
||||
--cert /etc/ssl/media.pem --key /etc/ssl/media.key
|
||||
vhostctl.sh enable --domain zigbee.local
|
||||
vhostctl.sh reload
|
||||
EOF
|
||||
}
|
||||
|
||||
ensure_config() {
|
||||
[ -f "$CONFIG" ] && return
|
||||
cat <<'EOF' >"$CONFIG"
|
||||
config global 'global'
|
||||
option enabled '1'
|
||||
option auto_reload '1'
|
||||
EOF
|
||||
}
|
||||
|
||||
sanitize_section() {
|
||||
local cleaned
|
||||
cleaned=$(echo "$1" | tr 'A-Z' 'a-z' | tr -cd 'a-z0-9_' )
|
||||
[ -z "$cleaned" ] && cleaned="vhost"
|
||||
printf 'vh_%s' "$cleaned"
|
||||
}
|
||||
|
||||
strip_quotes() {
|
||||
local val="$1"
|
||||
val="${val#\'}"
|
||||
val="${val%\'}"
|
||||
printf '%s' "$val"
|
||||
}
|
||||
|
||||
find_section() {
|
||||
local domain="$1"
|
||||
uci -q show vhosts 2>/dev/null | while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
vhosts.*.domain)
|
||||
local section="${key%.*}"
|
||||
local val
|
||||
val=$(strip_quotes "$value")
|
||||
if [ "$val" = "$domain" ]; then
|
||||
echo "${section#vhosts.}"
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
set_option() {
|
||||
local section="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
[ -n "$value" ] && uci set "vhosts.${section}.${key}=$value" || uci -q delete "vhosts.${section}.${key}"
|
||||
}
|
||||
|
||||
reload_nginx() {
|
||||
if command -v ubus >/dev/null 2>&1; then
|
||||
if ubus call luci.vhost-manager reload_nginx >/dev/null 2>&1; then
|
||||
info "nginx reloaded."
|
||||
else
|
||||
warn "Failed to reload nginx via luci.vhost-manager."
|
||||
fi
|
||||
else
|
||||
warn "ubus not available; reload nginx manually."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
ensure_config
|
||||
printf '%-30s %-30s %-6s %-6s\n' "Domain" "Upstream" "TLS" "En"
|
||||
uci -q show vhosts 2>/dev/null | grep '\.domain=' | while IFS='=' read -r key value; do
|
||||
local sname="${key%.*}"
|
||||
sname="${sname#vhosts.}"
|
||||
local domain upstream tls enabled
|
||||
domain=$(strip_quotes "$value")
|
||||
upstream=$(uci -q get "vhosts.${sname}.upstream" 2>/dev/null || printf '')
|
||||
tls=$(uci -q get "vhosts.${sname}.tls" 2>/dev/null || printf 'off')
|
||||
enabled=$(uci -q get "vhosts.${sname}.enabled" 2>/dev/null || printf '1')
|
||||
printf '%-30s %-30s %-6s %-6s\n' "$domain" "${upstream:-n/a}" "$tls" "$enabled"
|
||||
done
|
||||
}
|
||||
|
||||
cmd_show() {
|
||||
local domain="$1"
|
||||
local section
|
||||
section=$(find_section "$domain") || {
|
||||
err "Domain not found: $domain"
|
||||
exit 1
|
||||
}
|
||||
uci -q show "vhosts.$section"
|
||||
}
|
||||
|
||||
cmd_add() {
|
||||
local domain='' upstream='' tls='off' websocket='' auth_user='' auth_pass='' cert='' key=''
|
||||
local enabled='1'
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--domain) domain="$2"; shift 2 ;;
|
||||
--upstream) upstream="$2"; shift 2 ;;
|
||||
--tls) tls="$2"; shift 2 ;;
|
||||
--cert) cert="$2"; shift 2 ;;
|
||||
--key) key="$2"; shift 2 ;;
|
||||
--auth-user) auth_user="$2"; shift 2 ;;
|
||||
--auth-pass) auth_pass="$2"; shift 2 ;;
|
||||
--websocket) websocket='1'; shift ;;
|
||||
--no-websocket) websocket='0'; shift ;;
|
||||
--enable) enabled='1'; shift ;;
|
||||
--disable) enabled='0'; shift ;;
|
||||
*) err "Unknown option: $1"; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$domain" ] || { err "--domain required"; exit 1; }
|
||||
[ -n "$upstream" ] || { err "--upstream required"; exit 1; }
|
||||
if [ "$tls" = "manual" ] && { [ -z "$cert" ] || [ -z "$key" ]; }; then
|
||||
err "Manual TLS requires --cert and --key"
|
||||
exit 1
|
||||
fi
|
||||
ensure_config
|
||||
local section
|
||||
section=$(find_section "$domain" || true)
|
||||
if [ -z "$section" ]; then
|
||||
section=$(uci add vhosts vhost)
|
||||
local sanitized
|
||||
sanitized=$(sanitize_section "$domain")
|
||||
uci rename "vhosts.$section=$sanitized"
|
||||
section="$sanitized"
|
||||
fi
|
||||
set_option "$section" domain "$domain"
|
||||
set_option "$section" upstream "$upstream"
|
||||
set_option "$section" tls "$tls"
|
||||
set_option "$section" cert_path "$cert"
|
||||
set_option "$section" key_path "$key"
|
||||
set_option "$section" auth_user "$auth_user"
|
||||
set_option "$section" auth_pass "$auth_pass"
|
||||
set_option "$section" enabled "$enabled"
|
||||
[ -n "$websocket" ] && set_option "$section" websocket "$websocket"
|
||||
uci commit vhosts
|
||||
info "VHost saved for $domain → $upstream (section: $section)"
|
||||
}
|
||||
|
||||
cmd_remove() {
|
||||
local domain="$1"
|
||||
local section
|
||||
section=$(find_section "$domain") || {
|
||||
err "Domain not found: $domain"
|
||||
exit 1
|
||||
}
|
||||
uci delete "vhosts.$section"
|
||||
uci commit vhosts
|
||||
info "Removed vhost for $domain"
|
||||
}
|
||||
|
||||
cmd_toggle() {
|
||||
local domain="$1"
|
||||
local value="$2"
|
||||
local section
|
||||
section=$(find_section "$domain") || {
|
||||
err "Domain not found: $domain"
|
||||
exit 1
|
||||
}
|
||||
set_option "$section" enabled "$value"
|
||||
uci commit vhosts
|
||||
info "Set enabled=$value for $domain"
|
||||
}
|
||||
|
||||
command="${1:-help}"
|
||||
[ $# -gt 0 ] && shift
|
||||
|
||||
case "$command" in
|
||||
list) cmd_list ;;
|
||||
show)
|
||||
if [ "${1:-}" != "--domain" ] || [ -z "${2:-}" ]; then
|
||||
err "show requires --domain <name>"
|
||||
exit 1
|
||||
fi
|
||||
cmd_show "$2"
|
||||
;;
|
||||
add) cmd_add "$@" ;;
|
||||
remove)
|
||||
if [ "${1:-}" != "--domain" ] || [ -z "${2:-}" ]; then
|
||||
err "remove requires --domain <name>"
|
||||
exit 1
|
||||
fi
|
||||
cmd_remove "$2"
|
||||
;;
|
||||
enable)
|
||||
if [ "${1:-}" != "--domain" ] || [ -z "${2:-}" ]; then
|
||||
err "enable requires --domain <name>"
|
||||
exit 1
|
||||
fi
|
||||
cmd_toggle "$2" "1"
|
||||
;;
|
||||
disable)
|
||||
if [ "${1:-}" != "--domain" ] || [ -z "${2:-}" ]; then
|
||||
err "disable requires --domain <name>"
|
||||
exit 1
|
||||
fi
|
||||
cmd_toggle "$2" "0"
|
||||
;;
|
||||
reload) reload_nginx ;;
|
||||
help|--help|-h|"") usage ;;
|
||||
*) err "Unknown command: $command"; usage; exit 1 ;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user