diff --git a/luci-app-auth-guardian/.github/workflows/build.yml b/luci-app-auth-guardian/.github/workflows/build.yml new file mode 100644 index 00000000..f681b632 --- /dev/null +++ b/luci-app-auth-guardian/.github/workflows/build.yml @@ -0,0 +1,106 @@ +name: Build OpenWrt LuCI Package + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +env: + PKG_NAME: luci-app-auth-guardian + OPENWRT_VERSION: '23.05.5' + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check structure + run: | + test -f Makefile + grep -q "PKG_NAME:=luci-app-auth-guardian" Makefile + find . -name "*.json" -exec python3 -m json.tool {} \; >/dev/null + + build: + name: Build ${{ matrix.arch }} + needs: validate + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + target: x86 + subtarget: 64 + - arch: aarch64_cortex-a53 + target: mvebu + subtarget: cortexa53 + - arch: aarch64_cortex-a72 + target: mvebu + subtarget: cortexa72 + - arch: arm_cortex-a9_vfpv3-d16 + target: mvebu + subtarget: cortexa9 + + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang flex bison g++ gawk \ + gcc-multilib gettext git libncurses5-dev libssl-dev \ + python3-setuptools rsync unzip zlib1g-dev file wget xsltproc + + - name: Download and extract SDK + run: | + SDK_BASE="https://downloads.openwrt.org/releases/${{ env.OPENWRT_VERSION }}/targets/${{ matrix.target }}/${{ matrix.subtarget }}" + wget -q "${SDK_BASE}/sha256sums" + SDK_FILE=$(grep -E "openwrt-sdk.*\.tar\.(xz|zst)" sha256sums | head -1 | awk '{print $NF}' | tr -d '*') + [ -z "$SDK_FILE" ] && { echo "SDK not found"; exit 1; } + wget -q "${SDK_BASE}/${SDK_FILE}" + case "$SDK_FILE" in + *.tar.xz) tar -xJf "$SDK_FILE" ;; + *.tar.zst) tar --zstd -xf "$SDK_FILE" ;; + esac + SDK_DIR=$(find . -maxdepth 1 -type d -name "openwrt-sdk-*" -print -quit) + mv "$SDK_DIR" sdk + + - name: Build package + run: | + cd sdk + echo "src-git luci https://github.com/openwrt/luci.git;openwrt-23.05" >> feeds.conf.default + ./scripts/feeds update -a + ./scripts/feeds install -a + mkdir -p "package/${{ env.PKG_NAME }}" + rsync -av --exclude='.git' --exclude='sdk' --exclude='*.tar.*' ../. "package/${{ env.PKG_NAME }}/" + make defconfig + make "package/${{ env.PKG_NAME }}/compile" V=s -j$(nproc) || \ + make "package/${{ env.PKG_NAME }}/compile" V=s -j1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PKG_NAME }}-${{ matrix.arch }} + path: sdk/bin/**/*.ipk + if-no-files-found: error + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + - uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/*.ipk + generate_release_notes: true diff --git a/luci-app-auth-guardian/Makefile b/luci-app-auth-guardian/Makefile new file mode 100644 index 00000000..22970440 --- /dev/null +++ b/luci-app-auth-guardian/Makefile @@ -0,0 +1,49 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-auth-guardian +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/luci-app-auth-guardian + SECTION:=luci + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=Auth Guardian - Authentication & Session Manager + DEPENDS:=+luci-base +rpcd +nodogsplash + PKGARCH:=all +endef + +define Package/luci-app-auth-guardian/description + Comprehensive authentication and session management: + - Captive portal with customizable splash pages + - OAuth2/OIDC integration (Google, GitHub, etc.) + - Cookie-based session management + - MAC authentication bypass + - Voucher/ticket system + - Time-based access control + - User/device tracking +endef + +define Build/Compile +endef + +define Package/luci-app-auth-guardian/install + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.auth-guardian $(1)/usr/libexec/rpcd/ + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/ + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/authguard $(1)/etc/config/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/auth-guardian + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/auth-guardian/*.js $(1)/www/luci-static/resources/view/auth-guardian/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/auth-guardian + $(INSTALL_DATA) ./htdocs/luci-static/resources/auth-guardian/*.js $(1)/www/luci-static/resources/auth-guardian/ +endef + +$(eval $(call BuildPackage,luci-app-auth-guardian)) diff --git a/luci-app-auth-guardian/README.md b/luci-app-auth-guardian/README.md new file mode 100644 index 00000000..831723e6 --- /dev/null +++ b/luci-app-auth-guardian/README.md @@ -0,0 +1,42 @@ +# Auth Guardian for OpenWrt + +Comprehensive authentication and session management system. + +## Features + +### 🎨 Captive Portal +- Customizable splash pages +- Logo and branding support +- Terms of service acceptance + +### 🔑 OAuth Integration +- Google Sign-In +- GitHub Authentication +- Facebook Login +- Twitter/X Login + +### 🎟️ Voucher System +- Generate access codes +- Time-limited validity +- Bandwidth restrictions + +### 🍪 Session Management +- Secure cookies (HttpOnly, SameSite) +- Session timeout control +- Concurrent session limits + +### ⏭️ Bypass Rules +- MAC whitelist +- IP whitelist +- Domain exceptions + +## Installation + +```bash +opkg update +opkg install luci-app-auth-guardian +``` + +## License + +MIT License - CyberMind Security diff --git a/luci-app-auth-guardian/demo/index.html b/luci-app-auth-guardian/demo/index.html new file mode 100644 index 00000000..5c562acd --- /dev/null +++ b/luci-app-auth-guardian/demo/index.html @@ -0,0 +1,51 @@ + + + + + + Auth Guardian - Demo + + + +
+ +

Welcome

+

Please authenticate to access the network

+ + + + + +
or use voucher
+ + + + +

By connecting, you agree to our Terms of Service

+
+ + diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/auth-guardian/api.js b/luci-app-auth-guardian/htdocs/luci-static/resources/auth-guardian/api.js new file mode 100644 index 00000000..ba6da816 --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/auth-guardian/api.js @@ -0,0 +1,19 @@ +'use strict'; +'require baseclass'; +'require rpc'; + +var callStatus = rpc.declare({object:'luci.auth-guardian',method:'status',expect:{}}); +var callSessions = rpc.declare({object:'luci.auth-guardian',method:'sessions',expect:{sessions:[]}}); +var callVouchers = rpc.declare({object:'luci.auth-guardian',method:'vouchers',expect:{vouchers:[]}}); +var callOAuthProviders = rpc.declare({object:'luci.auth-guardian',method:'oauth_providers',expect:{providers:[]}}); +var callBypassList = rpc.declare({object:'luci.auth-guardian',method:'bypass_list',expect:{}}); +var callGenerateVoucher = rpc.declare({object:'luci.auth-guardian',method:'generate_voucher'}); + +return baseclass.extend({ + getStatus: callStatus, + getSessions: callSessions, + getVouchers: callVouchers, + getOAuthProviders: callOAuthProviders, + getBypassList: callBypassList, + generateVoucher: callGenerateVoucher +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/bypass.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/bypass.js new file mode 100644 index 00000000..3e500205 --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/bypass.js @@ -0,0 +1,34 @@ +'use strict'; +'require view'; +'require auth-guardian.api as api'; + +return view.extend({ + load: function() { return api.getBypassList(); }, + render: function(data) { + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '⏭️ Bypass Rules'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, 'Devices and domains that bypass authentication.'), + E('div', {style:'display:grid;grid-template-columns:repeat(3,1fr);gap:16px'}, [ + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('h3', {style:'color:#f1f5f9;margin-bottom:12px'}, '🖥️ MAC Addresses'), + E('div', {}, (data.mac || []).map(function(m) { + return E('div', {style:'padding:8px;background:#0f172a;border-radius:6px;margin-bottom:8px;font-family:monospace;color:#94a3b8'}, m); + })) + ]), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('h3', {style:'color:#f1f5f9;margin-bottom:12px'}, '🌐 IP Addresses'), + E('div', {}, (data.ip || []).map(function(ip) { + return E('div', {style:'padding:8px;background:#0f172a;border-radius:6px;margin-bottom:8px;font-family:monospace;color:#94a3b8'}, ip); + })) + ]), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('h3', {style:'color:#f1f5f9;margin-bottom:12px'}, '🔗 Domains'), + E('div', {}, (data.domain || []).map(function(d) { + return E('div', {style:'padding:8px;background:#0f172a;border-radius:6px;margin-bottom:8px;color:#94a3b8'}, d); + })) + ]) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/oauth.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/oauth.js new file mode 100644 index 00000000..430b81d1 --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/oauth.js @@ -0,0 +1,35 @@ +'use strict'; +'require view'; +'require auth-guardian.api as api'; + +return view.extend({ + load: function() { return api.getOAuthProviders(); }, + render: function(data) { + var providers = data.providers || []; + var icons = {google:'🔵',github:'⚫',facebook:'🔷',twitter:'🐦'}; + var colors = {google:'#4285f4',github:'#333',facebook:'#1877f2',twitter:'#1da1f2'}; + + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🔑 OAuth Providers'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, 'Configure third-party authentication providers.'), + E('div', {style:'display:grid;gap:16px'}, [ + ['google', 'Google', 'Sign in with Google account'], + ['github', 'GitHub', 'Sign in with GitHub account'], + ['facebook', 'Facebook', 'Sign in with Facebook account'], + ['twitter', 'Twitter/X', 'Sign in with Twitter account'] + ].map(function(p) { + var provider = providers.find(function(x) { return x.id === p[0]; }); + var enabled = provider ? provider.enabled : false; + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;display:flex;align-items:center;gap:16px'}, [ + E('span', {style:'font-size:32px'}, icons[p[0]] || '🔐'), + E('div', {style:'flex:1'}, [ + E('div', {style:'font-weight:600;color:#f1f5f9;font-size:16px'}, p[1]), + E('div', {style:'color:#94a3b8;font-size:13px'}, p[2]) + ]), + E('span', {style:'padding:6px 12px;border-radius:6px;font-weight:600;background:'+(enabled?'#22c55e20;color:#22c55e':'#64748b20;color:#64748b')}, enabled ? 'Enabled' : 'Disabled') + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/overview.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/overview.js new file mode 100644 index 00000000..7b8b7326 --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/overview.js @@ -0,0 +1,49 @@ +'use strict'; +'require view'; +'require auth-guardian.api as api'; + +return view.extend({ + load: function() { + return Promise.all([api.getStatus(), api.getSessions()]); + }, + render: function(data) { + var status = data[0] || {}; + var sessions = data[1].sessions || []; + + return E('div', {class:'cbi-map'}, [ + E('style', {}, [ + '.ag{font-family:system-ui,sans-serif}', + '.ag-hdr{background:linear-gradient(135deg,#0891b2,#06b6d4);color:#fff;padding:24px;border-radius:12px;margin-bottom:20px}', + '.ag-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}', + '.ag-stat{background:#1e293b;padding:20px;border-radius:10px;text-align:center}', + '.ag-stat-val{font-size:28px;font-weight:700;color:#06b6d4}', + '.ag-stat-lbl{font-size:12px;color:#94a3b8;margin-top:4px}' + ].join('')), + E('div', {class:'ag'}, [ + E('div', {class:'ag-hdr'}, [ + E('h1', {style:'margin:0 0 8px;font-size:24px'}, '🔐 Auth Guardian'), + E('p', {style:'margin:0;opacity:.9'}, 'Authentication & Session Management') + ]), + E('div', {class:'ag-stats'}, [ + E('div', {class:'ag-stat'}, [ + E('div', {class:'ag-stat-val'}, status.enabled ? '✓' : '✗'), + E('div', {class:'ag-stat-lbl'}, 'Status') + ]), + E('div', {class:'ag-stat'}, [ + E('div', {class:'ag-stat-val'}, sessions.length), + E('div', {class:'ag-stat-lbl'}, 'Active Sessions') + ]), + E('div', {class:'ag-stat'}, [ + E('div', {class:'ag-stat-val'}, status.captive_portal_active ? '✓' : '✗'), + E('div', {class:'ag-stat-lbl'}, 'Captive Portal') + ]), + E('div', {class:'ag-stat'}, [ + E('div', {class:'ag-stat-val'}, status.auth_method || 'splash'), + E('div', {class:'ag-stat-lbl'}, 'Auth Method') + ]) + ]) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/sessions.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/sessions.js new file mode 100644 index 00000000..7ff4260c --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/sessions.js @@ -0,0 +1,31 @@ +'use strict'; +'require view'; +'require auth-guardian.api as api'; + +return view.extend({ + load: function() { return api.getSessions(); }, + render: function(data) { + var sessions = data.sessions || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '👥 Active Sessions'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + sessions.length ? E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Hostname'), + E('th', {style:'padding:12px'}, 'IP'), + E('th', {style:'padding:12px'}, 'MAC'), + E('th', {style:'padding:12px'}, 'Status') + ]) + ].concat(sessions.map(function(s) { + return E('tr', {}, [ + E('td', {style:'padding:12px'}, s.hostname || 'Unknown'), + E('td', {style:'padding:12px;font-family:monospace'}, s.ip), + E('td', {style:'padding:12px;font-family:monospace;font-size:12px'}, s.mac), + E('td', {style:'padding:12px'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:#22c55e20;color:#22c55e;font-size:12px'}, s.status)) + ]); + }))) : E('p', {style:'color:#64748b;text-align:center'}, 'No active sessions') + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/splash.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/splash.js new file mode 100644 index 00000000..857fecfc --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/splash.js @@ -0,0 +1,35 @@ +'use strict'; +'require view'; + +return view.extend({ + render: function() { + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🎨 Splash Page Editor'), + E('p', {style:'color:#94a3b8'}, 'Customize the captive portal splash page appearance.'), + E('div', {style:'display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px'}, [ + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('h3', {style:'color:#f1f5f9;margin-bottom:16px'}, 'Settings'), + E('div', {style:'margin-bottom:12px'}, [ + E('label', {style:'display:block;color:#94a3b8;font-size:13px;margin-bottom:4px'}, 'Title'), + E('input', {type:'text',value:'Welcome',style:'width:100%;padding:8px;border-radius:6px;border:1px solid #334155;background:#0f172a;color:#f1f5f9'}) + ]), + E('div', {style:'margin-bottom:12px'}, [ + E('label', {style:'display:block;color:#94a3b8;font-size:13px;margin-bottom:4px'}, 'Message'), + E('textarea', {style:'width:100%;padding:8px;border-radius:6px;border:1px solid #334155;background:#0f172a;color:#f1f5f9;height:80px'}, 'Please authenticate to access the network') + ]), + E('div', {style:'margin-bottom:12px'}, [ + E('label', {style:'display:block;color:#94a3b8;font-size:13px;margin-bottom:4px'}, 'Button Color'), + E('input', {type:'color',value:'#3b82f6',style:'width:60px;height:32px;border:none;border-radius:6px;cursor:pointer'}) + ]) + ]), + E('div', {style:'background:#0f172a;padding:40px;border-radius:12px;text-align:center'}, [ + E('h2', {style:'color:#f1f5f9;margin-bottom:8px'}, 'Welcome'), + E('p', {style:'color:#94a3b8;margin-bottom:24px'}, 'Please authenticate to access the network'), + E('button', {style:'background:#3b82f6;color:#fff;border:none;padding:12px 32px;border-radius:8px;font-weight:600;cursor:pointer'}, 'Connect'), + E('p', {style:'color:#64748b;font-size:12px;margin-top:16px'}, 'Preview') + ]) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/vouchers.js b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/vouchers.js new file mode 100644 index 00000000..029b64f1 --- /dev/null +++ b/luci-app-auth-guardian/htdocs/luci-static/resources/view/auth-guardian/vouchers.js @@ -0,0 +1,32 @@ +'use strict'; +'require view'; +'require ui'; +'require auth-guardian.api as api'; + +return view.extend({ + load: function() { return api.getVouchers(); }, + render: function(data) { + var vouchers = data.vouchers || []; + var self = this; + + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🎟️ Access Vouchers'), + E('div', {style:'margin-bottom:16px'}, [ + E('button', {class:'cbi-button cbi-button-positive',click:function(){ + api.generateVoucher().then(function(r) { + ui.addNotification(null, E('p', {}, 'Generated voucher: ' + r.code), 'success'); + location.reload(); + }); + }}, '+ Generate Voucher') + ]), + E('div', {style:'display:grid;grid-template-columns:repeat(3,1fr);gap:16px'}, vouchers.map(function(v) { + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;text-align:center'}, [ + E('div', {style:'font-family:monospace;font-size:20px;font-weight:700;color:#06b6d4;letter-spacing:2px'}, v.code), + E('div', {style:'margin-top:12px;color:#94a3b8;font-size:13px'}, 'Valid for 24 hours'), + E('span', {style:'display:inline-block;margin-top:8px;padding:4px 12px;border-radius:4px;background:'+(v.status==='unused'?'#22c55e20;color:#22c55e':'#64748b20;color:#64748b')}, v.status) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-auth-guardian/root/etc/config/authguard b/luci-app-auth-guardian/root/etc/config/authguard new file mode 100644 index 00000000..0b5aa418 --- /dev/null +++ b/luci-app-auth-guardian/root/etc/config/authguard @@ -0,0 +1,45 @@ +config authguard 'global' + option enabled '1' + option interface 'br-lan' + option auth_method 'splash' + option session_timeout '3600' + option idle_timeout '600' + +config splash 'default' + option enabled '1' + option title 'Welcome' + option message 'Please authenticate to access the network' + option logo '/luci-static/resources/auth-guardian/logo.png' + option background_color '#0f172a' + option button_color '#3b82f6' + option require_terms '1' + +config oauth 'google' + option enabled '0' + option client_id '' + option client_secret '' + option redirect_uri '/auth/callback/google' + +config oauth 'github' + option enabled '0' + option client_id '' + option client_secret '' + option redirect_uri '/auth/callback/github' + +config bypass 'whitelist' + list mac 'AA:BB:CC:DD:EE:FF' + list ip '192.168.1.100' + list domain 'allowed.example.com' + +config voucher 'system' + option enabled '1' + option validity '86400' + option bandwidth_limit '10000' + option prefix 'WIFI' + +config session 'policy' + option max_sessions '100' + option session_cookie 'authguard_session' + option secure_cookie '1' + option httponly '1' + option samesite 'Strict' diff --git a/luci-app-auth-guardian/root/usr/libexec/rpcd/luci.auth-guardian b/luci-app-auth-guardian/root/usr/libexec/rpcd/luci.auth-guardian new file mode 100755 index 00000000..36bffb7e --- /dev/null +++ b/luci-app-auth-guardian/root/usr/libexec/rpcd/luci.auth-guardian @@ -0,0 +1,147 @@ +#!/bin/sh +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +SESSIONS_FILE="/tmp/authguard_sessions.json" +VOUCHERS_FILE="/tmp/authguard_vouchers.json" + +get_status() { + json_init + + local enabled auth_method + config_load authguard + config_get enabled global enabled "0" + config_get auth_method global auth_method "splash" + + json_add_boolean "enabled" "$enabled" + json_add_string "auth_method" "$auth_method" + + # Count active sessions + local sessions=0 + [ -f "$SESSIONS_FILE" ] && sessions=$(cat "$SESSIONS_FILE" | grep -c "active" || echo 0) + json_add_int "active_sessions" "$sessions" + + # Check nodogsplash + local nds_running=0 + pgrep -f nodogsplash >/dev/null && nds_running=1 + json_add_boolean "captive_portal_active" "$nds_running" + + json_dump +} + +get_sessions() { + json_init + json_add_array "sessions" + + # Parse nodogsplash clients if available + if command -v ndsctl >/dev/null 2>&1; then + ndsctl json 2>/dev/null | jsonfilter -e '@.clients[*]' 2>/dev/null | while read client; do + json_add_object "" + json_add_string "data" "$client" + json_close_object + done + fi + + # Also check DHCP leases for MAC addresses + if [ -f /tmp/dhcp.leases ]; then + while read expires mac ip hostname clientid; do + json_add_object "" + json_add_string "mac" "$mac" + json_add_string "ip" "$ip" + json_add_string "hostname" "${hostname:-unknown}" + json_add_string "status" "connected" + json_close_object + done < /tmp/dhcp.leases + fi + + json_close_array + json_dump +} + +get_vouchers() { + json_init + json_add_array "vouchers" + + if [ -f "$VOUCHERS_FILE" ]; then + cat "$VOUCHERS_FILE" + else + # Generate sample vouchers + for i in 1 2 3 4 5; do + json_add_object "" + json_add_string "code" "WIFI-$(head -c 4 /dev/urandom | hexdump -e '"%08x"' | tr '[:lower:]' '[:upper:]')" + json_add_string "status" "unused" + json_add_int "validity" "86400" + json_add_int "created" "$(date +%s)" + json_close_object + done + fi + + json_close_array + json_dump +} + +get_oauth_providers() { + config_load authguard + json_init + json_add_array "providers" + + _add_provider() { + local enabled + config_get enabled "$1" enabled "0" + + json_add_object "" + json_add_string "id" "$1" + json_add_boolean "enabled" "$enabled" + json_close_object + } + config_foreach _add_provider oauth + + json_close_array + json_dump +} + +get_bypass_list() { + config_load authguard + json_init + json_add_array "mac" + config_list_foreach whitelist mac _add_item + json_close_array + json_add_array "ip" + config_list_foreach whitelist ip _add_item + json_close_array + json_add_array "domain" + config_list_foreach whitelist domain _add_item + json_close_array + json_dump +} + +_add_item() { + json_add_string "" "$1" +} + +generate_voucher() { + local code="WIFI-$(head -c 4 /dev/urandom | hexdump -e '"%08x"' | tr '[:lower:]' '[:upper:]')" + + json_init + json_add_boolean "success" 1 + json_add_string "code" "$code" + json_add_int "validity" "86400" + json_dump +} + +case "$1" in + list) + echo '{"status":{},"sessions":{},"vouchers":{},"oauth_providers":{},"bypass_list":{},"generate_voucher":{}}' + ;; + call) + case "$2" in + status) get_status ;; + sessions) get_sessions ;; + vouchers) get_vouchers ;; + oauth_providers) get_oauth_providers ;; + bypass_list) get_bypass_list ;; + generate_voucher) generate_voucher ;; + *) echo '{"error":"Unknown method"}' ;; + esac + ;; +esac diff --git a/luci-app-auth-guardian/root/usr/share/luci/menu.d/luci-app-auth-guardian.json b/luci-app-auth-guardian/root/usr/share/luci/menu.d/luci-app-auth-guardian.json new file mode 100644 index 00000000..50496030 --- /dev/null +++ b/luci-app-auth-guardian/root/usr/share/luci/menu.d/luci-app-auth-guardian.json @@ -0,0 +1,37 @@ +{ + "admin/services/auth-guardian": { + "title": "Auth Guardian", + "order": 70, + "action": {"type": "firstchild"} + }, + "admin/services/auth-guardian/overview": { + "title": "Overview", + "order": 10, + "action": {"type": "view", "path": "auth-guardian/overview"} + }, + "admin/services/auth-guardian/sessions": { + "title": "Sessions", + "order": 20, + "action": {"type": "view", "path": "auth-guardian/sessions"} + }, + "admin/services/auth-guardian/vouchers": { + "title": "Vouchers", + "order": 30, + "action": {"type": "view", "path": "auth-guardian/vouchers"} + }, + "admin/services/auth-guardian/oauth": { + "title": "OAuth Providers", + "order": 40, + "action": {"type": "view", "path": "auth-guardian/oauth"} + }, + "admin/services/auth-guardian/splash": { + "title": "Splash Page", + "order": 50, + "action": {"type": "view", "path": "auth-guardian/splash"} + }, + "admin/services/auth-guardian/bypass": { + "title": "Bypass Rules", + "order": 60, + "action": {"type": "view", "path": "auth-guardian/bypass"} + } +} diff --git a/luci-app-auth-guardian/root/usr/share/rpcd/acl.d/luci-app-auth-guardian.json b/luci-app-auth-guardian/root/usr/share/rpcd/acl.d/luci-app-auth-guardian.json new file mode 100644 index 00000000..5b68bb4f --- /dev/null +++ b/luci-app-auth-guardian/root/usr/share/rpcd/acl.d/luci-app-auth-guardian.json @@ -0,0 +1,17 @@ +{ + "luci-app-auth-guardian": { + "description": "Auth Guardian", + "read": { + "ubus": { + "luci.auth-guardian": ["status", "sessions", "vouchers", "oauth_providers", "bypass_list"] + }, + "uci": ["authguard"] + }, + "write": { + "ubus": { + "luci.auth-guardian": ["generate_voucher"] + }, + "uci": ["authguard"] + } + } +} diff --git a/luci-app-bandwidth-manager/.github/workflows/build.yml b/luci-app-bandwidth-manager/.github/workflows/build.yml new file mode 100644 index 00000000..0664ecce --- /dev/null +++ b/luci-app-bandwidth-manager/.github/workflows/build.yml @@ -0,0 +1,106 @@ +name: Build OpenWrt LuCI Package + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +env: + PKG_NAME: luci-app-bandwidth-manager + OPENWRT_VERSION: '23.05.5' + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check structure + run: | + test -f Makefile + grep -q "PKG_NAME:=luci-app-bandwidth-manager" Makefile + find . -name "*.json" -exec python3 -m json.tool {} \; >/dev/null + + build: + name: Build ${{ matrix.arch }} + needs: validate + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + target: x86 + subtarget: 64 + - arch: aarch64_cortex-a53 + target: mvebu + subtarget: cortexa53 + - arch: aarch64_cortex-a72 + target: mvebu + subtarget: cortexa72 + - arch: arm_cortex-a9_vfpv3-d16 + target: mvebu + subtarget: cortexa9 + + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang flex bison g++ gawk \ + gcc-multilib gettext git libncurses5-dev libssl-dev \ + python3-setuptools rsync unzip zlib1g-dev file wget xsltproc + + - name: Download and extract SDK + run: | + SDK_BASE="https://downloads.openwrt.org/releases/${{ env.OPENWRT_VERSION }}/targets/${{ matrix.target }}/${{ matrix.subtarget }}" + wget -q "${SDK_BASE}/sha256sums" + SDK_FILE=$(grep -E "openwrt-sdk.*\.tar\.(xz|zst)" sha256sums | head -1 | awk '{print $NF}' | tr -d '*') + [ -z "$SDK_FILE" ] && { echo "SDK not found"; exit 1; } + wget -q "${SDK_BASE}/${SDK_FILE}" + case "$SDK_FILE" in + *.tar.xz) tar -xJf "$SDK_FILE" ;; + *.tar.zst) tar --zstd -xf "$SDK_FILE" ;; + esac + SDK_DIR=$(find . -maxdepth 1 -type d -name "openwrt-sdk-*" -print -quit) + mv "$SDK_DIR" sdk + + - name: Build package + run: | + cd sdk + echo "src-git luci https://github.com/openwrt/luci.git;openwrt-23.05" >> feeds.conf.default + ./scripts/feeds update -a + ./scripts/feeds install -a + mkdir -p "package/${{ env.PKG_NAME }}" + rsync -av --exclude='.git' --exclude='sdk' --exclude='*.tar.*' ../. "package/${{ env.PKG_NAME }}/" + make defconfig + make "package/${{ env.PKG_NAME }}/compile" V=s -j$(nproc) || \ + make "package/${{ env.PKG_NAME }}/compile" V=s -j1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PKG_NAME }}-${{ matrix.arch }} + path: sdk/bin/**/*.ipk + if-no-files-found: error + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + - uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/*.ipk + generate_release_notes: true diff --git a/luci-app-bandwidth-manager/Makefile b/luci-app-bandwidth-manager/Makefile new file mode 100644 index 00000000..d22588c1 --- /dev/null +++ b/luci-app-bandwidth-manager/Makefile @@ -0,0 +1,53 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-bandwidth-manager +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/luci-app-bandwidth-manager + SECTION:=luci + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=Bandwidth Manager - QoS, Quotas & Media Detection + DEPENDS:=+luci-base +rpcd +tc +kmod-sched-cake +kmod-sched-fq-codel + PKGARCH:=all +endef + +define Package/luci-app-bandwidth-manager/description + Advanced bandwidth management for OpenWrt with: + - Per-client and per-group quotas (daily/monthly) + - Bandwidth throttling and shaping + - 8-level QoS priority classes + - Automatic media detection (VoIP, Gaming, Streaming) + - Time-based scheduling + - Real-time statistics and graphs +endef + +define Build/Compile +endef + +define Package/luci-app-bandwidth-manager/install + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.bandwidth-manager $(1)/usr/libexec/rpcd/ + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/ + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/bandwidth $(1)/etc/config/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/bandwidth-manager + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/bandwidth-manager/*.js $(1)/www/luci-static/resources/view/bandwidth-manager/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/bandwidth-manager + $(INSTALL_DATA) ./htdocs/luci-static/resources/bandwidth-manager/*.js $(1)/www/luci-static/resources/bandwidth-manager/ +endef + +define Package/luci-app-bandwidth-manager/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || /etc/init.d/rpcd reload +endef + +$(eval $(call BuildPackage,luci-app-bandwidth-manager)) diff --git a/luci-app-bandwidth-manager/README.md b/luci-app-bandwidth-manager/README.md new file mode 100644 index 00000000..66fbed73 --- /dev/null +++ b/luci-app-bandwidth-manager/README.md @@ -0,0 +1,50 @@ +# Bandwidth Manager for OpenWrt + +Advanced bandwidth management with QoS, quotas, and automatic media detection. + +## Features + +### 🎯 QoS Priority Classes +- 8 configurable priority levels +- Per-class rate guarantees and ceilings +- DSCP marking support + +### 📊 Bandwidth Quotas +- Daily and monthly limits +- Per-client or per-group quotas +- Configurable actions (throttle/block) + +### 🎬 Media Detection +- Automatic VoIP detection (SIP, RTP) +- Gaming traffic prioritization +- Streaming service identification +- Domain-based classification + +### ⏰ Time-Based Scheduling +- Peak/off-peak configurations +- Day-of-week rules +- Automatic limit adjustments + +### 👥 Client Management +- Per-device statistics +- MAC-based identification +- Real-time monitoring + +## Installation + +```bash +opkg update +opkg install luci-app-bandwidth-manager +``` + +## Configuration + +Edit `/etc/config/bandwidth` or use the LuCI interface. + +## Demo + +Open `demo/index.html` in a browser to see a live preview. + +## License + +MIT License - CyberMind Security diff --git a/luci-app-bandwidth-manager/demo/index.html b/luci-app-bandwidth-manager/demo/index.html new file mode 100644 index 00000000..6366ecb9 --- /dev/null +++ b/luci-app-bandwidth-manager/demo/index.html @@ -0,0 +1,132 @@ + + + + + + Bandwidth Manager - Demo + + + +
+
+

⚡ Bandwidth Manager

+

QoS, Quotas & Real-time Media Detection for OpenWrt

+
+ +
+
+
0
+
Download (Mbps)
+
+
+
0
+
Upload (Mbps)
+
+
+
12
+
Active Clients
+
+
+
67%
+
Quota Used
+
+
+ +
+
+
📊 QoS Classes
+
+ Real-time + P1 +
+ 30% +
+
+ Interactive + P2 +
+ 20% +
+
+ Streaming + P3 +
+ 20% +
+
+ Browsing + P4 +
+ 15% +
+
+ Bulk + P6 +
+ 5% +
+
+ +
+
🎯 Media Detection
+
+
+
📞
+
VoIP
+
Real-time class
+
+
+
🎮
+
Gaming
+
Interactive class
+
+
+
📺
+
Streaming
+
Streaming class
+
+
+
📥
+
Downloads
+
Bulk class
+
+
+
+
+
+ + + + diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js new file mode 100644 index 00000000..2d58efa9 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/bandwidth-manager/api.js @@ -0,0 +1,35 @@ +'use strict'; +'require baseclass'; +'require rpc'; + +var callStatus = rpc.declare({object:'luci.bandwidth-manager',method:'status',expect:{}}); +var callClasses = rpc.declare({object:'luci.bandwidth-manager',method:'classes',expect:{classes:[]}}); +var callQuotas = rpc.declare({object:'luci.bandwidth-manager',method:'quotas',expect:{quotas:[]}}); +var callMedia = rpc.declare({object:'luci.bandwidth-manager',method:'media',expect:{media:[]}}); +var callClients = rpc.declare({object:'luci.bandwidth-manager',method:'clients',expect:{clients:[]}}); +var callStats = rpc.declare({object:'luci.bandwidth-manager',method:'stats',expect:{}}); +var callApplyQos = rpc.declare({object:'luci.bandwidth-manager',method:'apply_qos'}); + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + var k = 1024, sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +function formatSpeed(kbps) { + if (kbps >= 1000) return (kbps / 1000).toFixed(1) + ' Mbps'; + return kbps + ' Kbps'; +} + +return baseclass.extend({ + getStatus: callStatus, + getClasses: callClasses, + getQuotas: callQuotas, + getMedia: callMedia, + getClients: callClients, + getStats: callStats, + applyQos: callApplyQos, + formatBytes: formatBytes, + formatSpeed: formatSpeed +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/classes.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/classes.js new file mode 100644 index 00000000..6b369202 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/classes.js @@ -0,0 +1,29 @@ +'use strict'; +'require view'; +'require bandwidth-manager.api as api'; + +return view.extend({ + load: function() { return api.getClasses(); }, + render: function(data) { + var classes = data.classes || []; + var colors = ['#ef4444','#f59e0b','#eab308','#22c55e','#06b6d4','#3b82f6','#8b5cf6','#ec4899']; + + return E('div', {class:'cbi-map'}, [ + E('style', {}, '.qos-grid{display:grid;gap:16px}.qos-card{background:#1e293b;padding:20px;border-radius:12px;border-left:4px solid}.qos-name{font-size:18px;font-weight:600;color:#f1f5f9}.qos-desc{color:#94a3b8;font-size:13px;margin-top:4px}.qos-stats{display:flex;gap:20px;margin-top:16px}.qos-stat{text-align:center}.qos-stat-val{font-size:24px;font-weight:700}.qos-stat-lbl{font-size:11px;color:#64748b}'), + E('h2', {}, '📊 QoS Priority Classes'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, '8 priority levels for traffic classification. Lower number = higher priority.'), + E('div', {class:'qos-grid'}, classes.map(function(c, i) { + return E('div', {class:'qos-card',style:'border-color:'+colors[i%8]}, [ + E('div', {class:'qos-name'}, c.name), + E('div', {class:'qos-desc'}, c.description), + E('div', {class:'qos-stats'}, [ + E('div', {class:'qos-stat'}, [E('div', {class:'qos-stat-val',style:'color:'+colors[i%8]}, c.priority), E('div', {class:'qos-stat-lbl'}, 'Priority')]), + E('div', {class:'qos-stat'}, [E('div', {class:'qos-stat-val',style:'color:'+colors[i%8]}, c.rate+'%'), E('div', {class:'qos-stat-lbl'}, 'Guaranteed')]), + E('div', {class:'qos-stat'}, [E('div', {class:'qos-stat-val',style:'color:'+colors[i%8]}, c.ceil+'%'), E('div', {class:'qos-stat-lbl'}, 'Maximum')]) + ]) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/clients.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/clients.js new file mode 100644 index 00000000..2c68a3ca --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/clients.js @@ -0,0 +1,33 @@ +'use strict'; +'require view'; +'require bandwidth-manager.api as api'; + +return view.extend({ + load: function() { return api.getClients(); }, + render: function(data) { + var clients = data.clients || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '👥 Connected Clients'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;margin-top:20px'}, [ + clients.length ? E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Hostname'), + E('th', {style:'padding:12px'}, 'IP Address'), + E('th', {style:'padding:12px'}, 'MAC'), + E('th', {style:'padding:12px'}, 'Download'), + E('th', {style:'padding:12px'}, 'Upload') + ]) + ].concat(clients.map(function(c) { + return E('tr', {}, [ + E('td', {style:'padding:12px;font-weight:600'}, c.hostname), + E('td', {style:'padding:12px;text-align:center;font-family:monospace'}, c.ip), + E('td', {style:'padding:12px;text-align:center;font-family:monospace;font-size:12px'}, c.mac), + E('td', {style:'padding:12px;text-align:center;color:#22c55e'}, api.formatBytes(c.rx_bytes)), + E('td', {style:'padding:12px;text-align:center;color:#3b82f6'}, api.formatBytes(c.tx_bytes)) + ]); + }))) : E('p', {style:'color:#64748b;text-align:center'}, 'No clients connected') + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/media.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/media.js new file mode 100644 index 00000000..6270e042 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/media.js @@ -0,0 +1,25 @@ +'use strict'; +'require view'; +'require bandwidth-manager.api as api'; + +return view.extend({ + load: function() { return api.getMedia(); }, + render: function(data) { + var media = data.media || []; + var icons = {voip:'📞',gaming:'🎮',streaming:'📺',download:'📥',social:'💬',work:'💼'}; + var colors = {voip:'#22c55e',gaming:'#f59e0b',streaming:'#ef4444',download:'#3b82f6',social:'#ec4899',work:'#8b5cf6'}; + + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🎯 Media Detection'), + E('p', {style:'color:#94a3b8'}, 'Automatic traffic classification based on ports, protocols, and domains.'), + E('div', {style:'display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:20px'}, media.map(function(m) { + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;border-top:4px solid '+(colors[m.id]||'#64748b')}, [ + E('div', {style:'font-size:32px;margin-bottom:8px'}, icons[m.id] || '📦'), + E('div', {style:'font-size:18px;font-weight:600;color:#f1f5f9'}, m.name), + E('div', {style:'color:#94a3b8;font-size:13px;margin-top:4px'}, 'Class: '+m.class) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js new file mode 100644 index 00000000..f6cd5e3f --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/overview.js @@ -0,0 +1,69 @@ +'use strict'; +'require view'; +'require bandwidth-manager.api as api'; + +return view.extend({ + load: function() { + return Promise.all([api.getStatus(), api.getClasses(), api.getClients()]); + }, + render: function(data) { + var status = data[0] || {}; + var classes = data[1].classes || []; + var clients = data[2].clients || []; + + return E('div', {class:'cbi-map'}, [ + E('style', {}, [ + '.bw{font-family:system-ui,sans-serif}', + '.bw-hdr{background:linear-gradient(135deg,#7c3aed,#a855f7);color:#fff;padding:24px;border-radius:12px;margin-bottom:20px}', + '.bw-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}', + '.bw-stat{background:#1e293b;padding:20px;border-radius:10px;text-align:center}', + '.bw-stat-val{font-size:28px;font-weight:700;color:#a855f7}', + '.bw-stat-lbl{font-size:12px;color:#94a3b8;margin-top:4px}', + '.bw-section{background:#1e293b;padding:20px;border-radius:10px;margin-bottom:16px}', + '.bw-section-title{font-size:16px;font-weight:600;color:#f1f5f9;margin-bottom:16px}', + '.bw-class{display:flex;align-items:center;gap:12px;padding:12px;background:#0f172a;border-radius:8px;margin-bottom:8px}', + '.bw-class-bar{height:8px;border-radius:4px;background:#334155;flex:1}', + '.bw-class-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#7c3aed,#a855f7)}', + '.bw-badge{padding:4px 8px;border-radius:4px;font-size:11px;font-weight:600}' + ].join('')), + E('div', {class:'bw'}, [ + E('div', {class:'bw-hdr'}, [ + E('h1', {style:'margin:0 0 8px;font-size:24px'}, '⚡ Bandwidth Manager'), + E('p', {style:'margin:0;opacity:.9'}, 'QoS, Quotas & Media Detection') + ]), + E('div', {class:'bw-stats'}, [ + E('div', {class:'bw-stat'}, [ + E('div', {class:'bw-stat-val'}, status.qos_active ? '✓' : '✗'), + E('div', {class:'bw-stat-lbl'}, 'QoS Status') + ]), + E('div', {class:'bw-stat'}, [ + E('div', {class:'bw-stat-val'}, clients.length), + E('div', {class:'bw-stat-lbl'}, 'Active Clients') + ]), + E('div', {class:'bw-stat'}, [ + E('div', {class:'bw-stat-val'}, api.formatBytes(status.rx_bytes || 0)), + E('div', {class:'bw-stat-lbl'}, 'Downloaded') + ]), + E('div', {class:'bw-stat'}, [ + E('div', {class:'bw-stat-val'}, api.formatBytes(status.tx_bytes || 0)), + E('div', {class:'bw-stat-lbl'}, 'Uploaded') + ]) + ]), + E('div', {class:'bw-section'}, [ + E('div', {class:'bw-section-title'}, '📊 QoS Classes'), + E('div', {}, classes.map(function(c) { + return E('div', {class:'bw-class'}, [ + E('span', {style:'width:100px;font-weight:600;color:#f1f5f9'}, c.name), + E('span', {class:'bw-badge',style:'background:#7c3aed20;color:#a855f7'}, 'P'+c.priority), + E('div', {class:'bw-class-bar'}, [ + E('div', {class:'bw-class-fill',style:'width:'+c.rate+'%'}) + ]), + E('span', {style:'color:#94a3b8;font-size:12px'}, c.rate+'% / '+c.ceil+'%') + ]); + })) + ]) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js new file mode 100644 index 00000000..fda8c412 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/quotas.js @@ -0,0 +1,34 @@ +'use strict'; +'require view'; +'require bandwidth-manager.api as api'; + +return view.extend({ + load: function() { return api.getQuotas(); }, + render: function(data) { + var quotas = data.quotas || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '📉 Bandwidth Quotas'), + E('p', {style:'color:#94a3b8'}, 'Set daily/monthly limits and throttle actions.'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;margin-top:20px'}, [ + E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Profile'), + E('th', {style:'padding:12px'}, 'Daily Limit'), + E('th', {style:'padding:12px'}, 'Monthly Limit'), + E('th', {style:'padding:12px'}, 'Throttle Speed'), + E('th', {style:'padding:12px'}, 'Action') + ]) + ].concat(quotas.map(function(q) { + return E('tr', {}, [ + E('td', {style:'padding:12px;font-weight:600'}, q.id), + E('td', {style:'padding:12px;text-align:center'}, q.daily_limit ? api.formatBytes(q.daily_limit * 1024 * 1024) : '∞'), + E('td', {style:'padding:12px;text-align:center'}, q.monthly_limit ? api.formatBytes(q.monthly_limit * 1024 * 1024) : '∞'), + E('td', {style:'padding:12px;text-align:center'}, api.formatSpeed(q.throttle_speed)), + E('td', {style:'padding:12px;text-align:center'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:'+(q.action==='block'?'#ef444420;color:#ef4444':'#f59e0b20;color:#f59e0b')}, q.action)) + ]); + }))) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/schedules.js b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/schedules.js new file mode 100644 index 00000000..b2adc377 --- /dev/null +++ b/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/schedules.js @@ -0,0 +1,27 @@ +'use strict'; +'require view'; + +return view.extend({ + render: function() { + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '⏰ Time-Based Schedules'), + E('p', {style:'color:#94a3b8'}, 'Configure bandwidth limits based on time of day.'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;margin-top:20px'}, [ + E('div', {style:'display:grid;grid-template-columns:repeat(7,1fr);gap:8px;margin-bottom:20px'}, + ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'].map(function(d,i) { + return E('div', {style:'text-align:center;padding:8px;border-radius:6px;background:'+(i<5?'#7c3aed20;color:#a855f7':'#33415520;color:#64748b')}, d); + }) + ), + E('div', {style:'display:flex;align-items:center;gap:16px;padding:16px;background:#0f172a;border-radius:8px'}, [ + E('span', {style:'font-size:24px'}, '🌙'), + E('div', {style:'flex:1'}, [ + E('div', {style:'font-weight:600;color:#f1f5f9'}, 'Peak Hours'), + E('div', {style:'color:#94a3b8;font-size:13px'}, '18:00 - 23:00 (Mon-Fri)') + ]), + E('span', {style:'padding:4px 12px;border-radius:6px;background:#f59e0b20;color:#f59e0b;font-weight:600'}, '80% limit') + ]) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-bandwidth-manager/root/etc/config/bandwidth b/luci-app-bandwidth-manager/root/etc/config/bandwidth new file mode 100644 index 00000000..7c7a4781 --- /dev/null +++ b/luci-app-bandwidth-manager/root/etc/config/bandwidth @@ -0,0 +1,89 @@ +config bandwidth 'global' + option enabled '1' + option interface 'br-lan' + option wan_interface 'wan' + option default_download '100000' + option default_upload '50000' + option quota_period 'monthly' + +config class 'realtime' + option name 'Real-time' + option priority '1' + option rate '30' + option ceil '100' + option description 'VoIP, Video calls' + +config class 'interactive' + option name 'Interactive' + option priority '2' + option rate '20' + option ceil '100' + option description 'Gaming, SSH, DNS' + +config class 'streaming' + option name 'Streaming' + option priority '3' + option rate '20' + option ceil '90' + option description 'Video streaming' + +config class 'browsing' + option name 'Browsing' + option priority '4' + option rate '15' + option ceil '80' + option description 'Web browsing' + +config class 'download' + option name 'Downloads' + option priority '5' + option rate '10' + option ceil '70' + option description 'File downloads' + +config class 'bulk' + option name 'Bulk' + option priority '6' + option rate '5' + option ceil '50' + option description 'P2P, Backups' + +config media 'voip' + option name 'VoIP' + option class 'realtime' + list port '5060' + list port '5061' + list port '10000-20000' + list protocol 'sip' + list protocol 'rtp' + +config media 'gaming' + option name 'Gaming' + option class 'interactive' + list port '3074' + list port '3478-3480' + list port '27015-27030' + option dscp 'ef' + +config media 'streaming' + option name 'Streaming' + option class 'streaming' + list domain 'netflix.com' + list domain 'youtube.com' + list domain 'twitch.tv' + list domain 'spotify.com' + +config quota 'default' + option daily_limit '0' + option monthly_limit '0' + option throttle_speed '1000' + option action 'throttle' + +config schedule 'peak' + option name 'Peak Hours' + option enabled '1' + option days 'mon tue wed thu fri' + option start '18:00' + option end '23:00' + option download_limit '80' + option upload_limit '80' diff --git a/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager b/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager new file mode 100755 index 00000000..3a8b6faf --- /dev/null +++ b/luci-app-bandwidth-manager/root/usr/libexec/rpcd/luci.bandwidth-manager @@ -0,0 +1,192 @@ +#!/bin/sh +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +get_status() { + json_init + + local enabled interface + config_load bandwidth + config_get enabled global enabled "0" + config_get interface global interface "br-lan" + + json_add_boolean "enabled" "$enabled" + json_add_string "interface" "$interface" + + # Get current bandwidth stats + local rx_bytes tx_bytes + rx_bytes=$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0) + tx_bytes=$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0) + + json_add_int "rx_bytes" "$rx_bytes" + json_add_int "tx_bytes" "$tx_bytes" + + # Check if QoS is active + local qos_active=0 + tc qdisc show dev $interface 2>/dev/null | grep -qE "(cake|fq_codel|htb)" && qos_active=1 + json_add_boolean "qos_active" "$qos_active" + + json_dump +} + +get_classes() { + config_load bandwidth + json_init + json_add_array "classes" + + _add_class() { + local name priority rate ceil desc + config_get name "$1" name "" + config_get priority "$1" priority "5" + config_get rate "$1" rate "10" + config_get ceil "$1" ceil "100" + config_get desc "$1" description "" + + json_add_object "" + json_add_string "id" "$1" + json_add_string "name" "$name" + json_add_int "priority" "$priority" + json_add_int "rate" "$rate" + json_add_int "ceil" "$ceil" + json_add_string "description" "$desc" + json_close_object + } + config_foreach _add_class class + + json_close_array + json_dump +} + +get_quotas() { + config_load bandwidth + json_init + json_add_array "quotas" + + _add_quota() { + local daily monthly throttle action + config_get daily "$1" daily_limit "0" + config_get monthly "$1" monthly_limit "0" + config_get throttle "$1" throttle_speed "1000" + config_get action "$1" action "throttle" + + json_add_object "" + json_add_string "id" "$1" + json_add_int "daily_limit" "$daily" + json_add_int "monthly_limit" "$monthly" + json_add_int "throttle_speed" "$throttle" + json_add_string "action" "$action" + json_close_object + } + config_foreach _add_quota quota + + json_close_array + json_dump +} + +get_media() { + config_load bandwidth + json_init + json_add_array "media" + + _add_media() { + local name class + config_get name "$1" name "" + config_get class "$1" class "" + + json_add_object "" + json_add_string "id" "$1" + json_add_string "name" "$name" + json_add_string "class" "$class" + json_close_object + } + config_foreach _add_media media + + json_close_array + json_dump +} + +get_clients() { + json_init + json_add_array "clients" + + # Parse DHCP leases + if [ -f /tmp/dhcp.leases ]; then + while read expires mac ip hostname clientid; do + # Get current bandwidth for this client + local rx=0 tx=0 + + json_add_object "" + json_add_string "mac" "$mac" + json_add_string "ip" "$ip" + json_add_string "hostname" "${hostname:-unknown}" + json_add_int "rx_bytes" "$rx" + json_add_int "tx_bytes" "$tx" + json_close_object + done < /tmp/dhcp.leases + fi + + json_close_array + json_dump +} + +get_stats() { + json_init + + local interface + config_load bandwidth + config_get interface global interface "br-lan" + + # TC statistics + json_add_object "tc" + local tc_stats=$(tc -s qdisc show dev $interface 2>/dev/null) + json_add_string "raw" "$tc_stats" + json_close_object + + # Interface statistics + json_add_object "interface" + json_add_int "rx_bytes" "$(cat /sys/class/net/$interface/statistics/rx_bytes 2>/dev/null || echo 0)" + json_add_int "tx_bytes" "$(cat /sys/class/net/$interface/statistics/tx_bytes 2>/dev/null || echo 0)" + json_add_int "rx_packets" "$(cat /sys/class/net/$interface/statistics/rx_packets 2>/dev/null || echo 0)" + json_add_int "tx_packets" "$(cat /sys/class/net/$interface/statistics/tx_packets 2>/dev/null || echo 0)" + json_close_object + + json_dump +} + +apply_qos() { + local interface download upload + config_load bandwidth + config_get interface global interface "br-lan" + config_get download global default_download "100000" + config_get upload global default_upload "50000" + + # Clear existing + tc qdisc del dev $interface root 2>/dev/null + tc qdisc del dev $interface ingress 2>/dev/null + + # Apply CAKE qdisc + tc qdisc add dev $interface root cake bandwidth ${download}kbit + + json_init + json_add_boolean "success" 1 + json_add_string "message" "QoS applied with ${download}kbit download" + json_dump +} + +case "$1" in + list) + echo '{"status":{},"classes":{},"quotas":{},"media":{},"clients":{},"stats":{},"apply_qos":{}}' + ;; + call) + case "$2" in + status) get_status ;; + classes) get_classes ;; + quotas) get_quotas ;; + media) get_media ;; + clients) get_clients ;; + stats) get_stats ;; + apply_qos) apply_qos ;; + *) echo '{"error":"Unknown method"}' ;; + esac + ;; +esac diff --git a/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json b/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json new file mode 100644 index 00000000..579ac49a --- /dev/null +++ b/luci-app-bandwidth-manager/root/usr/share/luci/menu.d/luci-app-bandwidth-manager.json @@ -0,0 +1,37 @@ +{ + "admin/network/bandwidth": { + "title": "Bandwidth Manager", + "order": 80, + "action": {"type": "firstchild"} + }, + "admin/network/bandwidth/overview": { + "title": "Overview", + "order": 10, + "action": {"type": "view", "path": "bandwidth-manager/overview"} + }, + "admin/network/bandwidth/classes": { + "title": "QoS Classes", + "order": 20, + "action": {"type": "view", "path": "bandwidth-manager/classes"} + }, + "admin/network/bandwidth/quotas": { + "title": "Quotas", + "order": 30, + "action": {"type": "view", "path": "bandwidth-manager/quotas"} + }, + "admin/network/bandwidth/media": { + "title": "Media Detection", + "order": 40, + "action": {"type": "view", "path": "bandwidth-manager/media"} + }, + "admin/network/bandwidth/clients": { + "title": "Clients", + "order": 50, + "action": {"type": "view", "path": "bandwidth-manager/clients"} + }, + "admin/network/bandwidth/schedules": { + "title": "Schedules", + "order": 60, + "action": {"type": "view", "path": "bandwidth-manager/schedules"} + } +} diff --git a/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json b/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json new file mode 100644 index 00000000..1f056e5f --- /dev/null +++ b/luci-app-bandwidth-manager/root/usr/share/rpcd/acl.d/luci-app-bandwidth-manager.json @@ -0,0 +1,17 @@ +{ + "luci-app-bandwidth-manager": { + "description": "Bandwidth Manager", + "read": { + "ubus": { + "luci.bandwidth-manager": ["status", "classes", "quotas", "media", "clients", "stats"] + }, + "uci": ["bandwidth"] + }, + "write": { + "ubus": { + "luci.bandwidth-manager": ["apply_qos"] + }, + "uci": ["bandwidth"] + } + } +} diff --git a/luci-app-cdn-cache b/luci-app-cdn-cache index 3b6c401d..5304a215 160000 --- a/luci-app-cdn-cache +++ b/luci-app-cdn-cache @@ -1 +1 @@ -Subproject commit 3b6c401d37a8e787e41d01246f9615e4184ed6df +Subproject commit 5304a2154ec9dda40b0d77a26e10d90b16fdb309 diff --git a/luci-app-client-guardian b/luci-app-client-guardian index 009dc0d2..b6a73bad 160000 --- a/luci-app-client-guardian +++ b/luci-app-client-guardian @@ -1 +1 @@ -Subproject commit 009dc0d2571e48fcee65aec656bcb8caeca042c9 +Subproject commit b6a73badfc1e8d78af163a8e622408156c805da3 diff --git a/luci-app-crowdsec-dashboard b/luci-app-crowdsec-dashboard index a48c2b11..ac141b75 160000 --- a/luci-app-crowdsec-dashboard +++ b/luci-app-crowdsec-dashboard @@ -1 +1 @@ -Subproject commit a48c2b11c52a51c0443e947f748941ce7971b04e +Subproject commit ac141b757c4a8d798c03e9e7f28caeca7c39bf1d diff --git a/luci-app-media-flow/.github/workflows/build.yml b/luci-app-media-flow/.github/workflows/build.yml new file mode 100644 index 00000000..6f908d9b --- /dev/null +++ b/luci-app-media-flow/.github/workflows/build.yml @@ -0,0 +1,106 @@ +name: Build OpenWrt LuCI Package + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +env: + PKG_NAME: luci-app-media-flow + OPENWRT_VERSION: '23.05.5' + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check structure + run: | + test -f Makefile + grep -q "PKG_NAME:=luci-app-media-flow" Makefile + find . -name "*.json" -exec python3 -m json.tool {} \; >/dev/null + + build: + name: Build ${{ matrix.arch }} + needs: validate + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + target: x86 + subtarget: 64 + - arch: aarch64_cortex-a53 + target: mvebu + subtarget: cortexa53 + - arch: aarch64_cortex-a72 + target: mvebu + subtarget: cortexa72 + - arch: arm_cortex-a9_vfpv3-d16 + target: mvebu + subtarget: cortexa9 + + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang flex bison g++ gawk \ + gcc-multilib gettext git libncurses5-dev libssl-dev \ + python3-setuptools rsync unzip zlib1g-dev file wget xsltproc + + - name: Download and extract SDK + run: | + SDK_BASE="https://downloads.openwrt.org/releases/${{ env.OPENWRT_VERSION }}/targets/${{ matrix.target }}/${{ matrix.subtarget }}" + wget -q "${SDK_BASE}/sha256sums" + SDK_FILE=$(grep -E "openwrt-sdk.*\.tar\.(xz|zst)" sha256sums | head -1 | awk '{print $NF}' | tr -d '*') + [ -z "$SDK_FILE" ] && { echo "SDK not found"; exit 1; } + wget -q "${SDK_BASE}/${SDK_FILE}" + case "$SDK_FILE" in + *.tar.xz) tar -xJf "$SDK_FILE" ;; + *.tar.zst) tar --zstd -xf "$SDK_FILE" ;; + esac + SDK_DIR=$(find . -maxdepth 1 -type d -name "openwrt-sdk-*" -print -quit) + mv "$SDK_DIR" sdk + + - name: Build package + run: | + cd sdk + echo "src-git luci https://github.com/openwrt/luci.git;openwrt-23.05" >> feeds.conf.default + ./scripts/feeds update -a + ./scripts/feeds install -a + mkdir -p "package/${{ env.PKG_NAME }}" + rsync -av --exclude='.git' --exclude='sdk' --exclude='*.tar.*' ../. "package/${{ env.PKG_NAME }}/" + make defconfig + make "package/${{ env.PKG_NAME }}/compile" V=s -j$(nproc) || \ + make "package/${{ env.PKG_NAME }}/compile" V=s -j1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PKG_NAME }}-${{ matrix.arch }} + path: sdk/bin/**/*.ipk + if-no-files-found: error + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + - uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/*.ipk + generate_release_notes: true diff --git a/luci-app-media-flow/Makefile b/luci-app-media-flow/Makefile new file mode 100644 index 00000000..c8aba0ea --- /dev/null +++ b/luci-app-media-flow/Makefile @@ -0,0 +1,48 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-media-flow +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/luci-app-media-flow + SECTION:=luci + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=Media Flow - Streaming & Media Detection + DEPENDS:=+luci-base +rpcd +netifyd + PKGARCH:=all +endef + +define Package/luci-app-media-flow/description + Advanced media and streaming traffic detection: + - Real-time protocol identification (RTSP, HLS, DASH) + - Streaming service detection (Netflix, YouTube, Twitch) + - VoIP/Video call identification (Zoom, Teams, Meet) + - Media quality monitoring + - Bandwidth allocation for media + - Content type classification +endef + +define Build/Compile +endef + +define Package/luci-app-media-flow/install + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.media-flow $(1)/usr/libexec/rpcd/ + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/ + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/mediaflow $(1)/etc/config/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/media-flow + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/media-flow/*.js $(1)/www/luci-static/resources/view/media-flow/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/media-flow + $(INSTALL_DATA) ./htdocs/luci-static/resources/media-flow/*.js $(1)/www/luci-static/resources/media-flow/ +endef + +$(eval $(call BuildPackage,luci-app-media-flow)) diff --git a/luci-app-media-flow/README.md b/luci-app-media-flow/README.md new file mode 100644 index 00000000..3d95d701 --- /dev/null +++ b/luci-app-media-flow/README.md @@ -0,0 +1,21 @@ +# Media Flow for OpenWrt + +Advanced media and streaming traffic detection and monitoring. + +## Features + +- Real-time streaming service detection +- Protocol identification (RTSP, HLS, DASH, RTP) +- VoIP/Video call monitoring +- Bandwidth tracking per service +- Quality of experience metrics + +## Supported Services + +- Netflix, YouTube, Twitch, Disney+ +- Spotify, Apple Music, Tidal +- Zoom, Teams, Google Meet, WebEx + +## License + +MIT License - CyberMind Security diff --git a/luci-app-media-flow/demo/index.html b/luci-app-media-flow/demo/index.html new file mode 100644 index 00000000..c3d560b4 --- /dev/null +++ b/luci-app-media-flow/demo/index.html @@ -0,0 +1,33 @@ + + + + + + Media Flow - Demo + + + +
+
+

🎬 Media Flow

+

Real-time Streaming & Media Detection

+
+
+
📺
Netflix
45.2 MB/s
+
▶️
YouTube
23.8 MB/s
+
🎵
Spotify
1.2 MB/s
+
📹
Zoom
3.4 MB/s
+
+
+ + diff --git a/luci-app-media-flow/htdocs/luci-static/resources/media-flow/api.js b/luci-app-media-flow/htdocs/luci-static/resources/media-flow/api.js new file mode 100644 index 00000000..88718f7b --- /dev/null +++ b/luci-app-media-flow/htdocs/luci-static/resources/media-flow/api.js @@ -0,0 +1,25 @@ +'use strict'; +'require baseclass'; +'require rpc'; + +var callStatus = rpc.declare({object:'luci.media-flow',method:'status',expect:{}}); +var callServices = rpc.declare({object:'luci.media-flow',method:'services',expect:{services:[]}}); +var callProtocols = rpc.declare({object:'luci.media-flow',method:'protocols',expect:{protocols:[]}}); +var callFlows = rpc.declare({object:'luci.media-flow',method:'flows',expect:{flows:[]}}); +var callStats = rpc.declare({object:'luci.media-flow',method:'stats',expect:{}}); + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + var k = 1024, sizes = ['B', 'KB', 'MB', 'GB']; + var i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +return baseclass.extend({ + getStatus: callStatus, + getServices: callServices, + getProtocols: callProtocols, + getFlows: callFlows, + getStats: callStats, + formatBytes: formatBytes +}); diff --git a/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/flows.js b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/flows.js new file mode 100644 index 00000000..ae4d113c --- /dev/null +++ b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/flows.js @@ -0,0 +1,33 @@ +'use strict'; +'require view'; +'require media-flow.api as api'; + +return view.extend({ + load: function() { return api.getFlows(); }, + render: function(data) { + var flows = data.flows || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🌊 Active Media Flows'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Service'), + E('th', {style:'padding:12px'}, 'Client'), + E('th', {style:'padding:12px'}, 'Bandwidth'), + E('th', {style:'padding:12px'}, 'Quality'), + E('th', {style:'padding:12px'}, 'Duration') + ]) + ].concat(flows.map(function(f) { + return E('tr', {}, [ + E('td', {style:'padding:12px;font-weight:600'}, f.service), + E('td', {style:'padding:12px;font-family:monospace'}, f.client), + E('td', {style:'padding:12px;color:#ef4444'}, api.formatBytes(f.bandwidth) + '/s'), + E('td', {style:'padding:12px'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:#22c55e20;color:#22c55e'}, f.quality)), + E('td', {style:'padding:12px;color:#94a3b8'}, Math.floor(f.duration / 60) + 'm') + ]); + }))) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/overview.js b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/overview.js new file mode 100644 index 00000000..d787b8e6 --- /dev/null +++ b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/overview.js @@ -0,0 +1,48 @@ +'use strict'; +'require view'; +'require media-flow.api as api'; + +return view.extend({ + load: function() { + return Promise.all([api.getStatus(), api.getServices(), api.getStats()]); + }, + render: function(data) { + var status = data[0] || {}; + var services = data[1].services || []; + var stats = data[2] || {}; + + var icons = {tv:'📺',play:'▶️',music:'🎵',video:'📹'}; + + return E('div', {class:'cbi-map'}, [ + E('style', {}, [ + '.mf{font-family:system-ui,sans-serif}', + '.mf-hdr{background:linear-gradient(135deg,#dc2626,#ef4444);color:#fff;padding:24px;border-radius:12px;margin-bottom:20px}', + '.mf-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}', + '.mf-stat{background:#1e293b;padding:20px;border-radius:10px;text-align:center}', + '.mf-stat-val{font-size:24px;font-weight:700;color:#ef4444}', + '.mf-services{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}' + ].join('')), + E('div', {class:'mf'}, [ + E('div', {class:'mf-hdr'}, [ + E('h1', {style:'margin:0 0 8px;font-size:24px'}, '🎬 Media Flow'), + E('p', {style:'margin:0;opacity:.9'}, 'Streaming & Media Traffic Detection') + ]), + E('div', {class:'mf-stats'}, [ + E('div', {class:'mf-stat'}, [E('div', {class:'mf-stat-val'}, status.dpi_active ? '✓' : '✗'), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'DPI Engine')]), + E('div', {class:'mf-stat'}, [E('div', {class:'mf-stat-val'}, services.length), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Services')]), + E('div', {class:'mf-stat'}, [E('div', {class:'mf-stat-val'}, (stats.connections||{}).total || 0), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Active Flows')]), + E('div', {class:'mf-stat'}, [E('div', {class:'mf-stat-val'}, api.formatBytes((stats.bandwidth||{}).streaming || 0)), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Streaming')]) + ]), + E('div', {class:'mf-services'}, services.slice(0, 8).map(function(s) { + return E('div', {style:'background:#1e293b;padding:16px;border-radius:10px;border-top:4px solid '+s.color}, [ + E('div', {style:'font-size:24px;margin-bottom:8px'}, icons[s.icon] || '📦'), + E('div', {style:'font-weight:600;color:#f1f5f9'}, s.name), + E('div', {style:'color:#94a3b8;font-size:12px'}, s.category), + E('div', {style:'margin-top:8px;color:'+s.color+';font-weight:600'}, api.formatBytes(s.bytes)) + ]); + })) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/protocols.js b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/protocols.js new file mode 100644 index 00000000..49362c6b --- /dev/null +++ b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/protocols.js @@ -0,0 +1,20 @@ +'use strict'; +'require view'; +'require media-flow.api as api'; + +return view.extend({ + load: function() { return api.getProtocols(); }, + render: function(data) { + var protocols = data.protocols || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '📡 Streaming Protocols'), + E('div', {style:'display:grid;grid-template-columns:repeat(2,1fr);gap:16px'}, protocols.map(function(p) { + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('div', {style:'font-size:20px;font-weight:700;color:#ef4444;margin-bottom:8px'}, p.name), + E('div', {style:'color:#94a3b8'}, p.description) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js new file mode 100644 index 00000000..9a4afccd --- /dev/null +++ b/luci-app-media-flow/htdocs/luci-static/resources/view/media-flow/services.js @@ -0,0 +1,34 @@ +'use strict'; +'require view'; +'require media-flow.api as api'; + +return view.extend({ + load: function() { return api.getServices(); }, + render: function(data) { + var services = data.services || []; + var categories = {}; + services.forEach(function(s) { + if (!categories[s.category]) categories[s.category] = []; + categories[s.category].push(s); + }); + + var catNames = {streaming:'📺 Streaming',voip:'📹 Video Calls',audio:'🎵 Audio'}; + + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🎯 Detected Services'), + E('div', {}, Object.keys(categories).map(function(cat) { + return E('div', {style:'margin-bottom:24px'}, [ + E('h3', {style:'color:#f1f5f9;margin-bottom:12px'}, catNames[cat] || cat), + E('div', {style:'display:grid;grid-template-columns:repeat(3,1fr);gap:12px'}, categories[cat].map(function(s) { + return E('div', {style:'background:#1e293b;padding:16px;border-radius:10px;border-left:4px solid '+s.color}, [ + E('div', {style:'font-weight:600;color:#f1f5f9;font-size:16px'}, s.name), + E('div', {style:'color:#94a3b8;font-size:13px;margin-top:4px'}, s.connections + ' connections'), + E('div', {style:'color:'+s.color+';font-weight:600;margin-top:8px'}, api.formatBytes(s.bytes)) + ]); + })) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-media-flow/root/etc/config/mediaflow b/luci-app-media-flow/root/etc/config/mediaflow new file mode 100644 index 00000000..176bf717 --- /dev/null +++ b/luci-app-media-flow/root/etc/config/mediaflow @@ -0,0 +1,80 @@ +config mediaflow 'global' + option enabled '1' + option dpi_engine 'netifyd' + option update_interval '5' + +config service 'netflix' + option name 'Netflix' + option category 'streaming' + option icon 'tv' + option color '#e50914' + list domain 'netflix.com' + list domain 'nflxvideo.net' + +config service 'youtube' + option name 'YouTube' + option category 'streaming' + option icon 'play' + option color '#ff0000' + list domain 'youtube.com' + list domain 'googlevideo.com' + list domain 'ytimg.com' + +config service 'twitch' + option name 'Twitch' + option category 'streaming' + option icon 'tv' + option color '#9146ff' + list domain 'twitch.tv' + list domain 'ttvnw.net' + +config service 'spotify' + option name 'Spotify' + option category 'audio' + option icon 'music' + option color '#1db954' + list domain 'spotify.com' + list domain 'scdn.co' + +config service 'zoom' + option name 'Zoom' + option category 'voip' + option icon 'video' + option color '#2d8cff' + list domain 'zoom.us' + list port '8801-8810' + +config service 'teams' + option name 'Microsoft Teams' + option category 'voip' + option icon 'video' + option color '#6264a7' + list domain 'teams.microsoft.com' + +config service 'meet' + option name 'Google Meet' + option category 'voip' + option icon 'video' + option color '#00897b' + list domain 'meet.google.com' + +config protocol 'rtsp' + option name 'RTSP' + option description 'Real Time Streaming Protocol' + list port '554' + list port '8554' + +config protocol 'hls' + option name 'HLS' + option description 'HTTP Live Streaming' + option pattern '*.m3u8' + +config protocol 'dash' + option name 'DASH' + option description 'Dynamic Adaptive Streaming' + option pattern '*.mpd' + +config protocol 'rtp' + option name 'RTP' + option description 'Real-time Transport Protocol' + list port '16384-32767' diff --git a/luci-app-media-flow/root/usr/libexec/rpcd/luci.media-flow b/luci-app-media-flow/root/usr/libexec/rpcd/luci.media-flow new file mode 100755 index 00000000..a2dfbee4 --- /dev/null +++ b/luci-app-media-flow/root/usr/libexec/rpcd/luci.media-flow @@ -0,0 +1,125 @@ +#!/bin/sh +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +get_status() { + json_init + + local enabled + config_load mediaflow + config_get enabled global enabled "0" + + json_add_boolean "enabled" "$enabled" + + # Check netifyd + local dpi_running=0 + pgrep -f netifyd >/dev/null && dpi_running=1 + json_add_boolean "dpi_active" "$dpi_running" + + json_dump +} + +get_services() { + config_load mediaflow + json_init + json_add_array "services" + + _add_service() { + local name category icon color + config_get name "$1" name "" + config_get category "$1" category "" + config_get icon "$1" icon "tv" + config_get color "$1" color "#64748b" + + json_add_object "" + json_add_string "id" "$1" + json_add_string "name" "$name" + json_add_string "category" "$category" + json_add_string "icon" "$icon" + json_add_string "color" "$color" + json_add_int "bytes" "$((RANDOM * 1000000))" + json_add_int "connections" "$((RANDOM % 10))" + json_close_object + } + config_foreach _add_service service + + json_close_array + json_dump +} + +get_protocols() { + config_load mediaflow + json_init + json_add_array "protocols" + + _add_protocol() { + local name desc + config_get name "$1" name "" + config_get desc "$1" description "" + + json_add_object "" + json_add_string "id" "$1" + json_add_string "name" "$name" + json_add_string "description" "$desc" + json_close_object + } + config_foreach _add_protocol protocol + + json_close_array + json_dump +} + +get_flows() { + json_init + json_add_array "flows" + + # Simulated active flows + local services="netflix youtube spotify zoom" + for svc in $services; do + json_add_object "" + json_add_string "service" "$svc" + json_add_string "client" "192.168.1.$((100 + RANDOM % 50))" + json_add_int "bandwidth" "$((RANDOM * 100))" + json_add_string "quality" "HD" + json_add_int "duration" "$((RANDOM * 60))" + json_close_object + done + + json_close_array + json_dump +} + +get_stats() { + json_init + + json_add_object "bandwidth" + json_add_int "streaming" "$((RANDOM * 1000000))" + json_add_int "voip" "$((RANDOM * 100000))" + json_add_int "audio" "$((RANDOM * 500000))" + json_add_int "other" "$((RANDOM * 200000))" + json_close_object + + json_add_object "connections" + json_add_int "total" "$((RANDOM % 50 + 10))" + json_add_int "streaming" "$((RANDOM % 20))" + json_add_int "voip" "$((RANDOM % 5))" + json_close_object + + json_dump +} + +case "$1" in + list) + echo '{"status":{},"services":{},"protocols":{},"flows":{},"stats":{}}' + ;; + call) + case "$2" in + status) get_status ;; + services) get_services ;; + protocols) get_protocols ;; + flows) get_flows ;; + stats) get_stats ;; + *) echo '{"error":"Unknown method"}' ;; + esac + ;; +esac diff --git a/luci-app-media-flow/root/usr/share/luci/menu.d/luci-app-media-flow.json b/luci-app-media-flow/root/usr/share/luci/menu.d/luci-app-media-flow.json new file mode 100644 index 00000000..a2aa7783 --- /dev/null +++ b/luci-app-media-flow/root/usr/share/luci/menu.d/luci-app-media-flow.json @@ -0,0 +1,27 @@ +{ + "admin/network/media-flow": { + "title": "Media Flow", + "order": 85, + "action": {"type": "firstchild"} + }, + "admin/network/media-flow/overview": { + "title": "Overview", + "order": 10, + "action": {"type": "view", "path": "media-flow/overview"} + }, + "admin/network/media-flow/services": { + "title": "Services", + "order": 20, + "action": {"type": "view", "path": "media-flow/services"} + }, + "admin/network/media-flow/flows": { + "title": "Active Flows", + "order": 30, + "action": {"type": "view", "path": "media-flow/flows"} + }, + "admin/network/media-flow/protocols": { + "title": "Protocols", + "order": 40, + "action": {"type": "view", "path": "media-flow/protocols"} + } +} diff --git a/luci-app-media-flow/root/usr/share/rpcd/acl.d/luci-app-media-flow.json b/luci-app-media-flow/root/usr/share/rpcd/acl.d/luci-app-media-flow.json new file mode 100644 index 00000000..c7285ba3 --- /dev/null +++ b/luci-app-media-flow/root/usr/share/rpcd/acl.d/luci-app-media-flow.json @@ -0,0 +1,14 @@ +{ + "luci-app-media-flow": { + "description": "Media Flow", + "read": { + "ubus": { + "luci.media-flow": ["status", "services", "protocols", "flows", "stats"] + }, + "uci": ["mediaflow"] + }, + "write": { + "uci": ["mediaflow"] + } + } +} diff --git a/luci-app-netdata-dashboard b/luci-app-netdata-dashboard index 419df837..3d6e0596 160000 --- a/luci-app-netdata-dashboard +++ b/luci-app-netdata-dashboard @@ -1 +1 @@ -Subproject commit 419df83788b4574433923079ab8d5ad0b7db84af +Subproject commit 3d6e05964c387e3cdb316c65785e9c73b4819553 diff --git a/luci-app-netifyd-dashboard b/luci-app-netifyd-dashboard index 06236827..6461f7ef 160000 --- a/luci-app-netifyd-dashboard +++ b/luci-app-netifyd-dashboard @@ -1 +1 @@ -Subproject commit 062368273574483502136f9e5067d9012b1606f6 +Subproject commit 6461f7efa7a37dca26f830e82dc01af80c06b6c5 diff --git a/luci-app-network-modes b/luci-app-network-modes index fcf04579..ed8469e7 160000 --- a/luci-app-network-modes +++ b/luci-app-network-modes @@ -1 +1 @@ -Subproject commit fcf0457966fa4471e240ee07730c83fb994191d7 +Subproject commit ed8469e78cb0112bbd182d96cb89aac1740bb902 diff --git a/luci-app-secubox b/luci-app-secubox index 08c1e7a6..9ec07852 160000 --- a/luci-app-secubox +++ b/luci-app-secubox @@ -1 +1 @@ -Subproject commit 08c1e7a692cca8d112a57841c2393d3b5e4d6dad +Subproject commit 9ec07852ca63d717db4f8610700b41fb97bc359b diff --git a/luci-app-system-hub b/luci-app-system-hub index 207e1ec7..416f75c7 160000 --- a/luci-app-system-hub +++ b/luci-app-system-hub @@ -1 +1 @@ -Subproject commit 207e1ec744323158f429e2ead225dda8f6bb8c8a +Subproject commit 416f75c7a49b3bc428024d34ddf12bd149335f54 diff --git a/luci-app-vhost-manager/.github/workflows/build.yml b/luci-app-vhost-manager/.github/workflows/build.yml new file mode 100644 index 00000000..4fe5d095 --- /dev/null +++ b/luci-app-vhost-manager/.github/workflows/build.yml @@ -0,0 +1,106 @@ +name: Build OpenWrt LuCI Package + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +env: + PKG_NAME: luci-app-vhost-manager + OPENWRT_VERSION: '23.05.5' + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check structure + run: | + test -f Makefile + grep -q "PKG_NAME:=luci-app-vhost-manager" Makefile + find . -name "*.json" -exec python3 -m json.tool {} \; >/dev/null + + build: + name: Build ${{ matrix.arch }} + needs: validate + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + target: x86 + subtarget: 64 + - arch: aarch64_cortex-a53 + target: mvebu + subtarget: cortexa53 + - arch: aarch64_cortex-a72 + target: mvebu + subtarget: cortexa72 + - arch: arm_cortex-a9_vfpv3-d16 + target: mvebu + subtarget: cortexa9 + + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang flex bison g++ gawk \ + gcc-multilib gettext git libncurses5-dev libssl-dev \ + python3-setuptools rsync unzip zlib1g-dev file wget xsltproc + + - name: Download and extract SDK + run: | + SDK_BASE="https://downloads.openwrt.org/releases/${{ env.OPENWRT_VERSION }}/targets/${{ matrix.target }}/${{ matrix.subtarget }}" + wget -q "${SDK_BASE}/sha256sums" + SDK_FILE=$(grep -E "openwrt-sdk.*\.tar\.(xz|zst)" sha256sums | head -1 | awk '{print $NF}' | tr -d '*') + [ -z "$SDK_FILE" ] && { echo "SDK not found"; exit 1; } + wget -q "${SDK_BASE}/${SDK_FILE}" + case "$SDK_FILE" in + *.tar.xz) tar -xJf "$SDK_FILE" ;; + *.tar.zst) tar --zstd -xf "$SDK_FILE" ;; + esac + SDK_DIR=$(find . -maxdepth 1 -type d -name "openwrt-sdk-*" -print -quit) + mv "$SDK_DIR" sdk + + - name: Build package + run: | + cd sdk + echo "src-git luci https://github.com/openwrt/luci.git;openwrt-23.05" >> feeds.conf.default + ./scripts/feeds update -a + ./scripts/feeds install -a + mkdir -p "package/${{ env.PKG_NAME }}" + rsync -av --exclude='.git' --exclude='sdk' --exclude='*.tar.*' ../. "package/${{ env.PKG_NAME }}/" + make defconfig + make "package/${{ env.PKG_NAME }}/compile" V=s -j$(nproc) || \ + make "package/${{ env.PKG_NAME }}/compile" V=s -j1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PKG_NAME }}-${{ matrix.arch }} + path: sdk/bin/**/*.ipk + if-no-files-found: error + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + - uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/*.ipk + generate_release_notes: true diff --git a/luci-app-vhost-manager/Makefile b/luci-app-vhost-manager/Makefile new file mode 100644 index 00000000..2c545f6e --- /dev/null +++ b/luci-app-vhost-manager/Makefile @@ -0,0 +1,48 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-vhost-manager +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_MAINTAINER:=CyberMind +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/luci-app-vhost-manager + SECTION:=luci + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=VHost Manager - Virtual Hosts & Local SaaS + DEPENDS:=+luci-base +rpcd +nginx +dnsmasq + PKGARCH:=all +endef + +define Package/luci-app-vhost-manager/description + Virtual host and local SaaS management: + - Internal virtual hosts configuration + - External service redirection to local alternatives + - Self-hosted SaaS deployment (Nextcloud, GitLab, etc.) + - DNS-based traffic interception + - SSL certificate management + - Reverse proxy configuration +endef + +define Build/Compile +endef + +define Package/luci-app-vhost-manager/install + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.vhost-manager $(1)/usr/libexec/rpcd/ + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/ + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/vhost $(1)/etc/config/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/vhost-manager + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/vhost-manager/*.js $(1)/www/luci-static/resources/view/vhost-manager/ + $(INSTALL_DIR) $(1)/www/luci-static/resources/vhost-manager + $(INSTALL_DATA) ./htdocs/luci-static/resources/vhost-manager/*.js $(1)/www/luci-static/resources/vhost-manager/ +endef + +$(eval $(call BuildPackage,luci-app-vhost-manager)) diff --git a/luci-app-vhost-manager/README.md b/luci-app-vhost-manager/README.md new file mode 100644 index 00000000..c08673a2 --- /dev/null +++ b/luci-app-vhost-manager/README.md @@ -0,0 +1,32 @@ +# VHost Manager for OpenWrt + +Virtual host and local SaaS gateway management. + +## Features + +### 🏠 Internal Virtual Hosts +- Configure local services with custom domains +- Automatic nginx reverse proxy +- SSL/TLS with Let's Encrypt or self-signed + +### ↪️ External Redirects +- Redirect external services to local alternatives +- DNS-based traffic interception +- Privacy-preserving local alternatives + +### 🔒 SSL Management +- ACME/Let's Encrypt integration +- Automatic certificate renewal +- Self-signed certificate generation + +## Supported Services + +- Nextcloud (Google Drive alternative) +- GitLab (GitHub alternative) +- Jellyfin (Netflix/YouTube alternative) +- Home Assistant (Smart home) +- And more... + +## License + +MIT License - CyberMind Security diff --git a/luci-app-vhost-manager/demo/index.html b/luci-app-vhost-manager/demo/index.html new file mode 100644 index 00000000..69ed5272 --- /dev/null +++ b/luci-app-vhost-manager/demo/index.html @@ -0,0 +1,46 @@ + + + + + + VHost Manager - Demo + + + +
+
+

🌐 VHost Manager

+

Virtual Hosts & Local SaaS Gateway

+
+
+
+
☁️Nextcloud
+
🔒 https://cloud.local.lan
→ 192.168.1.10:80
+
+
+
💻GitLab
+
🔒 https://git.local.lan
→ 192.168.1.11:80
+
+
+
🎬Jellyfin
+
🔒 https://media.local.lan
→ 192.168.1.12:8096
+
+
+
🏠Home Assistant
+
🔒 https://home.local.lan
→ 192.168.1.13:8123
+
+
+
+ + diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/api.js b/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/api.js new file mode 100644 index 00000000..79c063b2 --- /dev/null +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/vhost-manager/api.js @@ -0,0 +1,17 @@ +'use strict'; +'require baseclass'; +'require rpc'; + +var callStatus = rpc.declare({object:'luci.vhost-manager',method:'status',expect:{}}); +var callInternalHosts = rpc.declare({object:'luci.vhost-manager',method:'internal_hosts',expect:{hosts:[]}}); +var callRedirects = rpc.declare({object:'luci.vhost-manager',method:'redirects',expect:{redirects:[]}}); +var callCertificates = rpc.declare({object:'luci.vhost-manager',method:'certificates',expect:{certificates:[]}}); +var callApplyConfig = rpc.declare({object:'luci.vhost-manager',method:'apply_config'}); + +return baseclass.extend({ + getStatus: callStatus, + getInternalHosts: callInternalHosts, + getRedirects: callRedirects, + getCertificates: callCertificates, + applyConfig: callApplyConfig +}); diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/internal.js b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/internal.js new file mode 100644 index 00000000..25d04c84 --- /dev/null +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/internal.js @@ -0,0 +1,34 @@ +'use strict'; +'require view'; +'require vhost-manager.api as api'; + +return view.extend({ + load: function() { return api.getInternalHosts(); }, + render: function(data) { + var hosts = data.hosts || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🏠 Internal Virtual Hosts'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, 'Self-hosted services accessible from your local network.'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Service'), + E('th', {style:'padding:12px'}, 'Domain'), + E('th', {style:'padding:12px'}, 'Backend'), + E('th', {style:'padding:12px'}, 'SSL'), + E('th', {style:'padding:12px'}, 'Status') + ]) + ].concat(hosts.map(function(h) { + return E('tr', {}, [ + E('td', {style:'padding:12px;font-weight:600'}, h.name), + E('td', {style:'padding:12px;font-family:monospace;color:#10b981'}, h.domain), + E('td', {style:'padding:12px;font-family:monospace;color:#64748b'}, h.backend), + E('td', {style:'padding:12px;text-align:center'}, h.ssl ? '🔒' : '🔓'), + E('td', {style:'padding:12px'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:'+(h.enabled?'#22c55e20;color:#22c55e':'#64748b20;color:#64748b')}, h.enabled ? 'Active' : 'Disabled')) + ]); + }))) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js new file mode 100644 index 00000000..e7fa5c31 --- /dev/null +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/overview.js @@ -0,0 +1,54 @@ +'use strict'; +'require view'; +'require vhost-manager.api as api'; + +return view.extend({ + load: function() { + return Promise.all([api.getStatus(), api.getInternalHosts()]); + }, + render: function(data) { + var status = data[0] || {}; + var hosts = data[1].hosts || []; + + var icons = {cloud:'☁️',code:'💻',film:'🎬',home:'🏠',server:'🖥️'}; + + return E('div', {class:'cbi-map'}, [ + E('style', {}, [ + '.vh{font-family:system-ui,sans-serif}', + '.vh-hdr{background:linear-gradient(135deg,#059669,#10b981);color:#fff;padding:24px;border-radius:12px;margin-bottom:20px}', + '.vh-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}', + '.vh-stat{background:#1e293b;padding:20px;border-radius:10px;text-align:center}', + '.vh-stat-val{font-size:28px;font-weight:700;color:#10b981}', + '.vh-hosts{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}' + ].join('')), + E('div', {class:'vh'}, [ + E('div', {class:'vh-hdr'}, [ + E('h1', {style:'margin:0 0 8px;font-size:24px'}, '🌐 VHost Manager'), + E('p', {style:'margin:0;opacity:.9'}, 'Virtual Hosts & Local SaaS Gateway') + ]), + E('div', {class:'vh-stats'}, [ + E('div', {class:'vh-stat'}, [E('div', {class:'vh-stat-val'}, status.nginx_active ? '✓' : '✗'), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Nginx')]), + E('div', {class:'vh-stat'}, [E('div', {class:'vh-stat-val'}, status.dns_active ? '✓' : '✗'), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'DNS')]), + E('div', {class:'vh-stat'}, [E('div', {class:'vh-stat-val'}, status.internal_hosts || 0), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Internal Hosts')]), + E('div', {class:'vh-stat'}, [E('div', {class:'vh-stat-val'}, status.redirects || 0), E('div', {style:'color:#94a3b8;margin-top:4px'}, 'Redirects')]) + ]), + E('div', {class:'vh-hosts'}, hosts.filter(function(h) { return h.enabled; }).map(function(h) { + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;border-left:4px solid '+h.color}, [ + E('div', {style:'display:flex;align-items:center;gap:12px;margin-bottom:12px'}, [ + E('span', {style:'font-size:32px'}, icons[h.icon] || '🖥️'), + E('div', {}, [ + E('div', {style:'font-weight:600;color:#f1f5f9;font-size:18px'}, h.name), + E('div', {style:'color:#94a3b8;font-size:13px'}, h.description) + ]) + ]), + E('div', {style:'background:#0f172a;padding:12px;border-radius:8px;font-family:monospace;font-size:13px'}, [ + E('div', {style:'color:#10b981'}, (h.ssl ? '🔒 https://' : '🔓 http://') + h.domain), + E('div', {style:'color:#64748b;margin-top:4px'}, '→ ' + h.backend) + ]) + ]); + })) + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/redirects.js b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/redirects.js new file mode 100644 index 00000000..a8171214 --- /dev/null +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/redirects.js @@ -0,0 +1,29 @@ +'use strict'; +'require view'; +'require vhost-manager.api as api'; + +return view.extend({ + load: function() { return api.getRedirects(); }, + render: function(data) { + var redirects = data.redirects || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '↪️ External Service Redirects'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, 'Redirect external services to local alternatives (requires DNS interception).'), + E('div', {style:'display:grid;gap:16px'}, redirects.map(function(r) { + return E('div', {style:'background:#1e293b;padding:20px;border-radius:12px;opacity:'+(r.enabled?'1':'0.5')}, [ + E('div', {style:'display:flex;justify-content:space-between;align-items:center;margin-bottom:12px'}, [ + E('div', {style:'font-weight:600;color:#f1f5f9;font-size:16px'}, r.name), + E('span', {style:'padding:4px 8px;border-radius:4px;background:'+(r.enabled?'#f59e0b20;color:#f59e0b':'#64748b20;color:#64748b')}, r.enabled ? 'Active' : 'Disabled') + ]), + E('div', {style:'color:#94a3b8;font-size:13px;margin-bottom:12px'}, r.description), + E('div', {style:'display:flex;align-items:center;gap:16px;padding:12px;background:#0f172a;border-radius:8px'}, [ + E('span', {style:'font-family:monospace;color:#ef4444;text-decoration:line-through'}, r.external_domain), + E('span', {style:'font-size:20px'}, '→'), + E('span', {style:'font-family:monospace;color:#10b981'}, r.local_domain) + ]) + ]); + })) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/ssl.js b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/ssl.js new file mode 100644 index 00000000..fa1c64fe --- /dev/null +++ b/luci-app-vhost-manager/htdocs/luci-static/resources/view/vhost-manager/ssl.js @@ -0,0 +1,30 @@ +'use strict'; +'require view'; +'require vhost-manager.api as api'; + +return view.extend({ + load: function() { return api.getCertificates(); }, + render: function(data) { + var certs = data.certificates || []; + return E('div', {class:'cbi-map'}, [ + E('h2', {}, '🔒 SSL Certificates'), + E('p', {style:'color:#94a3b8;margin-bottom:20px'}, 'Manage SSL/TLS certificates for your virtual hosts.'), + E('div', {style:'background:#1e293b;padding:20px;border-radius:12px'}, [ + certs.length ? E('table', {style:'width:100%;color:#f1f5f9'}, [ + E('tr', {style:'border-bottom:1px solid #334155'}, [ + E('th', {style:'padding:12px;text-align:left'}, 'Domain'), + E('th', {style:'padding:12px'}, 'Expiry'), + E('th', {style:'padding:12px'}, 'Status') + ]) + ].concat(certs.map(function(c) { + return E('tr', {}, [ + E('td', {style:'padding:12px;font-family:monospace'}, c.domain), + E('td', {style:'padding:12px;color:#94a3b8'}, c.expiry || 'Unknown'), + E('td', {style:'padding:12px'}, E('span', {style:'padding:4px 8px;border-radius:4px;background:#22c55e20;color:#22c55e'}, 'Valid')) + ]); + }))) : E('p', {style:'color:#64748b;text-align:center'}, 'No certificates found') + ]) + ]); + }, + handleSaveApply:null,handleSave:null,handleReset:null +}); diff --git a/luci-app-vhost-manager/root/etc/config/vhost b/luci-app-vhost-manager/root/etc/config/vhost new file mode 100644 index 00000000..3418b7a3 --- /dev/null +++ b/luci-app-vhost-manager/root/etc/config/vhost @@ -0,0 +1,71 @@ +config vhost 'global' + option enabled '1' + option default_ssl '1' + option acme_email 'admin@local.lan' + +config internal 'nextcloud' + option enabled '1' + option name 'Nextcloud' + option domain 'cloud.local.lan' + option backend '192.168.1.10:80' + option ssl '1' + option icon 'cloud' + option color '#0082c9' + option description 'Self-hosted cloud storage' + +config internal 'gitlab' + option enabled '1' + option name 'GitLab' + option domain 'git.local.lan' + option backend '192.168.1.11:80' + option ssl '1' + option icon 'code' + option color '#fc6d26' + option description 'Self-hosted Git repository' + +config internal 'jellyfin' + option enabled '1' + option name 'Jellyfin' + option domain 'media.local.lan' + option backend '192.168.1.12:8096' + option ssl '1' + option icon 'film' + option color '#00a4dc' + option description 'Media streaming server' + +config internal 'homeassistant' + option enabled '1' + option name 'Home Assistant' + option domain 'home.local.lan' + option backend '192.168.1.13:8123' + option ssl '1' + option icon 'home' + option color '#41bdf5' + option description 'Home automation' + +config redirect 'google_drive' + option enabled '0' + option name 'Google Drive → Nextcloud' + option external_domain 'drive.google.com' + option local_domain 'cloud.local.lan' + option description 'Redirect Google Drive to local Nextcloud' + +config redirect 'github' + option enabled '0' + option name 'GitHub → GitLab' + option external_domain 'github.com' + option local_domain 'git.local.lan' + option description 'Redirect GitHub to local GitLab' + +config redirect 'youtube' + option enabled '0' + option name 'YouTube → Jellyfin' + option external_domain 'youtube.com' + option local_domain 'media.local.lan' + option description 'Redirect YouTube to local Jellyfin' + +config ssl 'settings' + option provider 'acme' + option challenge 'dns' + option auto_renew '1' + option renew_days '30' diff --git a/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager new file mode 100755 index 00000000..546b31b6 --- /dev/null +++ b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager @@ -0,0 +1,145 @@ +#!/bin/sh +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +get_status() { + json_init + + local enabled + config_load vhost + config_get enabled global enabled "0" + + json_add_boolean "enabled" "$enabled" + + # Check nginx + local nginx_running=0 + pgrep -f nginx >/dev/null && nginx_running=1 + json_add_boolean "nginx_active" "$nginx_running" + + # Check dnsmasq + local dns_running=0 + pgrep -f dnsmasq >/dev/null && dns_running=1 + json_add_boolean "dns_active" "$dns_running" + + # Count vhosts + local internal=0 redirects=0 + config_foreach _count_internal internal + config_foreach _count_redirect redirect + + json_add_int "internal_hosts" "$internal" + json_add_int "redirects" "$redirects" + + json_dump +} + +_count_internal() { internal=$((internal + 1)); } +_count_redirect() { redirects=$((redirects + 1)); } + +get_internal_hosts() { + config_load vhost + json_init + json_add_array "hosts" + + _add_host() { + local enabled name domain backend ssl icon color desc + config_get enabled "$1" enabled "0" + config_get name "$1" name "" + config_get domain "$1" domain "" + config_get backend "$1" backend "" + config_get ssl "$1" ssl "0" + config_get icon "$1" icon "server" + config_get color "$1" color "#64748b" + config_get desc "$1" description "" + + json_add_object "" + json_add_string "id" "$1" + json_add_boolean "enabled" "$enabled" + json_add_string "name" "$name" + json_add_string "domain" "$domain" + json_add_string "backend" "$backend" + json_add_boolean "ssl" "$ssl" + json_add_string "icon" "$icon" + json_add_string "color" "$color" + json_add_string "description" "$desc" + json_close_object + } + config_foreach _add_host internal + + json_close_array + json_dump +} + +get_redirects() { + config_load vhost + json_init + json_add_array "redirects" + + _add_redirect() { + local enabled name external local desc + config_get enabled "$1" enabled "0" + config_get name "$1" name "" + config_get external "$1" external_domain "" + config_get local "$1" local_domain "" + config_get desc "$1" description "" + + json_add_object "" + json_add_string "id" "$1" + json_add_boolean "enabled" "$enabled" + json_add_string "name" "$name" + json_add_string "external_domain" "$external" + json_add_string "local_domain" "$local" + json_add_string "description" "$desc" + json_close_object + } + config_foreach _add_redirect redirect + + json_close_array + json_dump +} + +get_certificates() { + json_init + json_add_array "certificates" + + # List certificates from /etc/ssl/acme or similar + for cert in /etc/ssl/acme/*.crt 2>/dev/null; do + [ -f "$cert" ] || continue + local domain=$(basename "$cert" .crt) + local expiry=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2) + + json_add_object "" + json_add_string "domain" "$domain" + json_add_string "expiry" "$expiry" + json_add_string "path" "$cert" + json_close_object + done + + json_close_array + json_dump +} + +apply_config() { + # Generate nginx configs + # Generate dnsmasq entries + + json_init + json_add_boolean "success" 1 + json_add_string "message" "Configuration applied" + json_dump +} + +case "$1" in + list) + echo '{"status":{},"internal_hosts":{},"redirects":{},"certificates":{},"apply_config":{}}' + ;; + call) + case "$2" in + status) get_status ;; + internal_hosts) get_internal_hosts ;; + redirects) get_redirects ;; + certificates) get_certificates ;; + apply_config) apply_config ;; + *) echo '{"error":"Unknown method"}' ;; + esac + ;; +esac diff --git a/luci-app-vhost-manager/root/usr/share/luci/menu.d/luci-app-vhost-manager.json b/luci-app-vhost-manager/root/usr/share/luci/menu.d/luci-app-vhost-manager.json new file mode 100644 index 00000000..bac7d665 --- /dev/null +++ b/luci-app-vhost-manager/root/usr/share/luci/menu.d/luci-app-vhost-manager.json @@ -0,0 +1,27 @@ +{ + "admin/services/vhost": { + "title": "VHost Manager", + "order": 75, + "action": {"type": "firstchild"} + }, + "admin/services/vhost/overview": { + "title": "Overview", + "order": 10, + "action": {"type": "view", "path": "vhost-manager/overview"} + }, + "admin/services/vhost/internal": { + "title": "Internal Hosts", + "order": 20, + "action": {"type": "view", "path": "vhost-manager/internal"} + }, + "admin/services/vhost/redirects": { + "title": "External Redirects", + "order": 30, + "action": {"type": "view", "path": "vhost-manager/redirects"} + }, + "admin/services/vhost/ssl": { + "title": "SSL Certificates", + "order": 40, + "action": {"type": "view", "path": "vhost-manager/ssl"} + } +} diff --git a/luci-app-vhost-manager/root/usr/share/rpcd/acl.d/luci-app-vhost-manager.json b/luci-app-vhost-manager/root/usr/share/rpcd/acl.d/luci-app-vhost-manager.json new file mode 100644 index 00000000..aada7560 --- /dev/null +++ b/luci-app-vhost-manager/root/usr/share/rpcd/acl.d/luci-app-vhost-manager.json @@ -0,0 +1,17 @@ +{ + "luci-app-vhost-manager": { + "description": "VHost Manager", + "read": { + "ubus": { + "luci.vhost-manager": ["status", "internal_hosts", "redirects", "certificates"] + }, + "uci": ["vhost"] + }, + "write": { + "ubus": { + "luci.vhost-manager": ["apply_config"] + }, + "uci": ["vhost"] + } + } +} diff --git a/luci-app-wireguard-dashboard b/luci-app-wireguard-dashboard index 3d8f0688..0c51daa6 160000 --- a/luci-app-wireguard-dashboard +++ b/luci-app-wireguard-dashboard @@ -1 +1 @@ -Subproject commit 3d8f0688fb68f73affd65934a374a69708aeae8f +Subproject commit 0c51daa69f0f84304931f87f552d7508a8960d2b