fix(service-registry): Fix RPC data handling and landing page permissions
- Remove expect clause from RPC declarations to get raw response - Add proper error handling with catch blocks for all RPC calls - Fix landing page generator to chmod 644 after generation - Fixes "No Services Found" issue in dashboard - Fixes "Forbidden" error when accessing landing page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ccba39da62
commit
8d08ccd4a4
@ -164,7 +164,9 @@
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(nc:*)",
|
||||
"Bash(pkill:*)"
|
||||
"Bash(pkill:*)",
|
||||
"Bash(python3 -m json.tool:*)",
|
||||
"Bash(git restore:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,12 @@ var callGetServices = rpc.declare({
|
||||
expect: { services: [] }
|
||||
});
|
||||
|
||||
var callDashboardData = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
method: 'get_dashboard_data',
|
||||
expect: { counts: {} }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
currentSection: 'dashboard',
|
||||
appStatuses: {},
|
||||
@ -48,10 +54,13 @@ return view.extend({
|
||||
callCrowdSecStats().catch(function() { return null; }),
|
||||
portal.checkInstalledApps(),
|
||||
callGetServices().catch(function() { return []; }),
|
||||
callSecurityStats().catch(function() { return null; })
|
||||
callSecurityStats().catch(function() { return null; }),
|
||||
callDashboardData().catch(function() { return { counts: {} }; })
|
||||
]).then(function(results) {
|
||||
// Store installed apps info from the last promise
|
||||
self.installedApps = results[4] || {};
|
||||
// Store dashboard counts from RPCD (reliable source)
|
||||
self.dashboardCounts = results[7] || {};
|
||||
// RPC expect unwraps the services array directly
|
||||
var svcResult = results[5] || [];
|
||||
self.detectedServices = Array.isArray(svcResult) ? svcResult : (svcResult.services || []);
|
||||
@ -244,9 +253,10 @@ return view.extend({
|
||||
var networkApps = portal.getAppsBySection('network');
|
||||
var monitoringApps = portal.getAppsBySection('monitoring');
|
||||
|
||||
// Count running services
|
||||
var runningCount = Object.values(this.appStatuses).filter(function(s) { return s === 'running'; }).length;
|
||||
var totalServices = Object.keys(this.appStatuses).length;
|
||||
// Count running services - prefer RPCD counts (reliable), fallback to local check
|
||||
var counts = this.dashboardCounts || {};
|
||||
var runningCount = counts.running || Object.values(this.appStatuses).filter(function(s) { return s === 'running'; }).length;
|
||||
var totalServices = counts.total || Object.keys(this.appStatuses).length;
|
||||
|
||||
// CrowdSec blocked IPs count
|
||||
var blockedIPv4 = (crowdSecStats.ipv4_total_count || 0);
|
||||
|
||||
@ -5,8 +5,7 @@
|
||||
// RPC method declarations
|
||||
var callListServices = rpc.declare({
|
||||
object: 'luci.service-registry',
|
||||
method: 'list_services',
|
||||
expect: { services: [], providers: {} }
|
||||
method: 'list_services'
|
||||
});
|
||||
|
||||
var callGetService = rpc.declare({
|
||||
@ -65,8 +64,7 @@ var callGetQrData = rpc.declare({
|
||||
|
||||
var callListCategories = rpc.declare({
|
||||
object: 'luci.service-registry',
|
||||
method: 'list_categories',
|
||||
expect: { categories: [] }
|
||||
method: 'list_categories'
|
||||
});
|
||||
|
||||
var callGetCertificateStatus = rpc.declare({
|
||||
@ -179,9 +177,9 @@ return baseclass.extend({
|
||||
// Get dashboard data (services + provider status)
|
||||
getDashboardData: function() {
|
||||
return Promise.all([
|
||||
callListServices(),
|
||||
callListCategories(),
|
||||
callGetLandingConfig(),
|
||||
callListServices().catch(function(e) { console.error('list_services failed:', e); return { services: [], providers: {} }; }),
|
||||
callListCategories().catch(function(e) { console.error('list_categories failed:', e); return { categories: [] }; }),
|
||||
callGetLandingConfig().catch(function(e) { console.error('get_landing_config failed:', e); return {}; }),
|
||||
callHAProxyStatus().catch(function() { return { enabled: false }; }),
|
||||
callTorStatus().catch(function() { return { enabled: false }; })
|
||||
]).then(function(results) {
|
||||
|
||||
@ -208,34 +208,78 @@ _aggregate_haproxy_services() {
|
||||
local lan_ip="$1"
|
||||
local tmp_file="$2"
|
||||
|
||||
# Call HAProxy RPCD to get vhosts
|
||||
local vhosts_json
|
||||
# Call HAProxy RPCD to get vhosts and backends
|
||||
local vhosts_json backends_json certs_json
|
||||
vhosts_json=$(ubus call luci.haproxy list_vhosts 2>/dev/null)
|
||||
[ -z "$vhosts_json" ] && return
|
||||
|
||||
echo "$vhosts_json" | jsonfilter -e '@.vhosts[*]' 2>/dev/null | while read -r line; do
|
||||
local domain backend ssl
|
||||
domain=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$line].domain" 2>/dev/null)
|
||||
backend=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$line].backend" 2>/dev/null)
|
||||
ssl=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$line].ssl" 2>/dev/null)
|
||||
backends_json=$(ubus call luci.haproxy list_backends 2>/dev/null)
|
||||
certs_json=$(ubus call luci.haproxy list_certificates 2>/dev/null)
|
||||
|
||||
# Skip if domain empty
|
||||
# Get array length
|
||||
local count
|
||||
count=$(echo "$vhosts_json" | jsonfilter -e '@.vhosts[*].domain' 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 0 ] && return
|
||||
|
||||
local i=0
|
||||
while [ $i -lt "$count" ]; do
|
||||
local id domain backend ssl ssl_redirect acme enabled
|
||||
id=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].id" 2>/dev/null)
|
||||
domain=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].domain" 2>/dev/null)
|
||||
backend=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].backend" 2>/dev/null)
|
||||
ssl=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].ssl" 2>/dev/null)
|
||||
ssl_redirect=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].ssl_redirect" 2>/dev/null)
|
||||
acme=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].acme" 2>/dev/null)
|
||||
enabled=$(echo "$vhosts_json" | jsonfilter -e "@.vhosts[$i].enabled" 2>/dev/null)
|
||||
|
||||
i=$((i + 1))
|
||||
|
||||
# Skip if domain empty or disabled
|
||||
[ -z "$domain" ] && continue
|
||||
|
||||
# Check if already processed by local_port
|
||||
# For HAProxy, we identify by domain
|
||||
# Check if already processed
|
||||
grep -q "^haproxy_${domain}$" "$tmp_file" 2>/dev/null && continue
|
||||
|
||||
# Get backend port from servers
|
||||
local backend_port=""
|
||||
if [ -n "$backends_json" ] && [ -n "$backend" ]; then
|
||||
backend_port=$(echo "$backends_json" | jsonfilter -e "@.backends[@.name='$backend'].servers[0].port" 2>/dev/null)
|
||||
[ -z "$backend_port" ] && backend_port=$(echo "$backends_json" | jsonfilter -e "@.backends[@.id='$backend'].servers[0].port" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Check certificate status
|
||||
local cert_status="none"
|
||||
if [ "$ssl" = "true" ] || [ "$ssl" = "1" ]; then
|
||||
if [ "$acme" = "true" ] || [ "$acme" = "1" ]; then
|
||||
cert_status="acme"
|
||||
else
|
||||
cert_status="manual"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine status based on enabled and HAProxy running
|
||||
local status="stopped"
|
||||
if [ "$enabled" = "true" ] || [ "$enabled" = "1" ]; then
|
||||
# Check if HAProxy container is running
|
||||
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
|
||||
status="running"
|
||||
fi
|
||||
else
|
||||
status="disabled"
|
||||
fi
|
||||
|
||||
json_add_object
|
||||
json_add_string "id" "haproxy_$(echo "$domain" | sed 's/[^a-zA-Z0-9]/_/g')"
|
||||
json_add_string "id" "$id"
|
||||
json_add_string "name" "$domain"
|
||||
json_add_string "category" "proxy"
|
||||
json_add_string "icon" "arrow"
|
||||
json_add_string "status" "running"
|
||||
json_add_string "status" "$status"
|
||||
json_add_boolean "published" 1
|
||||
json_add_string "source" "haproxy"
|
||||
|
||||
# URLs
|
||||
json_add_object "urls"
|
||||
json_add_string "local" "http://${lan_ip}${backend_port:+:$backend_port}"
|
||||
if [ "$ssl" = "true" ] || [ "$ssl" = "1" ]; then
|
||||
json_add_string "clearnet" "https://${domain}"
|
||||
else
|
||||
@ -243,11 +287,33 @@ _aggregate_haproxy_services() {
|
||||
fi
|
||||
json_close_object
|
||||
|
||||
# HAProxy details
|
||||
json_add_object "haproxy"
|
||||
json_add_boolean "enabled" 1
|
||||
json_add_string "id" "$id"
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_string "backend" "$backend"
|
||||
json_add_boolean "ssl" "$ssl"
|
||||
[ -n "$backend_port" ] && json_add_int "backend_port" "$backend_port"
|
||||
if [ "$ssl" = "true" ] || [ "$ssl" = "1" ]; then
|
||||
json_add_boolean "ssl" 1
|
||||
else
|
||||
json_add_boolean "ssl" 0
|
||||
fi
|
||||
if [ "$ssl_redirect" = "true" ] || [ "$ssl_redirect" = "1" ]; then
|
||||
json_add_boolean "ssl_redirect" 1
|
||||
else
|
||||
json_add_boolean "ssl_redirect" 0
|
||||
fi
|
||||
if [ "$acme" = "true" ] || [ "$acme" = "1" ]; then
|
||||
json_add_boolean "acme" 1
|
||||
else
|
||||
json_add_boolean "acme" 0
|
||||
fi
|
||||
if [ "$enabled" = "true" ] || [ "$enabled" = "1" ]; then
|
||||
json_add_boolean "enabled" 1
|
||||
else
|
||||
json_add_boolean "enabled" 0
|
||||
fi
|
||||
json_add_string "cert_status" "$cert_status"
|
||||
json_close_object
|
||||
|
||||
json_close_object
|
||||
|
||||
@ -483,4 +483,7 @@ TMP_OUTPUT="${OUTPUT_PATH}.tmp"
|
||||
awk -v json="$SERVICES_JSON" '{gsub(/SERVICES_JSON_PLACEHOLDER/, json); print}' "$OUTPUT_PATH" > "$TMP_OUTPUT"
|
||||
mv "$TMP_OUTPUT" "$OUTPUT_PATH"
|
||||
|
||||
# Ensure web server can read the file
|
||||
chmod 644 "$OUTPUT_PATH"
|
||||
|
||||
echo "Landing page generated: $OUTPUT_PATH"
|
||||
|
||||
255
package/secubox/secubox-app-streamlit/README.md
Normal file
255
package/secubox/secubox-app-streamlit/README.md
Normal file
@ -0,0 +1,255 @@
|
||||
# SecuBox Streamlit Platform
|
||||
|
||||
Multi-instance Streamlit hosting platform for OpenWrt with LXC containers and Gitea integration.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-instance support**: Run multiple Streamlit apps on different ports
|
||||
- **Folder-based apps**: Each app in its own directory with dependencies
|
||||
- **Gitea integration**: Clone and sync apps directly from Gitea repositories
|
||||
- **LXC isolation**: Apps run in isolated Alpine Linux container
|
||||
- **Auto-dependency install**: `requirements.txt` processed automatically
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install & Enable
|
||||
|
||||
```bash
|
||||
opkg install secubox-app-streamlit
|
||||
/etc/init.d/streamlit enable
|
||||
streamlitctl install
|
||||
```
|
||||
|
||||
### 2. Create Your First App
|
||||
|
||||
```bash
|
||||
streamlitctl app create myapp
|
||||
streamlitctl instance add myapp 8502
|
||||
/etc/init.d/streamlit restart
|
||||
```
|
||||
|
||||
Access at: `http://<device-ip>:8502`
|
||||
|
||||
## Deploy from Gitea
|
||||
|
||||
The platform integrates with Gitea for source-controlled app deployment.
|
||||
|
||||
### Setup Gitea Credentials
|
||||
|
||||
```bash
|
||||
# Configure Gitea connection
|
||||
uci set streamlit.gitea.enabled=1
|
||||
uci set streamlit.gitea.url='http://192.168.255.1:3000'
|
||||
uci set streamlit.gitea.user='admin'
|
||||
uci set streamlit.gitea.token='your-access-token'
|
||||
uci commit streamlit
|
||||
|
||||
# Store git credentials in container
|
||||
streamlitctl gitea setup
|
||||
```
|
||||
|
||||
### Clone App from Gitea Repository
|
||||
|
||||
**Method 1: Using streamlitctl (recommended)**
|
||||
|
||||
```bash
|
||||
# Clone using repo shorthand (user/repo)
|
||||
streamlitctl gitea clone yijing CyberMood/yijing-oracle
|
||||
|
||||
# Add instance on port
|
||||
streamlitctl instance add yijing 8505
|
||||
|
||||
# Restart to apply
|
||||
/etc/init.d/streamlit restart
|
||||
```
|
||||
|
||||
**Method 2: Manual Clone + UCI Config**
|
||||
|
||||
```bash
|
||||
# Clone directly to apps directory
|
||||
git clone http://192.168.255.1:3000/CyberMood/yijing-oracle.git /srv/streamlit/apps/yijing
|
||||
|
||||
# Register in UCI
|
||||
uci set streamlit.yijing=app
|
||||
uci set streamlit.yijing.name='Yijing Oracle'
|
||||
uci set streamlit.yijing.path='yijing/app.py'
|
||||
uci set streamlit.yijing.enabled='1'
|
||||
uci set streamlit.yijing.port='8505'
|
||||
uci commit streamlit
|
||||
|
||||
# Add instance and restart
|
||||
streamlitctl instance add yijing 8505
|
||||
/etc/init.d/streamlit restart
|
||||
```
|
||||
|
||||
### Update App from Gitea
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
streamlitctl gitea pull yijing
|
||||
|
||||
# Restart to apply changes
|
||||
/etc/init.d/streamlit restart
|
||||
```
|
||||
|
||||
## App Folder Structure
|
||||
|
||||
Each app lives in `/srv/streamlit/apps/<appname>/`:
|
||||
|
||||
```
|
||||
/srv/streamlit/apps/myapp/
|
||||
├── app.py # Main entry point (or main.py, <appname>.py)
|
||||
├── requirements.txt # Python dependencies (auto-installed)
|
||||
├── .streamlit/ # Optional Streamlit config
|
||||
│ └── config.toml
|
||||
└── ... # Other files (pages/, data/, etc.)
|
||||
```
|
||||
|
||||
**Main file detection order**: `app.py` > `main.py` > `<appname>.py` > first `.py` file
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### Container Management
|
||||
|
||||
```bash
|
||||
streamlitctl install # Setup LXC container
|
||||
streamlitctl uninstall # Remove container (keeps apps)
|
||||
streamlitctl update # Update Streamlit version
|
||||
streamlitctl status # Show platform status
|
||||
streamlitctl logs [app] # View logs
|
||||
streamlitctl shell # Open container shell
|
||||
```
|
||||
|
||||
### App Management
|
||||
|
||||
```bash
|
||||
streamlitctl app list # List all apps
|
||||
streamlitctl app create <name> # Create new app folder
|
||||
streamlitctl app delete <name> # Delete app
|
||||
streamlitctl app deploy <name> <path> # Deploy from path/archive
|
||||
```
|
||||
|
||||
### Instance Management
|
||||
|
||||
```bash
|
||||
streamlitctl instance list # List instances
|
||||
streamlitctl instance add <app> <port> # Add instance
|
||||
streamlitctl instance remove <name> # Remove instance
|
||||
streamlitctl instance start <name> # Start single instance
|
||||
streamlitctl instance stop <name> # Stop single instance
|
||||
```
|
||||
|
||||
### Gitea Integration
|
||||
|
||||
```bash
|
||||
streamlitctl gitea setup # Configure git credentials
|
||||
streamlitctl gitea clone <name> <repo> # Clone from Gitea
|
||||
streamlitctl gitea pull <name> # Pull latest changes
|
||||
```
|
||||
|
||||
## UCI Configuration
|
||||
|
||||
Main config: `/etc/config/streamlit`
|
||||
|
||||
```
|
||||
config streamlit 'main'
|
||||
option enabled '1'
|
||||
option http_port '8501'
|
||||
option data_path '/srv/streamlit'
|
||||
option memory_limit '512M'
|
||||
|
||||
config streamlit 'gitea'
|
||||
option enabled '1'
|
||||
option url 'http://192.168.255.1:3000'
|
||||
option user 'admin'
|
||||
option token 'your-token'
|
||||
|
||||
config app 'myapp'
|
||||
option name 'My App'
|
||||
option enabled '1'
|
||||
option repo 'user/myapp'
|
||||
|
||||
config instance 'myapp'
|
||||
option app 'myapp'
|
||||
option port '8502'
|
||||
option enabled '1'
|
||||
```
|
||||
|
||||
## Example: Complete Gitea Workflow
|
||||
|
||||
```bash
|
||||
# 1. Create repo in Gitea with your Streamlit app
|
||||
# - app.py (main file)
|
||||
# - requirements.txt (dependencies)
|
||||
|
||||
# 2. Configure streamlit platform
|
||||
uci set streamlit.gitea.enabled=1
|
||||
uci set streamlit.gitea.url='http://192.168.255.1:3000'
|
||||
uci set streamlit.gitea.user='admin'
|
||||
uci set streamlit.gitea.token='abc123'
|
||||
uci commit streamlit
|
||||
|
||||
# 3. Clone and deploy
|
||||
streamlitctl gitea setup
|
||||
streamlitctl gitea clone myapp admin/my-streamlit-app
|
||||
streamlitctl instance add myapp 8502
|
||||
/etc/init.d/streamlit restart
|
||||
|
||||
# 4. Access app
|
||||
curl http://192.168.255.1:8502
|
||||
|
||||
# 5. Update from Gitea when code changes
|
||||
streamlitctl gitea pull myapp
|
||||
/etc/init.d/streamlit restart
|
||||
```
|
||||
|
||||
## HAProxy Integration
|
||||
|
||||
To expose Streamlit apps via HAProxy vhost:
|
||||
|
||||
```bash
|
||||
# Add backend for app
|
||||
uci add haproxy backend
|
||||
uci set haproxy.@backend[-1].name='streamlit_myapp'
|
||||
uci set haproxy.@backend[-1].mode='http'
|
||||
uci add_list haproxy.@backend[-1].server='myapp 127.0.0.1:8502'
|
||||
uci commit haproxy
|
||||
|
||||
# Add vhost
|
||||
uci add haproxy vhost
|
||||
uci set haproxy.@vhost[-1].name='myapp_vhost'
|
||||
uci set haproxy.@vhost[-1].domain='myapp.example.com'
|
||||
uci set haproxy.@vhost[-1].backend='streamlit_myapp'
|
||||
uci set haproxy.@vhost[-1].ssl='1'
|
||||
uci commit haproxy
|
||||
|
||||
/etc/init.d/haproxy restart
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Container won't start:**
|
||||
```bash
|
||||
streamlitctl status
|
||||
lxc-info -n streamlit
|
||||
```
|
||||
|
||||
**App not loading:**
|
||||
```bash
|
||||
streamlitctl logs myapp
|
||||
streamlitctl shell
|
||||
# Inside container:
|
||||
cd /srv/apps/myapp && streamlit run app.py
|
||||
```
|
||||
|
||||
**Git clone fails:**
|
||||
```bash
|
||||
# Check credentials
|
||||
streamlitctl gitea setup
|
||||
# Test manually
|
||||
git clone http://admin:token@192.168.255.1:3000/user/repo.git /tmp/test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2025 CyberMind.fr
|
||||
@ -113,9 +113,26 @@ App Folder Structure:
|
||||
... Other files
|
||||
|
||||
Examples:
|
||||
# Create local app
|
||||
streamlitctl app create myapp
|
||||
streamlitctl instance add myapp 8502
|
||||
streamlitctl gitea clone myapp myuser/myapp-repo
|
||||
/etc/init.d/streamlit restart
|
||||
|
||||
# Deploy from Gitea (complete workflow)
|
||||
uci set streamlit.gitea.enabled=1
|
||||
uci set streamlit.gitea.url='http://192.168.255.1:3000'
|
||||
uci set streamlit.gitea.user='admin'
|
||||
uci set streamlit.gitea.token='your-token'
|
||||
uci commit streamlit
|
||||
streamlitctl gitea setup
|
||||
streamlitctl gitea clone yijing CyberMood/yijing-oracle
|
||||
streamlitctl instance add yijing 8505
|
||||
/etc/init.d/streamlit restart
|
||||
# Access at: http://<device-ip>:8505
|
||||
|
||||
# Update app from Gitea
|
||||
streamlitctl gitea pull yijing
|
||||
/etc/init.d/streamlit restart
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user