diff --git a/package/secubox/luci-app-service-registry/README.md b/package/secubox/luci-app-service-registry/README.md
index 498305ae..8b5008da 100644
--- a/package/secubox/luci-app-service-registry/README.md
+++ b/package/secubox/luci-app-service-registry/README.md
@@ -17,6 +17,15 @@ Unified service aggregation dashboard with automatic publishing to HAProxy (clea
## Dashboard
+### Network Connectivity Panel
+
+Real-time network status showing:
+- **Public IPv4** - Your external IP address with reverse DNS hostname
+- **Public IPv6** - IPv6 address if available
+- **External Port 80/443** - Whether ports are reachable from the internet (tests upstream router/ISP forwarding)
+- **Local Firewall** - OpenWrt firewall rule status
+- **HAProxy** - Reverse proxy container status
+
### Health Summary Bar
Shows overall system status at a glance:
@@ -32,12 +41,17 @@ Before publishing a service, verify the domain is properly configured:
1. Enter a domain in the checker (e.g., `example.com`)
2. Click "Check" to verify:
- - **DNS Resolution** - Domain resolves to expected IP
- - **Firewall Ports** - Ports 80 and 443 open from WAN
+ - **Your Public IP** - Shows your IPv4/IPv6 addresses and reverse DNS
+ - **DNS Resolution** - Verifies domain resolves to your public IP (detects private IP misconfiguration)
+ - **Internet Accessibility** - Tests if ports 80/443 are reachable from internet (upstream router check)
+ - **Local Firewall** - OpenWrt firewall rule status
- **SSL Certificate** - Valid certificate with expiry status
- **HAProxy** - Reverse proxy container running
-The checker provides actionable recommendations when issues are found.
+The checker provides specific actionable recommendations:
+- If DNS points to private IP (e.g., 192.168.x.x), shows the correct public IP to use
+- If ports are blocked externally, advises checking upstream router port forwarding
+- Shows exact DNS A record to create: `domain.com → your.public.ip`
### Service Health Indicators
@@ -96,6 +110,39 @@ When you publish a service with a domain:
## Health Check API
+### Get Network Info
+
+```bash
+ubus call luci.service-registry get_network_info
+```
+
+Response:
+```json
+{
+ "success": true,
+ "lan_ip": "192.168.255.1",
+ "ipv4": {
+ "address": "185.220.101.12",
+ "status": "ok",
+ "hostname": "server.example.com"
+ },
+ "ipv6": {
+ "address": "2001:db8::1",
+ "status": "ok"
+ },
+ "external_ports": {
+ "http": { "accessible": true, "status": "open" },
+ "https": { "accessible": true, "status": "open" }
+ },
+ "firewall": {
+ "status": "ok",
+ "http_open": true,
+ "https_open": true
+ },
+ "haproxy": { "status": "running" }
+}
+```
+
### Check Single Domain
```bash
@@ -107,25 +154,41 @@ Response:
{
"success": true,
"domain": "example.com",
+ "public_ip": {
+ "ipv4": "185.220.101.12",
+ "ipv6": "2001:db8::1",
+ "hostname": "server.example.com"
+ },
"dns": {
"status": "ok",
- "resolved_ip": "203.0.113.10"
+ "resolved_ip": "185.220.101.12"
},
- "certificate": {
+ "external_access": {
"status": "ok",
- "days_left": 45
+ "http_accessible": true,
+ "https_accessible": true
},
"firewall": {
"status": "ok",
"http_open": true,
"https_open": true
},
+ "certificate": {
+ "status": "ok",
+ "days_left": 45
+ },
"haproxy": {
"status": "running"
}
}
```
+DNS status values:
+- `ok` - Domain resolves to your public IP
+- `private` - Domain resolves to a private IP (192.168.x.x, 10.x.x.x, etc.)
+- `mismatch` - Domain resolves to a different public IP
+- `failed` - DNS resolution failed
+
### Check All Services
```bash
@@ -222,8 +285,9 @@ uci commit service-registry
| `list_services` | List all services from all providers |
| `publish_service` | Publish a service to HAProxy/Tor |
| `unpublish_service` | Remove service from HAProxy/Tor |
-| `check_service_health` | Check DNS/cert/firewall for domain |
+| `check_service_health` | Check DNS/cert/firewall/external access for domain |
| `check_all_health` | Batch health check all services |
+| `get_network_info` | Get public IPs, external port accessibility, firewall status |
| `generate_landing_page` | Regenerate static landing page |
## License
diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js
index 2735d499..a1577732 100644
--- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js
+++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js
@@ -100,6 +100,12 @@ var callCheckAllHealth = rpc.declare({
expect: {}
});
+var callGetNetworkInfo = rpc.declare({
+ object: 'luci.service-registry',
+ method: 'get_network_info',
+ expect: {}
+});
+
// HAProxy status for provider info
var callHAProxyStatus = rpc.declare({
object: 'luci.haproxy',
@@ -245,6 +251,11 @@ return baseclass.extend({
return callCheckAllHealth();
},
+ // Get network connectivity info (public IPs, port accessibility)
+ getNetworkInfo: function() {
+ return callGetNetworkInfo();
+ },
+
// Get dashboard data with health status
getDashboardDataWithHealth: function() {
return Promise.all([
diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js
index 547ad6e2..1ed7c65c 100644
--- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js
+++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js
@@ -50,9 +50,15 @@ return view.extend({
var published = services.filter(function(s) { return s.published; });
var unpublished = services.filter(function(s) { return !s.published; });
+ // Load network info asynchronously
+ var networkPanel = E('div', { 'id': 'sr-network-panel', 'class': 'sr-network-loading' },
+ E('span', { 'class': 'spinning' }, 'Loading network info...'));
+ this.loadNetworkInfo(networkPanel);
+
return E('div', { 'class': 'sr-compact' }, [
this.renderHeader(services, providers, data.haproxy, data.tor),
this.renderHealthSummary(data.health),
+ networkPanel,
this.renderUrlChecker(),
this.renderSection('📡 Published Services', published, true),
this.renderSection('🔍 Discovered Services', unpublished, false),
@@ -60,6 +66,108 @@ return view.extend({
]);
},
+ loadNetworkInfo: function(container) {
+ api.getNetworkInfo().then(function(data) {
+ if (!data.success) {
+ container.innerHTML = '
Failed to load network info
';
+ return;
+ }
+
+ var ipv4 = data.ipv4 || {};
+ var ipv6 = data.ipv6 || {};
+ var extPorts = data.external_ports || {};
+ var firewall = data.firewall || {};
+
+ var html = '';
+ html += '';
+ html += '
';
+
+ // IPv4
+ html += '
';
+ html += 'IPv4';
+ if (ipv4.address) {
+ html += '' + ipv4.address + '';
+ if (ipv4.hostname) {
+ html += '' + ipv4.hostname + '';
+ }
+ } else {
+ html += 'Not available';
+ }
+ html += '
';
+
+ // IPv6
+ html += '
';
+ html += 'IPv6';
+ if (ipv6.address) {
+ html += '' + ipv6.address + '';
+ if (ipv6.hostname) {
+ html += '' + ipv6.hostname + '';
+ }
+ } else {
+ html += 'Not available';
+ }
+ html += '
';
+
+ // External Port 80
+ html += '
';
+ html += 'Port 80 (HTTP)';
+ var http = extPorts.http || {};
+ if (http.status === 'open') {
+ html += '✅ Open from Internet';
+ } else if (http.status === 'blocked') {
+ html += '🚫 Blocked';
+ html += '' + (http.hint || 'Check router') + '';
+ } else {
+ html += 'Unknown';
+ }
+ html += '
';
+
+ // External Port 443
+ html += '
';
+ html += 'Port 443 (HTTPS)';
+ var https = extPorts.https || {};
+ if (https.status === 'open') {
+ html += '✅ Open from Internet';
+ } else if (https.status === 'blocked') {
+ html += '🚫 Blocked';
+ html += '' + (https.hint || 'Check router') + '';
+ } else {
+ html += 'Unknown';
+ }
+ html += '
';
+
+ // Local Firewall
+ html += '
';
+ html += 'Local Firewall';
+ if (firewall.status === 'ok') {
+ html += '✅ Ports 80/443 open';
+ } else if (firewall.status === 'partial') {
+ html += '⚠️ Partial';
+ } else {
+ html += '🚫 Closed';
+ }
+ html += '
';
+
+ // HAProxy
+ html += '
';
+ html += 'HAProxy';
+ var haproxy = data.haproxy || {};
+ if (haproxy.status === 'running') {
+ html += '🟢 Running';
+ } else {
+ html += '🔴 Stopped';
+ }
+ html += '
';
+
+ html += '
';
+
+ container.className = 'sr-network-loaded';
+ container.innerHTML = html;
+ }).catch(function(err) {
+ container.innerHTML = 'Error: ' + err.message + '
';
+ });
+ },
+
renderHealthSummary: function(health) {
if (!health || !health.firewall) return E('div');
@@ -136,46 +244,90 @@ return view.extend({
var html = '';
- // DNS Status
+ // Public IP Info
+ var publicIp = result.public_ip || {};
+ html += '
';
+ html += '🌍';
+ html += 'Your Public IP';
+ html += '';
+ if (publicIp.ipv4) {
+ html += 'IPv4: ' + publicIp.ipv4 + '';
+ if (publicIp.hostname) html += ' (' + publicIp.hostname + ')';
+ }
+ if (publicIp.ipv6) {
+ html += '
IPv6: ' + publicIp.ipv6 + '';
+ }
+ html += '
';
+
+ // DNS Status with IP comparison
var dnsStatus = result.dns || {};
- var dnsIcon = healthIcons.dns[dnsStatus.status] || '❓';
- var dnsClass = dnsStatus.status === 'ok' ? 'sr-check-ok' : 'sr-check-fail';
+ var dnsClass = 'sr-check-fail';
+ if (dnsStatus.status === 'ok') dnsClass = 'sr-check-ok';
+ else if (dnsStatus.status === 'private' || dnsStatus.status === 'mismatch') dnsClass = 'sr-check-warn';
+
html += '
';
- html += '' + dnsIcon + '';
+ html += '🌐';
html += 'DNS Resolution';
if (dnsStatus.status === 'ok') {
- html += '✅ Resolves to ' + dnsStatus.resolved_ip + '';
+ html += '✅ Resolves to ' + dnsStatus.resolved_ip + ' (matches public IP)';
+ } else if (dnsStatus.status === 'private') {
+ html += '⚠️ Resolves to ' + dnsStatus.resolved_ip + ' (private IP!)';
+ html += 'Should be: ' + dnsStatus.expected + '';
+ } else if (dnsStatus.status === 'mismatch') {
+ html += '⚠️ Resolves to ' + dnsStatus.resolved_ip + '';
+ html += 'Your public IP: ' + dnsStatus.expected + '';
} else {
html += '❌ DNS not configured or not resolving';
}
html += '
';
- // Firewall Status
+ // External Port Accessibility
+ var extAccess = result.external_access || {};
+ var extClass = extAccess.status === 'ok' ? 'sr-check-ok' : (extAccess.status === 'partial' ? 'sr-check-warn' : 'sr-check-fail');
+ html += '
';
+ html += '🔌';
+ html += 'Internet Accessibility';
+ if (extAccess.status === 'ok') {
+ html += '✅ Ports 80 & 443 reachable from internet';
+ } else if (extAccess.status === 'partial') {
+ var open = [];
+ var closed = [];
+ if (extAccess.http_accessible) open.push('80'); else closed.push('80');
+ if (extAccess.https_accessible) open.push('443'); else closed.push('443');
+ html += '⚠️ Open: ' + open.join(',') + ' | Blocked: ' + closed.join(',') + '';
+ html += '' + (extAccess.hint || '') + '';
+ } else if (extAccess.status === 'blocked') {
+ html += '🚫 Ports NOT reachable from internet';
+ html += '' + (extAccess.hint || 'Check upstream router/ISP port forwarding') + '';
+ } else {
+ html += '❓ Could not test external accessibility';
+ }
+ html += '
';
+
+ // Local Firewall Status
var fwStatus = result.firewall || {};
- var fwIcon = healthIcons.firewall[fwStatus.status] || '❓';
var fwClass = fwStatus.status === 'ok' ? 'sr-check-ok' : (fwStatus.status === 'partial' ? 'sr-check-warn' : 'sr-check-fail');
html += '
';
- html += '' + fwIcon + '';
- html += 'Firewall Ports';
+ html += '🛡️';
+ html += 'Local Firewall';
var ports = [];
if (fwStatus.http_open) ports.push('80');
if (fwStatus.https_open) ports.push('443');
- html += '' + (ports.length ? 'Open: ' + ports.join(', ') : '❌ Ports 80/443 not open') + '';
+ html += '' + (ports.length === 2 ? '✅ Ports 80/443 open' : (ports.length ? '⚠️ Only port ' + ports.join(',') + ' open' : '❌ Ports closed')) + '';
html += '
';
// Certificate Status
var certStatus = result.certificate || {};
- var certIcon = healthIcons.cert[certStatus.status] || '❓';
var certClass = certStatus.status === 'ok' ? 'sr-check-ok' : (certStatus.status === 'warning' ? 'sr-check-warn' : 'sr-check-fail');
html += '
';
- html += '
' + certIcon + '';
+ html += '
🔒';
html += '
SSL Certificate';
if (certStatus.status === 'ok' || certStatus.status === 'warning') {
- html += '
' + certStatus.days_left + ' days remaining';
+ html += '
' + (certStatus.status === 'ok' ? '✅' : '⚠️') + ' ' + certStatus.days_left + ' days remaining';
} else if (certStatus.status === 'expired') {
html += '
❌ Certificate expired';
} else if (certStatus.status === 'missing') {
- html += '
⚪ No certificate (request via HAProxy)';
+ html += '
⚪ No certificate yet';
} else {
html += '
⚪ Not applicable';
}
@@ -183,10 +335,9 @@ return view.extend({
// HAProxy Status
var haStatus = result.haproxy || {};
- var haIcon = haStatus.status === 'running' ? '🟢' : '🔴';
var haClass = haStatus.status === 'running' ? 'sr-check-ok' : 'sr-check-fail';
html += '
';
- html += '' + haIcon + '';
+ html += '' + (haStatus.status === 'running' ? '🟢' : '🔴') + '';
html += 'HAProxy';
html += '' + (haStatus.status === 'running' ? '✅ Running' : '❌ Not running') + '';
html += '
';
@@ -194,24 +345,36 @@ return view.extend({
html += '
';
// Summary and recommendation
- var allOk = dnsStatus.status === 'ok' && fwStatus.status === 'ok' && haStatus.status === 'running';
+ var dnsOk = dnsStatus.status === 'ok';
+ var extOk = extAccess.status === 'ok';
+ var fwOk = fwStatus.status === 'ok';
+ var haOk = haStatus.status === 'running';
+ var certOk = certStatus.status === 'ok' || certStatus.status === 'warning';
var needsCert = certStatus.status === 'missing';
+ var allOk = dnsOk && extOk && fwOk && haOk;
html += '
';
- if (allOk && !needsCert) {
- html += '
✅ ' + domain + ' is ready and serving!
';
+ if (allOk && certOk) {
+ html += '
✅ ' + domain + ' is fully operational!
';
} else if (allOk && needsCert) {
html += '
⚠️ ' + domain + ' is ready - just need SSL certificate
';
html += '
📜 Request Certificate';
} else {
html += '
❌ ' + domain + ' needs configuration
';
- if (dnsStatus.status !== 'ok') {
- html += '
💡 Point DNS A record to your public IP
';
+ if (dnsStatus.status === 'private') {
+ html += '
💡 DNS points to private IP! Update A record to: ' + publicIp.ipv4 + '
';
+ } else if (dnsStatus.status === 'mismatch') {
+ html += '
💡 DNS points to different IP. Update A record to: ' + publicIp.ipv4 + '
';
+ } else if (dnsStatus.status !== 'ok') {
+ html += '
💡 Create DNS A record: ' + domain + ' → ' + publicIp.ipv4 + '
';
}
- if (fwStatus.status !== 'ok') {
- html += '
💡 Open ports 80 and 443 in firewall
';
+ if (!extOk && extAccess.status !== 'unknown') {
+ html += '
💡 Port forwarding needed! Forward ports 80/443 on your router to this device
';
}
- if (haStatus.status !== 'running') {
+ if (!fwOk) {
+ html += '
💡 Open ports 80 and 443 in local firewall
';
+ }
+ if (!haOk) {
html += '
💡 Start HAProxy container
';
}
}
@@ -550,6 +713,24 @@ return view.extend({
@media (prefers-color-scheme: dark) { .sr-health-bar { background: #1a2a3e; } }
.sr-health-item { font-size: 0.9em; }
+ /* Network Info Panel */
+ .sr-network-loading { padding: 20px; text-align: center; background: #f8f8f8; border-radius: 8px; margin-bottom: 15px; }
+ @media (prefers-color-scheme: dark) { .sr-network-loading { background: #1a1a2e; } }
+ .sr-network-loaded { margin-bottom: 15px; }
+ .sr-network-error { padding: 15px; background: #fef2f2; color: #dc2626; border-radius: 8px; margin-bottom: 15px; }
+ @media (prefers-color-scheme: dark) { .sr-network-error { background: #450a0a; color: #fca5a5; } }
+ .sr-network-card { background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%); border-radius: 12px; padding: 20px; color: #fff; }
+ .sr-network-header { font-size: 1.1em; font-weight: 600; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); }
+ .sr-network-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; }
+ .sr-network-item { background: rgba(0,0,0,0.2); padding: 12px; border-radius: 8px; }
+ .sr-network-label { display: block; font-size: 0.8em; color: #94a3b8; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px; }
+ .sr-network-value { display: block; font-size: 0.95em; font-weight: 500; word-break: break-all; }
+ .sr-network-value.sr-network-ok { color: #22c55e; }
+ .sr-network-value.sr-network-fail { color: #ef4444; }
+ .sr-network-value.sr-network-warn { color: #eab308; }
+ .sr-network-value.sr-network-na { color: #64748b; font-style: italic; }
+ .sr-network-sub { display: block; font-size: 0.75em; color: #64748b; margin-top: 4px; }
+
/* URL Checker Wizard Card */
.sr-wizard-card { background: linear-gradient(135deg, #0a192f 0%, #172a45 100%); border-radius: 12px; padding: 20px; margin-bottom: 25px; color: #fff; }
.sr-wizard-header { display: flex; align-items: center; gap: 12px; margin-bottom: 15px; }
@@ -569,14 +750,20 @@ return view.extend({
.sr-check-item.sr-check-ok { border-left-color: #22c55e; }
.sr-check-item.sr-check-warn { border-left-color: #eab308; }
.sr-check-item.sr-check-fail { border-left-color: #ef4444; }
- .sr-check-icon { font-size: 1.3em; }
- .sr-check-label { font-weight: 600; font-size: 0.9em; min-width: 100px; }
- .sr-check-value { font-size: 0.85em; opacity: 0.8; }
+ .sr-check-item.sr-check-info { border-left-color: #0099cc; }
+ .sr-check-icon { font-size: 1.3em; flex-shrink: 0; }
+ .sr-check-label { font-weight: 600; font-size: 0.9em; min-width: 100px; flex-shrink: 0; }
+ .sr-check-value { font-size: 0.85em; opacity: 0.9; flex: 1; }
+ .sr-check-value code { background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 3px; font-family: monospace; }
+ .sr-check-value strong { color: #fff; }
+ .sr-check-sub { display: block; font-size: 0.8em; color: #94a3b8; margin-top: 4px; }
.sr-check-summary { margin-top: 15px; padding: 15px; background: #0f172a; border-radius: 8px; text-align: center; }
.sr-check-ready { font-size: 1.1em; color: #22c55e; font-weight: 600; }
.sr-check-almost { font-size: 1.1em; color: #eab308; font-weight: 600; }
.sr-check-notready { font-size: 1.1em; color: #ef4444; font-weight: 600; margin-bottom: 10px; }
- .sr-check-tip { font-size: 0.85em; opacity: 0.8; margin-top: 5px; }
+ .sr-check-tip { font-size: 0.85em; opacity: 0.9; margin-top: 8px; text-align: left; padding: 0 20px; }
+ .sr-check-tip code { background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 3px; font-family: monospace; color: #0ff; }
+ .sr-check-tip strong { color: #fbbf24; }
.sr-check-action { display: inline-block; margin-top: 10px; padding: 8px 16px; background: #0099cc; color: #fff; text-decoration: none; border-radius: 6px; font-size: 0.9em; }
.sr-check-action:hover { background: #00b3e6; }
diff --git a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry
index 979e4714..58b166b9 100644
--- a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry
+++ b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry
@@ -901,7 +901,7 @@ check_dns_resolution() {
return 1
}
-# Helper: Get WAN IP address
+# Helper: Get WAN IP address (local interface)
get_wan_ip() {
local wan_ip=""
# Try to get WAN IP from interface
@@ -917,6 +917,62 @@ get_wan_ip() {
echo "$wan_ip"
}
+# Helper: Get public IPv4 address (from external service)
+get_public_ipv4() {
+ local ip=""
+ # Try multiple services for reliability
+ ip=$(wget -qO- -T 5 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n')
+ [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://api.ipify.org" 2>/dev/null | tr -d '\n')
+ [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://v4.ident.me" 2>/dev/null | tr -d '\n')
+ echo "$ip"
+}
+
+# Helper: Get public IPv6 address (from external service)
+get_public_ipv6() {
+ local ip=""
+ # Try multiple services for reliability
+ ip=$(wget -qO- -T 5 "http://ipv6.icanhazip.com" 2>/dev/null | tr -d '\n')
+ [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://v6.ident.me" 2>/dev/null | tr -d '\n')
+ echo "$ip"
+}
+
+# Helper: Check external port accessibility using portchecker service
+check_external_port() {
+ local ip="$1"
+ local port="$2"
+ local result=""
+
+ # Use canyouseeme.org API or similar
+ # Try portquiz.net which echoes back on any port
+ result=$(wget -qO- -T 5 "http://portquiz.net:${port}/" 2>/dev/null)
+ if echo "$result" | grep -q "Port ${port}"; then
+ return 0
+ fi
+
+ # Alternative: try to connect to our own IP from outside perspective
+ # Use online port checker API
+ result=$(wget -qO- -T 8 "https://ports.yougetsignal.com/short-url-check-port.php?remoteAddress=${ip}&portNumber=${port}" 2>/dev/null)
+ if echo "$result" | grep -qi "open"; then
+ return 0
+ fi
+
+ return 1
+}
+
+# Helper: Reverse DNS lookup
+get_reverse_dns() {
+ local ip="$1"
+ local hostname=""
+
+ if command -v nslookup >/dev/null 2>&1; then
+ hostname=$(nslookup "$ip" 2>/dev/null | grep "name =" | head -1 | awk '{print $NF}' | sed 's/\.$//')
+ elif command -v host >/dev/null 2>&1; then
+ hostname=$(host "$ip" 2>/dev/null | grep "pointer" | head -1 | awk '{print $NF}' | sed 's/\.$//')
+ fi
+
+ echo "$hostname"
+}
+
# Helper: Check certificate expiry
check_cert_expiry() {
local domain="$1"
@@ -975,6 +1031,116 @@ check_port_firewall_open() {
return 1
}
+# Get network connectivity info (public IPs, port accessibility)
+method_get_network_info() {
+ json_init
+ json_add_boolean "success" 1
+
+ local lan_ip
+ lan_ip=$(get_lan_ip)
+ json_add_string "lan_ip" "$lan_ip"
+
+ # Get public IPv4
+ json_add_object "ipv4"
+ local public_ipv4
+ public_ipv4=$(get_public_ipv4)
+ if [ -n "$public_ipv4" ]; then
+ json_add_string "address" "$public_ipv4"
+ json_add_string "status" "ok"
+
+ # Reverse DNS
+ local rdns
+ rdns=$(get_reverse_dns "$public_ipv4")
+ [ -n "$rdns" ] && json_add_string "hostname" "$rdns"
+ else
+ json_add_string "status" "unavailable"
+ fi
+ json_close_object
+
+ # Get public IPv6
+ json_add_object "ipv6"
+ local public_ipv6
+ public_ipv6=$(get_public_ipv6)
+ if [ -n "$public_ipv6" ]; then
+ json_add_string "address" "$public_ipv6"
+ json_add_string "status" "ok"
+
+ # Reverse DNS
+ local rdns6
+ rdns6=$(get_reverse_dns "$public_ipv6")
+ [ -n "$rdns6" ] && json_add_string "hostname" "$rdns6"
+ else
+ json_add_string "status" "unavailable"
+ fi
+ json_close_object
+
+ # External port accessibility (from internet perspective)
+ json_add_object "external_ports"
+ if [ -n "$public_ipv4" ]; then
+ # Check port 80
+ json_add_object "http"
+ if check_external_port "$public_ipv4" 80; then
+ json_add_boolean "accessible" 1
+ json_add_string "status" "open"
+ else
+ json_add_boolean "accessible" 0
+ json_add_string "status" "blocked"
+ json_add_string "hint" "Check upstream router port forwarding"
+ fi
+ json_close_object
+
+ # Check port 443
+ json_add_object "https"
+ if check_external_port "$public_ipv4" 443; then
+ json_add_boolean "accessible" 1
+ json_add_string "status" "open"
+ else
+ json_add_boolean "accessible" 0
+ json_add_string "status" "blocked"
+ json_add_string "hint" "Check upstream router port forwarding"
+ fi
+ json_close_object
+ else
+ json_add_object "http"
+ json_add_string "status" "unknown"
+ json_add_string "error" "No public IP"
+ json_close_object
+ json_add_object "https"
+ json_add_string "status" "unknown"
+ json_add_string "error" "No public IP"
+ json_close_object
+ fi
+ json_close_object
+
+ # Local firewall status
+ json_add_object "firewall"
+ local http_open=0
+ local https_open=0
+ check_port_firewall_open 80 && http_open=1
+ check_port_firewall_open 443 && https_open=1
+ json_add_boolean "http_open" "$http_open"
+ json_add_boolean "https_open" "$https_open"
+ if [ "$http_open" = "1" ] && [ "$https_open" = "1" ]; then
+ json_add_string "status" "ok"
+ elif [ "$http_open" = "1" ] || [ "$https_open" = "1" ]; then
+ json_add_string "status" "partial"
+ else
+ json_add_string "status" "closed"
+ fi
+ json_close_object
+
+ # HAProxy status
+ json_add_object "haproxy"
+ if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
+ json_add_string "status" "running"
+ else
+ json_add_string "status" "stopped"
+ fi
+ json_close_object
+
+ json_dump
+}
+
# Check health status for a service
method_check_service_health() {
local service_id domain
@@ -1003,14 +1169,46 @@ method_check_service_health() {
json_add_string "service_id" "$service_id"
json_add_string "domain" "$domain"
- # DNS check
+ # Get public IPs for comparison
+ local public_ipv4 public_ipv6
+ public_ipv4=$(get_public_ipv4)
+ public_ipv6=$(get_public_ipv6)
+
+ # Public IP info
+ json_add_object "public_ip"
+ json_add_string "ipv4" "$public_ipv4"
+ [ -n "$public_ipv6" ] && json_add_string "ipv6" "$public_ipv6"
+ if [ -n "$public_ipv4" ]; then
+ local rdns
+ rdns=$(get_reverse_dns "$public_ipv4")
+ [ -n "$rdns" ] && json_add_string "hostname" "$rdns"
+ fi
+ json_close_object
+
+ # DNS check with public IP comparison
json_add_object "dns"
if [ -n "$domain" ]; then
local resolved_ip
resolved_ip=$(check_dns_resolution "$domain")
if [ -n "$resolved_ip" ]; then
- json_add_string "status" "ok"
json_add_string "resolved_ip" "$resolved_ip"
+ # Check if DNS points to public IP or private IP
+ case "$resolved_ip" in
+ 10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*)
+ json_add_string "status" "private"
+ json_add_string "error" "DNS points to private IP (not reachable from internet)"
+ json_add_string "expected" "$public_ipv4"
+ ;;
+ *)
+ if [ "$resolved_ip" = "$public_ipv4" ]; then
+ json_add_string "status" "ok"
+ else
+ json_add_string "status" "mismatch"
+ json_add_string "expected" "$public_ipv4"
+ json_add_string "hint" "DNS points to different IP than your public IP"
+ fi
+ ;;
+ esac
else
json_add_string "status" "failed"
json_add_string "error" "DNS resolution failed"
@@ -1020,6 +1218,30 @@ method_check_service_health() {
fi
json_close_object
+ # External port accessibility check
+ json_add_object "external_access"
+ if [ -n "$public_ipv4" ]; then
+ local http_ext=0
+ local https_ext=0
+ check_external_port "$public_ipv4" 80 && http_ext=1
+ check_external_port "$public_ipv4" 443 && https_ext=1
+ json_add_boolean "http_accessible" "$http_ext"
+ json_add_boolean "https_accessible" "$https_ext"
+ if [ "$http_ext" = "1" ] && [ "$https_ext" = "1" ]; then
+ json_add_string "status" "ok"
+ elif [ "$http_ext" = "1" ] || [ "$https_ext" = "1" ]; then
+ json_add_string "status" "partial"
+ json_add_string "hint" "Check upstream router/ISP port forwarding"
+ else
+ json_add_string "status" "blocked"
+ json_add_string "hint" "Ports not accessible from internet - check router/ISP"
+ fi
+ else
+ json_add_string "status" "unknown"
+ json_add_string "error" "Could not determine public IP"
+ fi
+ json_close_object
+
# Certificate check
json_add_object "certificate"
if [ -n "$domain" ]; then
@@ -1293,6 +1515,7 @@ case "$1" in
"get_certificate_status": { "service_id": "string" },
"check_service_health": { "service_id": "string", "domain": "string" },
"check_all_health": {},
+ "get_network_info": {},
"get_landing_config": {},
"save_landing_config": { "auto_regen": "boolean" }
}
@@ -1313,6 +1536,7 @@ EOF
get_certificate_status) method_get_certificate_status ;;
check_service_health) method_check_service_health ;;
check_all_health) method_check_all_health ;;
+ get_network_info) method_get_network_info ;;
get_landing_config) method_get_landing_config ;;
save_landing_config) method_save_landing_config ;;
*)
diff --git a/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json b/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json
index f91234d3..68fd189d 100644
--- a/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json
+++ b/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json
@@ -11,6 +11,7 @@
"get_certificate_status",
"check_service_health",
"check_all_health",
+ "get_network_info",
"get_landing_config"
],
"luci.haproxy": [