chore: Remove redundant luci-app-masterlink package
The luci-app-master-link package already provides full mesh management UI. CLI tools (sbx-mesh-invite, sbx-mesh-join) are now in secubox-master-link. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
39fe2aced4
commit
58758e2824
@ -1,60 +0,0 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
|
||||||
|
|
||||||
PKG_NAME:=luci-app-masterlink
|
|
||||||
PKG_VERSION:=1.0.0
|
|
||||||
PKG_RELEASE:=1
|
|
||||||
|
|
||||||
PKG_MAINTAINER:=SecuBox <contact@secubox.fr>
|
|
||||||
PKG_LICENSE:=MIT
|
|
||||||
|
|
||||||
LUCI_TITLE:=SecuBox Master-Link Mesh Client
|
|
||||||
LUCI_DEPENDS:=+luci-base +wget +jsonfilter
|
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
|
||||||
|
|
||||||
define Package/luci-app-masterlink
|
|
||||||
SECTION:=luci
|
|
||||||
CATEGORY:=LuCI
|
|
||||||
SUBMENU:=3. Applications
|
|
||||||
TITLE:=$(LUCI_TITLE)
|
|
||||||
DEPENDS:=$(LUCI_DEPENDS)
|
|
||||||
endef
|
|
||||||
|
|
||||||
define Package/luci-app-masterlink/description
|
|
||||||
SecuBox Master-Link client for joining SecuBox mesh networks.
|
|
||||||
Provides CLI tool and LuCI web interface for mesh enrollment.
|
|
||||||
endef
|
|
||||||
|
|
||||||
define Package/luci-app-masterlink/install
|
|
||||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
|
||||||
$(INSTALL_BIN) ./files/luci.masterlink $(1)/usr/libexec/rpcd/
|
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
|
||||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-masterlink.json $(1)/usr/share/rpcd/acl.d/
|
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
|
||||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-masterlink.json $(1)/usr/share/luci/menu.d/
|
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/masterlink
|
|
||||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/masterlink/*.js $(1)/www/luci-static/resources/view/masterlink/
|
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc/config
|
|
||||||
$(INSTALL_CONF) ./files/masterlink.config $(1)/etc/config/masterlink
|
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/usr/bin
|
|
||||||
$(INSTALL_BIN) ./root/usr/bin/sbx-mesh-join $(1)/usr/bin/
|
|
||||||
endef
|
|
||||||
|
|
||||||
define Package/luci-app-masterlink/postinst
|
|
||||||
#!/bin/sh
|
|
||||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
|
||||||
# Restart rpcd to load new handler
|
|
||||||
/etc/init.d/rpcd restart
|
|
||||||
|
|
||||||
# Clear LuCI caches
|
|
||||||
rm -f /tmp/luci-indexcache* /tmp/luci-modulecache/* 2>/dev/null
|
|
||||||
}
|
|
||||||
exit 0
|
|
||||||
endef
|
|
||||||
|
|
||||||
$(eval $(call BuildPackage,luci-app-masterlink))
|
|
||||||
@ -1,220 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# SecuBox Master-Link RPCD handler
|
|
||||||
# Provides ubus interface for mesh enrollment
|
|
||||||
|
|
||||||
. /usr/share/libubox/jshn.sh
|
|
||||||
|
|
||||||
NODE_ID_FILE="/etc/secubox/node.id"
|
|
||||||
CONFIG_FILE="/etc/config/masterlink"
|
|
||||||
|
|
||||||
# Generate or retrieve node fingerprint
|
|
||||||
get_fingerprint() {
|
|
||||||
if [ -f "$NODE_ID_FILE" ]; then
|
|
||||||
cat "$NODE_ID_FILE"
|
|
||||||
else
|
|
||||||
mkdir -p /etc/secubox
|
|
||||||
local mac=""
|
|
||||||
# Try br-lan first (OpenWRT), then eth0
|
|
||||||
if [ -f /sys/class/net/br-lan/address ]; then
|
|
||||||
mac=$(cat /sys/class/net/br-lan/address | tr -d ':')
|
|
||||||
elif [ -f /sys/class/net/eth0/address ]; then
|
|
||||||
mac=$(cat /sys/class/net/eth0/address | tr -d ':')
|
|
||||||
else
|
|
||||||
mac=$(cat /sys/class/net/*/address 2>/dev/null | head -1 | tr -d ':')
|
|
||||||
fi
|
|
||||||
local fp="owrt-${mac}"
|
|
||||||
echo "$fp" > "$NODE_ID_FILE"
|
|
||||||
echo "$fp"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get primary LAN IP address
|
|
||||||
get_local_ip() {
|
|
||||||
local ip=""
|
|
||||||
# Try br-lan first (standard OpenWRT)
|
|
||||||
ip=$(ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
|
|
||||||
if [ -z "$ip" ]; then
|
|
||||||
# Fallback to eth0
|
|
||||||
ip=$(ip -4 addr show eth0 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1)
|
|
||||||
fi
|
|
||||||
echo "$ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get device model
|
|
||||||
get_model() {
|
|
||||||
if [ -f /tmp/sysinfo/model ]; then
|
|
||||||
cat /tmp/sysinfo/model
|
|
||||||
elif [ -f /etc/board.json ]; then
|
|
||||||
jsonfilter -i /etc/board.json -e '@.model.name' 2>/dev/null
|
|
||||||
else
|
|
||||||
echo "Unknown"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Join a mesh network
|
|
||||||
do_join() {
|
|
||||||
local master_ip="$1"
|
|
||||||
local token="$2"
|
|
||||||
local fingerprint=$(get_fingerprint)
|
|
||||||
local hostname=$(uci -q get system.@system[0].hostname || echo "openwrt")
|
|
||||||
local address=$(get_local_ip)
|
|
||||||
local model=$(get_model)
|
|
||||||
|
|
||||||
# Prepare JSON payload
|
|
||||||
local payload="{\"token\":\"${token}\",\"fingerprint\":\"${fingerprint}\",\"hostname\":\"${hostname}\",\"address\":\"${address}\",\"model\":\"${model}\"}"
|
|
||||||
|
|
||||||
# Call master API
|
|
||||||
local response=$(wget -qO- --post-data="$payload" \
|
|
||||||
--header="Content-Type: application/json" \
|
|
||||||
--timeout=30 \
|
|
||||||
"http://${master_ip}:7331/api/v1/p2p/master-link/join" 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -z "$response" ]; then
|
|
||||||
json_init
|
|
||||||
json_add_string "status" "error"
|
|
||||||
json_add_string "message" "Failed to connect to master"
|
|
||||||
json_dump
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Parse response status
|
|
||||||
local status=$(echo "$response" | jsonfilter -e '@.status' 2>/dev/null)
|
|
||||||
local master_fp=$(echo "$response" | jsonfilter -e '@.master_fingerprint' 2>/dev/null)
|
|
||||||
local depth=$(echo "$response" | jsonfilter -e '@.depth' 2>/dev/null)
|
|
||||||
|
|
||||||
case "$status" in
|
|
||||||
approved|pending)
|
|
||||||
# Save configuration
|
|
||||||
uci set masterlink.settings.enabled='1'
|
|
||||||
uci set masterlink.settings.role='peer'
|
|
||||||
uci set masterlink.settings.master_ip="$master_ip"
|
|
||||||
uci set masterlink.settings.status="$status"
|
|
||||||
[ -n "$master_fp" ] && uci set masterlink.settings.master_fingerprint="$master_fp"
|
|
||||||
[ -n "$depth" ] && uci set masterlink.settings.depth="$depth"
|
|
||||||
uci set masterlink.settings.joined_at="$(date -Iseconds)"
|
|
||||||
uci commit masterlink
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Return original response
|
|
||||||
echo "$response"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Leave current mesh
|
|
||||||
do_leave() {
|
|
||||||
local master_ip=$(uci -q get masterlink.settings.master_ip)
|
|
||||||
local fingerprint=$(get_fingerprint)
|
|
||||||
|
|
||||||
# Notify master if connected
|
|
||||||
if [ -n "$master_ip" ]; then
|
|
||||||
wget -qO- --post-data="{\"fingerprint\":\"${fingerprint}\"}" \
|
|
||||||
--header="Content-Type: application/json" \
|
|
||||||
--timeout=10 \
|
|
||||||
"http://${master_ip}:7331/api/v1/p2p/master-link/leave" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clear local configuration
|
|
||||||
uci set masterlink.settings.enabled='0'
|
|
||||||
uci set masterlink.settings.role='standalone'
|
|
||||||
uci set masterlink.settings.status='disconnected'
|
|
||||||
uci delete masterlink.settings.master_ip 2>/dev/null
|
|
||||||
uci delete masterlink.settings.master_fingerprint 2>/dev/null
|
|
||||||
uci delete masterlink.settings.depth 2>/dev/null
|
|
||||||
uci delete masterlink.settings.joined_at 2>/dev/null
|
|
||||||
uci commit masterlink
|
|
||||||
|
|
||||||
json_init
|
|
||||||
json_add_string "status" "ok"
|
|
||||||
json_add_string "message" "Left mesh network"
|
|
||||||
json_dump
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get current status
|
|
||||||
do_status() {
|
|
||||||
local enabled=$(uci -q get masterlink.settings.enabled || echo '0')
|
|
||||||
local role=$(uci -q get masterlink.settings.role || echo 'standalone')
|
|
||||||
local status=$(uci -q get masterlink.settings.status || echo 'disconnected')
|
|
||||||
local master_ip=$(uci -q get masterlink.settings.master_ip)
|
|
||||||
local master_fp=$(uci -q get masterlink.settings.master_fingerprint)
|
|
||||||
local depth=$(uci -q get masterlink.settings.depth || echo '0')
|
|
||||||
local joined_at=$(uci -q get masterlink.settings.joined_at)
|
|
||||||
|
|
||||||
json_init
|
|
||||||
json_add_boolean "enabled" "$enabled"
|
|
||||||
json_add_string "role" "$role"
|
|
||||||
json_add_string "status" "$status"
|
|
||||||
json_add_string "master_ip" "$master_ip"
|
|
||||||
json_add_string "master_fingerprint" "$master_fp"
|
|
||||||
json_add_int "depth" "$depth"
|
|
||||||
json_add_string "joined_at" "$joined_at"
|
|
||||||
json_add_string "fingerprint" "$(get_fingerprint)"
|
|
||||||
json_dump
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get local node info
|
|
||||||
do_info() {
|
|
||||||
json_init
|
|
||||||
json_add_string "fingerprint" "$(get_fingerprint)"
|
|
||||||
json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'openwrt')"
|
|
||||||
json_add_string "address" "$(get_local_ip)"
|
|
||||||
json_add_string "model" "$(get_model)"
|
|
||||||
json_add_string "firmware" "$(cat /etc/openwrt_release 2>/dev/null | grep DISTRIB_RELEASE | cut -d= -f2 | tr -d \"\')"
|
|
||||||
json_dump
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify master before joining (get master info without committing)
|
|
||||||
do_verify() {
|
|
||||||
local master_ip="$1"
|
|
||||||
local token="$2"
|
|
||||||
|
|
||||||
# Request master info
|
|
||||||
local response=$(wget -qO- \
|
|
||||||
--timeout=10 \
|
|
||||||
"http://${master_ip}:7331/api/v1/p2p/master-link/info?token=${token}" 2>/dev/null)
|
|
||||||
|
|
||||||
if [ -z "$response" ]; then
|
|
||||||
json_init
|
|
||||||
json_add_string "status" "error"
|
|
||||||
json_add_string "message" "Failed to connect to master"
|
|
||||||
json_dump
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$response"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
list)
|
|
||||||
echo '{"status":{},"join":{"master_ip":"str","token":"str"},"leave":{},"info":{},"verify":{"master_ip":"str","token":"str"}}'
|
|
||||||
;;
|
|
||||||
call)
|
|
||||||
case "$2" in
|
|
||||||
status)
|
|
||||||
do_status
|
|
||||||
;;
|
|
||||||
join)
|
|
||||||
read -r input
|
|
||||||
json_load "$input"
|
|
||||||
json_get_var master_ip master_ip
|
|
||||||
json_get_var token token
|
|
||||||
do_join "$master_ip" "$token"
|
|
||||||
;;
|
|
||||||
leave)
|
|
||||||
do_leave
|
|
||||||
;;
|
|
||||||
info)
|
|
||||||
do_info
|
|
||||||
;;
|
|
||||||
verify)
|
|
||||||
read -r input
|
|
||||||
json_load "$input"
|
|
||||||
json_get_var master_ip master_ip
|
|
||||||
json_get_var token token
|
|
||||||
do_verify "$master_ip" "$token"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo '{"error":"Unknown method"}'
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
config mesh 'settings'
|
|
||||||
option enabled '0'
|
|
||||||
option role 'standalone'
|
|
||||||
option status 'disconnected'
|
|
||||||
option master_ip ''
|
|
||||||
option master_fingerprint ''
|
|
||||||
option depth '0'
|
|
||||||
option joined_at ''
|
|
||||||
@ -1,274 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
'require view';
|
|
||||||
'require dom';
|
|
||||||
'require poll';
|
|
||||||
'require rpc';
|
|
||||||
'require ui';
|
|
||||||
|
|
||||||
var callMasterLinkStatus = rpc.declare({
|
|
||||||
object: 'luci.masterlink',
|
|
||||||
method: 'status',
|
|
||||||
expect: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
var callMasterLinkInfo = rpc.declare({
|
|
||||||
object: 'luci.masterlink',
|
|
||||||
method: 'info',
|
|
||||||
expect: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
var callMasterLinkVerify = rpc.declare({
|
|
||||||
object: 'luci.masterlink',
|
|
||||||
method: 'verify',
|
|
||||||
params: ['master_ip', 'token'],
|
|
||||||
expect: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
var callMasterLinkJoin = rpc.declare({
|
|
||||||
object: 'luci.masterlink',
|
|
||||||
method: 'join',
|
|
||||||
params: ['master_ip', 'token'],
|
|
||||||
expect: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
var callMasterLinkLeave = rpc.declare({
|
|
||||||
object: 'luci.masterlink',
|
|
||||||
method: 'leave',
|
|
||||||
expect: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Parse invite URL to extract master IP and token
|
|
||||||
function parseInviteUrl(input) {
|
|
||||||
input = input.trim();
|
|
||||||
|
|
||||||
// Pattern 1: Full URL - http://IP:PORT/path?token=XXX or https://...
|
|
||||||
var urlMatch = input.match(/https?:\/\/([^:/]+)(?::\d+)?.*[?&]token=([^&\s]+)/i);
|
|
||||||
if (urlMatch) {
|
|
||||||
return { master_ip: urlMatch[1], token: urlMatch[2] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern 2: IP and token separated by space or comma
|
|
||||||
var spaceMatch = input.match(/^([0-9.]+)[,\s]+([a-zA-Z0-9_-]+)$/);
|
|
||||||
if (spaceMatch) {
|
|
||||||
return { master_ip: spaceMatch[1], token: spaceMatch[2] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern 3: Just a token (need to ask for IP separately or use default)
|
|
||||||
if (/^[a-zA-Z0-9_-]{16,}$/.test(input)) {
|
|
||||||
return { token: input, master_ip: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatStatus(status) {
|
|
||||||
switch (status) {
|
|
||||||
case 'approved':
|
|
||||||
return E('span', { 'class': 'badge', 'style': 'background:#2e7d32;color:#fff' }, 'Connected');
|
|
||||||
case 'pending':
|
|
||||||
return E('span', { 'class': 'badge', 'style': 'background:#f57c00;color:#fff' }, 'Pending Approval');
|
|
||||||
case 'disconnected':
|
|
||||||
default:
|
|
||||||
return E('span', { 'class': 'badge', 'style': 'background:#616161;color:#fff' }, 'Not Connected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view.extend({
|
|
||||||
load: function() {
|
|
||||||
return Promise.all([
|
|
||||||
callMasterLinkStatus(),
|
|
||||||
callMasterLinkInfo()
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function(data) {
|
|
||||||
var status = data[0] || {};
|
|
||||||
var info = data[1] || {};
|
|
||||||
var view = this;
|
|
||||||
|
|
||||||
var statusSection = E('div', { 'class': 'cbi-section' }, [
|
|
||||||
E('h3', 'Mesh Status'),
|
|
||||||
E('table', { 'class': 'table' }, [
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td', 'style': 'width:200px' }, 'Status'),
|
|
||||||
E('td', { 'class': 'td' }, formatStatus(status.status))
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Role'),
|
|
||||||
E('td', { 'class': 'td' }, status.role || 'standalone')
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Local Fingerprint'),
|
|
||||||
E('td', { 'class': 'td' }, E('code', {}, status.fingerprint || info.fingerprint || 'N/A'))
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Hostname'),
|
|
||||||
E('td', { 'class': 'td' }, info.hostname || 'N/A')
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'IP Address'),
|
|
||||||
E('td', { 'class': 'td' }, info.address || 'N/A')
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
// If connected, show master info
|
|
||||||
if (status.enabled == 1 && status.master_ip) {
|
|
||||||
statusSection.appendChild(E('table', { 'class': 'table', 'style': 'margin-top:1em' }, [
|
|
||||||
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
|
|
||||||
E('th', { 'class': 'th', 'colspan': 2 }, 'Connected Master')
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Master IP'),
|
|
||||||
E('td', { 'class': 'td' }, status.master_ip)
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Master Fingerprint'),
|
|
||||||
E('td', { 'class': 'td' }, E('code', {}, status.master_fingerprint || 'N/A'))
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Depth'),
|
|
||||||
E('td', { 'class': 'td' }, String(status.depth || 0))
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr' }, [
|
|
||||||
E('td', { 'class': 'td' }, 'Joined At'),
|
|
||||||
E('td', { 'class': 'td' }, status.joined_at || 'N/A')
|
|
||||||
])
|
|
||||||
]));
|
|
||||||
|
|
||||||
// Leave button
|
|
||||||
var leaveBtn = E('button', {
|
|
||||||
'class': 'btn cbi-button cbi-button-negative',
|
|
||||||
'click': ui.createHandlerFn(this, function() {
|
|
||||||
return callMasterLinkLeave().then(function(res) {
|
|
||||||
if (res.status === 'ok') {
|
|
||||||
ui.addNotification(null, E('p', 'Left mesh network'), 'info');
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
ui.addNotification(null, E('p', 'Failed to leave: ' + (res.message || 'Unknown error')), 'error');
|
|
||||||
}
|
|
||||||
}).catch(function(err) {
|
|
||||||
ui.addNotification(null, E('p', 'Error: ' + err.message), 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}, 'Leave Mesh');
|
|
||||||
|
|
||||||
statusSection.appendChild(E('div', { 'style': 'margin-top:1em' }, leaveBtn));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join section (only if not connected)
|
|
||||||
var joinSection = E('div', { 'class': 'cbi-section' });
|
|
||||||
|
|
||||||
if (status.enabled != 1) {
|
|
||||||
var inviteInput = E('input', {
|
|
||||||
'type': 'text',
|
|
||||||
'class': 'cbi-input-text',
|
|
||||||
'id': 'invite-url',
|
|
||||||
'style': 'width:100%;font-family:monospace',
|
|
||||||
'placeholder': 'http://192.168.1.1:7331/master-link/?token=abc123... or: 192.168.1.1 abc123token'
|
|
||||||
});
|
|
||||||
|
|
||||||
var verifyBtn = E('button', {
|
|
||||||
'class': 'btn cbi-button',
|
|
||||||
'style': 'margin-right:0.5em',
|
|
||||||
'click': ui.createHandlerFn(this, function() {
|
|
||||||
var input = document.getElementById('invite-url').value;
|
|
||||||
var parsed = parseInviteUrl(input);
|
|
||||||
|
|
||||||
if (!parsed || !parsed.master_ip) {
|
|
||||||
ui.addNotification(null, E('p', 'Invalid invite URL or token'), 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return callMasterLinkVerify(parsed.master_ip, parsed.token).then(function(res) {
|
|
||||||
if (res.status === 'error') {
|
|
||||||
ui.addNotification(null, E('p', 'Verification failed: ' + (res.message || 'Unknown error')), 'error');
|
|
||||||
} else {
|
|
||||||
var msg = E('div', {}, [
|
|
||||||
E('p', { 'style': 'font-weight:bold' }, 'Master Node Information:'),
|
|
||||||
E('ul', {}, [
|
|
||||||
E('li', {}, 'Hostname: ' + (res.hostname || 'N/A')),
|
|
||||||
E('li', {}, 'Fingerprint: ' + (res.fingerprint || 'N/A')),
|
|
||||||
E('li', {}, 'Network: ' + (res.network_name || 'N/A'))
|
|
||||||
]),
|
|
||||||
E('p', { 'style': 'color:#f57c00' }, 'Verify this matches the expected master before joining!')
|
|
||||||
]);
|
|
||||||
ui.addNotification(null, msg, 'info');
|
|
||||||
}
|
|
||||||
}).catch(function(err) {
|
|
||||||
ui.addNotification(null, E('p', 'Verification error: ' + err.message), 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}, 'Verify Master');
|
|
||||||
|
|
||||||
var joinBtn = E('button', {
|
|
||||||
'class': 'btn cbi-button cbi-button-positive',
|
|
||||||
'click': ui.createHandlerFn(this, function() {
|
|
||||||
var input = document.getElementById('invite-url').value;
|
|
||||||
var parsed = parseInviteUrl(input);
|
|
||||||
|
|
||||||
if (!parsed || !parsed.master_ip) {
|
|
||||||
ui.addNotification(null, E('p', 'Invalid invite URL or token'), 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return callMasterLinkJoin(parsed.master_ip, parsed.token).then(function(res) {
|
|
||||||
if (res.status === 'approved') {
|
|
||||||
ui.addNotification(null, E('p', 'Successfully joined mesh network!'), 'success');
|
|
||||||
window.location.reload();
|
|
||||||
} else if (res.status === 'pending') {
|
|
||||||
ui.addNotification(null, E('p', 'Join request submitted. Waiting for master approval.'), 'info');
|
|
||||||
window.location.reload();
|
|
||||||
} else if (res.status === 'error') {
|
|
||||||
ui.addNotification(null, E('p', 'Join failed: ' + (res.message || 'Unknown error')), 'error');
|
|
||||||
} else {
|
|
||||||
ui.addNotification(null, E('p', 'Unexpected response: ' + JSON.stringify(res)), 'warning');
|
|
||||||
}
|
|
||||||
}).catch(function(err) {
|
|
||||||
ui.addNotification(null, E('p', 'Join error: ' + err.message), 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}, 'Join Mesh');
|
|
||||||
|
|
||||||
joinSection.appendChild(E('h3', 'Join Mesh Network'));
|
|
||||||
joinSection.appendChild(E('p', { 'class': 'cbi-section-descr' },
|
|
||||||
'Enter the invite URL or token provided by the mesh master to join the network.'));
|
|
||||||
joinSection.appendChild(E('div', { 'class': 'cbi-value' }, [
|
|
||||||
E('label', { 'class': 'cbi-value-title' }, 'Invite URL / Token'),
|
|
||||||
E('div', { 'class': 'cbi-value-field' }, inviteInput)
|
|
||||||
]));
|
|
||||||
joinSection.appendChild(E('div', { 'class': 'cbi-page-actions' }, [
|
|
||||||
verifyBtn,
|
|
||||||
joinBtn
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI help section
|
|
||||||
var cliSection = E('div', { 'class': 'cbi-section' }, [
|
|
||||||
E('h3', 'Command Line'),
|
|
||||||
E('p', { 'class': 'cbi-section-descr' }, 'You can also join using the CLI tool:'),
|
|
||||||
E('pre', { 'style': 'background:#1a1a1a;padding:1em;border-radius:4px;overflow-x:auto' }, [
|
|
||||||
E('code', {}, [
|
|
||||||
'# Using IP and token\n',
|
|
||||||
'sbx-mesh-join 192.168.1.1 abc123token\n\n',
|
|
||||||
'# Using full URL\n',
|
|
||||||
'sbx-mesh-join \'http://192.168.1.1:7331/master-link/?token=abc123\'\n\n',
|
|
||||||
'# One-liner from master\n',
|
|
||||||
'wget -qO- \'http://master-ip:7331/api/v1/p2p/master-link/join-script?token=xxx\' | sh'
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
return E('div', { 'class': 'cbi-map' }, [
|
|
||||||
E('h2', 'Master-Link'),
|
|
||||||
E('div', { 'class': 'cbi-map-descr' }, 'Join and manage SecuBox mesh network membership.'),
|
|
||||||
statusSection,
|
|
||||||
joinSection,
|
|
||||||
cliSection
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSaveApply: null,
|
|
||||||
handleSave: null,
|
|
||||||
handleReset: null
|
|
||||||
});
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# SecuBox Mesh Invite Generator
|
|
||||||
# Generates a join URL that can be shared with peer nodes
|
|
||||||
# Usage: sbx-mesh-invite [--auto-approve] [--ttl SECONDS]
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
TOKENS_FILE="/var/lib/secubox/p2p/master-link/tokens.json"
|
|
||||||
AUTO_APPROVE="true"
|
|
||||||
TTL=3600
|
|
||||||
MASTER_IP=""
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--auto-approve|-a)
|
|
||||||
AUTO_APPROVE="true"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--manual-approve|-m)
|
|
||||||
AUTO_APPROVE="false"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--ttl|-t)
|
|
||||||
TTL="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--ip|-i)
|
|
||||||
MASTER_IP="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--help|-h)
|
|
||||||
cat << 'EOF'
|
|
||||||
SecuBox Mesh Invite Generator
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
sbx-mesh-invite [options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-a, --auto-approve Auto-approve joining nodes (default)
|
|
||||||
-m, --manual-approve Require manual approval
|
|
||||||
-t, --ttl SECONDS Token validity in seconds (default: 3600)
|
|
||||||
-i, --ip ADDRESS Master IP address for the invite URL
|
|
||||||
-h, --help Show this help
|
|
||||||
|
|
||||||
Output:
|
|
||||||
Prints a join URL that can be copy-pasted to peer nodes.
|
|
||||||
On the peer, run: sbx-mesh-join <URL>
|
|
||||||
EOF
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown option: $1" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Ensure tokens directory exists
|
|
||||||
mkdir -p "$(dirname "$TOKENS_FILE")"
|
|
||||||
|
|
||||||
# Generate random token (32 hex chars)
|
|
||||||
if command -v openssl >/dev/null; then
|
|
||||||
TOKEN=$(openssl rand -hex 16)
|
|
||||||
elif [ -r /dev/urandom ]; then
|
|
||||||
TOKEN=$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
|
||||||
else
|
|
||||||
# Fallback: use date + random
|
|
||||||
TOKEN=$(date +%s%N | sha256sum | head -c 32)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Calculate hash
|
|
||||||
if command -v sha256sum >/dev/null; then
|
|
||||||
HASH=$(printf '%s' "$TOKEN" | sha256sum | cut -d' ' -f1)
|
|
||||||
elif command -v openssl >/dev/null; then
|
|
||||||
HASH=$(printf '%s' "$TOKEN" | openssl dgst -sha256 | awk '{print $2}')
|
|
||||||
else
|
|
||||||
echo "Error: No sha256 tool available" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get timestamps
|
|
||||||
NOW=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S)
|
|
||||||
NOW_TS=$(date +%s)
|
|
||||||
EXPIRES_TS=$((NOW_TS + TTL))
|
|
||||||
EXPIRES=$(date -Iseconds -d "@$EXPIRES_TS" 2>/dev/null || date -r "$EXPIRES_TS" -Iseconds 2>/dev/null || echo "$EXPIRES_TS")
|
|
||||||
|
|
||||||
# Get local IP for URL
|
|
||||||
LOCAL_IP="$MASTER_IP"
|
|
||||||
if [ -z "$LOCAL_IP" ]; then
|
|
||||||
# Try common LAN interfaces first, prefer 192.168.x.x addresses
|
|
||||||
for iface in br-lan eth0 enp0s8 enp0s3 ens3; do
|
|
||||||
candidate=$(ip -4 addr show "$iface" 2>/dev/null | grep -oE 'inet 192\.168\.[0-9.]+' | awk '{print $2}' | head -1)
|
|
||||||
if [ -n "$candidate" ]; then
|
|
||||||
LOCAL_IP="$candidate"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
# Fallback to any non-loopback, non-10.x address
|
|
||||||
if [ -z "$LOCAL_IP" ]; then
|
|
||||||
LOCAL_IP=$(ip -4 addr show 2>/dev/null | grep -oE 'inet [0-9.]+' | grep -v '127.0.0' | grep -v 'inet 10\.' | awk '{print $2}' | head -1)
|
|
||||||
fi
|
|
||||||
[ -z "$LOCAL_IP" ] && LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
||||||
[ -z "$LOCAL_IP" ] && LOCAL_IP="<MASTER_IP>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create new token entry
|
|
||||||
NEW_TOKEN="{
|
|
||||||
\"hash\": \"$HASH\",
|
|
||||||
\"type\": \"join\",
|
|
||||||
\"created\": \"$NOW\",
|
|
||||||
\"expires\": \"$EXPIRES\",
|
|
||||||
\"expires_ts\": $EXPIRES_TS,
|
|
||||||
\"ttl\": $TTL,
|
|
||||||
\"status\": \"active\",
|
|
||||||
\"auto_approve\": $AUTO_APPROVE
|
|
||||||
}"
|
|
||||||
|
|
||||||
# Add to tokens file
|
|
||||||
if [ -f "$TOKENS_FILE" ]; then
|
|
||||||
# Append to existing array using Python or jq
|
|
||||||
if command -v python3 >/dev/null; then
|
|
||||||
python3 << PYEOF
|
|
||||||
import json
|
|
||||||
with open("$TOKENS_FILE", "r") as f:
|
|
||||||
tokens = json.load(f)
|
|
||||||
tokens.append(json.loads('''$NEW_TOKEN'''))
|
|
||||||
with open("$TOKENS_FILE", "w") as f:
|
|
||||||
json.dump(tokens, f, indent=2)
|
|
||||||
PYEOF
|
|
||||||
elif command -v jq >/dev/null; then
|
|
||||||
tmp=$(mktemp)
|
|
||||||
jq ". + [$NEW_TOKEN]" "$TOKENS_FILE" > "$tmp" && mv "$tmp" "$TOKENS_FILE"
|
|
||||||
else
|
|
||||||
# Manual append (risky but works for simple cases)
|
|
||||||
sed -i 's/]$/,'"$(echo "$NEW_TOKEN" | tr -d '\n')"']/' "$TOKENS_FILE"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[$NEW_TOKEN]" > "$TOKENS_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Output the invite URL
|
|
||||||
echo ""
|
|
||||||
echo "Mesh Invite Generated"
|
|
||||||
echo "====================="
|
|
||||||
echo ""
|
|
||||||
echo "Token: $TOKEN"
|
|
||||||
echo "Expires: $EXPIRES"
|
|
||||||
echo "Auto-approve: $AUTO_APPROVE"
|
|
||||||
echo ""
|
|
||||||
echo "Join URL (copy this to the peer node):"
|
|
||||||
echo ""
|
|
||||||
echo " https://${LOCAL_IP}/master-link/?token=${TOKEN}"
|
|
||||||
echo ""
|
|
||||||
echo "Or run on the peer:"
|
|
||||||
echo ""
|
|
||||||
echo " sbx-mesh-join ${LOCAL_IP} ${TOKEN}"
|
|
||||||
echo ""
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# SecuBox Mesh Join CLI Tool
|
|
||||||
# Usage: sbx-mesh-join <master-ip> <token>
|
|
||||||
# or: sbx-mesh-join <invite-url>
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
NODE_ID_FILE="/etc/secubox/node.id"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
log_info() { printf "${GREEN}[+]${NC} %s\n" "$1"; }
|
|
||||||
log_warn() { printf "${YELLOW}[!]${NC} %s\n" "$1"; }
|
|
||||||
log_error() { printf "${RED}[-]${NC} %s\n" "$1"; }
|
|
||||||
log_step() { printf "${CYAN}[*]${NC} %s\n" "$1"; }
|
|
||||||
|
|
||||||
# Get or generate node fingerprint
|
|
||||||
get_fingerprint() {
|
|
||||||
if [ -f "$NODE_ID_FILE" ]; then
|
|
||||||
cat "$NODE_ID_FILE"
|
|
||||||
else
|
|
||||||
mkdir -p /etc/secubox
|
|
||||||
local mac=""
|
|
||||||
if [ -f /sys/class/net/br-lan/address ]; then
|
|
||||||
mac=$(cat /sys/class/net/br-lan/address | tr -d ':')
|
|
||||||
elif [ -f /sys/class/net/eth0/address ]; then
|
|
||||||
mac=$(cat /sys/class/net/eth0/address | tr -d ':')
|
|
||||||
else
|
|
||||||
mac=$(cat /sys/class/net/*/address 2>/dev/null | head -1 | tr -d ':')
|
|
||||||
fi
|
|
||||||
local fp="owrt-${mac}"
|
|
||||||
echo "$fp" > "$NODE_ID_FILE"
|
|
||||||
echo "$fp"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get local IP
|
|
||||||
get_local_ip() {
|
|
||||||
ip -4 addr show br-lan 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1 || \
|
|
||||||
ip -4 addr show eth0 2>/dev/null | grep -oE 'inet [0-9.]+' | awk '{print $2}' | head -1 || \
|
|
||||||
echo "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse URL to extract IP and token
|
|
||||||
parse_url() {
|
|
||||||
local url="$1"
|
|
||||||
# Match: http(s)://IP(:PORT)/...?token=XXX
|
|
||||||
echo "$url" | sed -n 's|.*://\([^:/]*\).*[?&]token=\([^&]*\).*|\1 \2|p'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
usage() {
|
|
||||||
cat << 'EOF'
|
|
||||||
SecuBox Mesh Join Tool
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
sbx-mesh-join <master-ip> <token>
|
|
||||||
sbx-mesh-join <invite-url>
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
sbx-mesh-join 192.168.1.1 abc123def456
|
|
||||||
sbx-mesh-join 'https://192.168.1.1/master-link/?token=abc123'
|
|
||||||
sbx-mesh-join 'https://master.local/master-link/?token=abc123'
|
|
||||||
|
|
||||||
The tool will:
|
|
||||||
1. Generate a unique node fingerprint (if not exists)
|
|
||||||
2. Collect local device info (hostname, IP, model)
|
|
||||||
3. Send join request to the master
|
|
||||||
4. Save mesh configuration on success
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
|
||||||
main() {
|
|
||||||
local master_ip=""
|
|
||||||
local token=""
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
case "$#" in
|
|
||||||
1)
|
|
||||||
# Single argument - could be URL
|
|
||||||
local parsed=$(parse_url "$1")
|
|
||||||
if [ -n "$parsed" ]; then
|
|
||||||
master_ip=$(echo "$parsed" | awk '{print $1}')
|
|
||||||
token=$(echo "$parsed" | awk '{print $2}')
|
|
||||||
else
|
|
||||||
log_error "Invalid URL format"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
2)
|
|
||||||
# Two arguments: IP and token
|
|
||||||
master_ip="$1"
|
|
||||||
token="$2"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -z "$master_ip" ] || [ -z "$token" ]; then
|
|
||||||
log_error "Missing master IP or token"
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "SecuBox Mesh Join"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Gather local info
|
|
||||||
log_step "Collecting node information..."
|
|
||||||
local fingerprint=$(get_fingerprint)
|
|
||||||
local hostname=$(uci -q get system.@system[0].hostname 2>/dev/null || hostname || echo "openwrt")
|
|
||||||
local address=$(get_local_ip)
|
|
||||||
local model=""
|
|
||||||
if [ -f /tmp/sysinfo/model ]; then
|
|
||||||
model=$(cat /tmp/sysinfo/model)
|
|
||||||
elif command -v jsonfilter >/dev/null && [ -f /etc/board.json ]; then
|
|
||||||
model=$(jsonfilter -i /etc/board.json -e '@.model.name' 2>/dev/null || echo "Unknown")
|
|
||||||
else
|
|
||||||
model="Unknown"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Fingerprint: $fingerprint"
|
|
||||||
log_info "Hostname: $hostname"
|
|
||||||
log_info "Address: $address"
|
|
||||||
log_info "Model: $model"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Prepare JSON payload
|
|
||||||
local payload="{\"token\":\"${token}\",\"fingerprint\":\"${fingerprint}\",\"hostname\":\"${hostname}\",\"address\":\"${address}\",\"model\":\"${model}\"}"
|
|
||||||
|
|
||||||
log_step "Connecting to master at ${master_ip}..."
|
|
||||||
|
|
||||||
# Send join request (HTTPS with self-signed cert support)
|
|
||||||
local response=""
|
|
||||||
local api_url="https://${master_ip}/api/v1/p2p/master-link/join"
|
|
||||||
|
|
||||||
if command -v curl >/dev/null; then
|
|
||||||
response=$(curl -sf -k --connect-timeout 30 -X POST \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$payload" \
|
|
||||||
"$api_url" 2>/dev/null)
|
|
||||||
else
|
|
||||||
response=$(wget -qO- --no-check-certificate --timeout=30 \
|
|
||||||
--header="Content-Type: application/json" \
|
|
||||||
--post-data="$payload" \
|
|
||||||
"$api_url" 2>/dev/null)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$response" ]; then
|
|
||||||
log_error "Failed to connect to master"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Parse response
|
|
||||||
local status=""
|
|
||||||
local message=""
|
|
||||||
local master_fp=""
|
|
||||||
local depth=""
|
|
||||||
|
|
||||||
if command -v jsonfilter >/dev/null; then
|
|
||||||
status=$(echo "$response" | jsonfilter -e '@.status' 2>/dev/null || true)
|
|
||||||
message=$(echo "$response" | jsonfilter -e '@.message' 2>/dev/null || true)
|
|
||||||
master_fp=$(echo "$response" | jsonfilter -e '@.master_fingerprint' 2>/dev/null || true)
|
|
||||||
depth=$(echo "$response" | jsonfilter -e '@.depth' 2>/dev/null || true)
|
|
||||||
elif command -v jq >/dev/null; then
|
|
||||||
status=$(echo "$response" | jq -r '.status // empty')
|
|
||||||
message=$(echo "$response" | jq -r '.message // empty')
|
|
||||||
master_fp=$(echo "$response" | jq -r '.master_fingerprint // empty')
|
|
||||||
depth=$(echo "$response" | jq -r '.depth // empty')
|
|
||||||
else
|
|
||||||
# Fallback: grep for status
|
|
||||||
status=$(echo "$response" | grep -oE '"status"\s*:\s*"[^"]*"' | cut -d'"' -f4)
|
|
||||||
message=$(echo "$response" | grep -oE '"message"\s*:\s*"[^"]*"' | cut -d'"' -f4)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
case "$status" in
|
|
||||||
approved)
|
|
||||||
log_info "Successfully joined mesh network!"
|
|
||||||
[ -n "$master_fp" ] && log_info "Master fingerprint: $master_fp"
|
|
||||||
[ -n "$depth" ] && log_info "Network depth: $depth"
|
|
||||||
|
|
||||||
# Save to UCI if available
|
|
||||||
if command -v uci >/dev/null; then
|
|
||||||
log_step "Saving configuration..."
|
|
||||||
# Ensure config file and section exist
|
|
||||||
[ -f /etc/config/masterlink ] || touch /etc/config/masterlink
|
|
||||||
uci -q get masterlink.settings >/dev/null || uci set masterlink.settings='mesh'
|
|
||||||
uci set masterlink.settings.enabled='1'
|
|
||||||
uci set masterlink.settings.role='peer'
|
|
||||||
uci set masterlink.settings.status='approved'
|
|
||||||
uci set masterlink.settings.master_ip="$master_ip"
|
|
||||||
[ -n "$master_fp" ] && uci set masterlink.settings.master_fingerprint="$master_fp"
|
|
||||||
[ -n "$depth" ] && uci set masterlink.settings.depth="$depth"
|
|
||||||
uci set masterlink.settings.joined_at="$(date -Iseconds 2>/dev/null || date)"
|
|
||||||
uci commit masterlink
|
|
||||||
log_info "Configuration saved"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
pending)
|
|
||||||
log_warn "Join request submitted - waiting for master approval"
|
|
||||||
log_info "Check back later or ask the master admin to approve your node"
|
|
||||||
|
|
||||||
# Save pending state
|
|
||||||
if command -v uci >/dev/null; then
|
|
||||||
[ -f /etc/config/masterlink ] || touch /etc/config/masterlink
|
|
||||||
uci -q get masterlink.settings >/dev/null || uci set masterlink.settings='mesh'
|
|
||||||
uci set masterlink.settings.enabled='1'
|
|
||||||
uci set masterlink.settings.role='peer'
|
|
||||||
uci set masterlink.settings.status='pending'
|
|
||||||
uci set masterlink.settings.master_ip="$master_ip"
|
|
||||||
uci commit masterlink
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
error|rejected)
|
|
||||||
log_error "Join failed: ${message:-Unknown error}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_error "Unexpected response: $response"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
log_info "Done"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"admin/secubox/master-link": {
|
|
||||||
"title": "Master-Link",
|
|
||||||
"order": 90,
|
|
||||||
"action": {
|
|
||||||
"type": "view",
|
|
||||||
"path": "masterlink/join"
|
|
||||||
},
|
|
||||||
"depends": {
|
|
||||||
"acl": ["luci-app-masterlink"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"luci-app-masterlink": {
|
|
||||||
"description": "Grant access to Master-Link mesh enrollment",
|
|
||||||
"read": {
|
|
||||||
"ubus": {
|
|
||||||
"luci.masterlink": ["status", "info", "verify"]
|
|
||||||
},
|
|
||||||
"uci": ["masterlink"]
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"ubus": {
|
|
||||||
"luci.masterlink": ["join", "leave"]
|
|
||||||
},
|
|
||||||
"uci": ["masterlink"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user