feat: GL kit-connect ipk + one-button wizard + web dashboard for both platforms
Synology kit-connect (SPK): - wizard.sh — one-button Keylink IT fleet setup (auto-runs on install) - www/index.html — dark-themed local dashboard (same design as busrouter) - bin/serve-dashboard.sh — busybox httpd on port 8089 with CGI endpoints - start-stop-status updated to manage dashboard alongside daemon - INFO updated: RT2600ac + RT6600ax compatibility noted - postinst now runs wizard --auto for first-time setup GL kit-connect (OpenWrt ipk): - Makefile — DEPENDS: +curl +openssh-client +tailscale - procd init — USE_PROCD=1, respawn with 5s delay - connect-daemon.sh — Tailscale + reverse SSH (same as Synology version) - connect-wizard.sh — one-button setup, idempotent, UCI config - connect-register.sh — standalone hub registration - www/kitconnect/index.html — dark-themed dashboard with wizard button - www/kitconnect/wizard.cgi — web-triggered wizard endpoint - UCI config at /etc/config/kitconnect Both platforms share: same hub protocol, same Tailscale key, same port pool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -1,10 +1,10 @@
|
||||
package="kit-connect"
|
||||
version="0.1-0001"
|
||||
description="Unified management connectivity for Pioneer bus fleet routers. Single SPK — deploys Tailscale + reverse SSH tunnel. Hub auto-assigns ports on install. One package, all routers."
|
||||
description="Unified management connectivity for Pioneer bus fleet routers. Single SPK — deploys Tailscale + reverse SSH tunnel. Hub auto-assigns ports on install. One package, all routers. Compatible: RT2600ac, RT6600ax."
|
||||
displayname="KIT Bus Router Connect"
|
||||
maintainer="Keylink IT"
|
||||
arch="ipq806x"
|
||||
firmware="1.3.1-9346"
|
||||
checkport="no"
|
||||
startable="yes"
|
||||
displayname="KIT Bus Router Connect"
|
||||
thirdparty="yes"
|
||||
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
# serve-dashboard.sh — Lightweight HTTP server for the kit-connect dashboard.
|
||||
# Uses busybox httpd, serves on port 8089.
|
||||
# Started by start-stop-status alongside the main daemon.
|
||||
|
||||
PKG_DIR="/var/packages/kit-connect/target"
|
||||
WWW_DIR="${PKG_DIR}/www"
|
||||
CGI_DIR="${PKG_DIR}/www/cgi-bin"
|
||||
PID_FILE="${PKG_DIR}/var/dashboard.pid"
|
||||
PORT="${1:-8089}"
|
||||
|
||||
mkdir -p "$CGI_DIR" "$PKG_DIR/var"
|
||||
|
||||
# ── Write busybox httpd config ──────────────────────────────────
|
||||
cat > "$PKG_DIR/var/httpd.conf" <<EOF
|
||||
# kit-connect dashboard httpd config
|
||||
*.html:text/html
|
||||
*.css:text/css
|
||||
*.js:text/javascript
|
||||
*.json:application/json
|
||||
EOF
|
||||
|
||||
# ── Link CGI scripts to www/cgi-bin/ ───────────────────────────
|
||||
# Status endpoint
|
||||
cat > "$CGI_DIR/kitconnect-status" << 'CGIEOF'
|
||||
#!/bin/sh
|
||||
printf "Content-Type: application/json\r\n\r\n"
|
||||
|
||||
DEVICE_ID="unknown"
|
||||
DAEMON_RUNNING="false"
|
||||
TUNNEL_PORT="0"
|
||||
TAILSCALE_IP=""
|
||||
HUB_REACHABLE="false"
|
||||
|
||||
# Device detection
|
||||
DEVICE_ID=$(cat /etc/busrouter/device-id 2>/dev/null || hostname 2>/dev/null || echo "unknown")
|
||||
|
||||
# Daemon check
|
||||
[ -f /var/packages/kit-connect/target/var/daemon.pid ] && \
|
||||
kill -0 "$(cat /var/packages/kit-connect/target/var/daemon.pid)" 2>/dev/null && \
|
||||
DAEMON_RUNNING="true"
|
||||
|
||||
# Tunnel port from config
|
||||
TUNNEL_PORT=$(grep '^TUNNEL_REMOTE_PORT=' /etc/kit-connect/connect.conf 2>/dev/null | cut -d= -f2 || echo "0")
|
||||
|
||||
# Tailscale
|
||||
TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "")
|
||||
|
||||
# Hub check
|
||||
HUB_HOST=$(grep '^HUB_HOST=' /etc/kit-connect/connect.conf 2>/dev/null | cut -d= -f2 || echo "162.243.83.36")
|
||||
HUB_PORT=$(grep '^HUB_PORT=' /etc/kit-connect/connect.conf 2>/dev/null | cut -d= -f2 || echo "8080")
|
||||
timeout 3 bash -c "echo >/dev/tcp/${HUB_HOST}/${HUB_PORT}" 2>/dev/null && HUB_REACHABLE="true"
|
||||
|
||||
printf '{"device_id":"%s","daemon_running":%s,"tunnel_port":"%s","tailscale_ip":"%s","hub_host":"%s","hub_port":"%s","hub_reachable":%s,"config_path":"%s","log_path":"%s"}\n' \
|
||||
"$DEVICE_ID" "$DAEMON_RUNNING" "$TUNNEL_PORT" "$TAILSCALE_IP" \
|
||||
"$HUB_HOST" "$HUB_PORT" "$HUB_REACHABLE" \
|
||||
"/etc/kit-connect/connect.conf" \
|
||||
"/var/packages/kit-connect/target/var/connect.log"
|
||||
CGIEOF
|
||||
chmod +x "$CGI_DIR/kitconnect-status"
|
||||
|
||||
# Wizard endpoint
|
||||
cat > "$CGI_DIR/kitconnect-wizard" << 'CGIEOF'
|
||||
#!/bin/sh
|
||||
printf "Content-Type: text/plain\r\n\r\n"
|
||||
/var/packages/kit-connect/target/wizard.sh --force 2>&1
|
||||
CGIEOF
|
||||
chmod +x "$CGI_DIR/kitconnect-wizard"
|
||||
|
||||
# ── Start busybox httpd ────────────────────────────────────────
|
||||
if command -v busybox >/dev/null 2>&1; then
|
||||
# Kill any existing instance
|
||||
[ -f "$PID_FILE" ] && kill "$(cat "$PID_FILE")" 2>/dev/null
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
busybox httpd -p "$PORT" -h "$WWW_DIR" -c "$PKG_DIR/var/httpd.conf" -v 2>/dev/null &
|
||||
echo $! > "$PID_FILE"
|
||||
echo "Dashboard started on port ${PORT}"
|
||||
else
|
||||
echo "busybox not available — dashboard not started"
|
||||
exit 1
|
||||
fi
|
||||
+24
-38
@@ -1,22 +1,17 @@
|
||||
#!/bin/sh
|
||||
# build.sh — Assemble kit-connect SPK for Synology SRM (ipq806x)
|
||||
# Compatible: RT2600ac, RT6600ax, all ipq806x SRM routers.
|
||||
# Output: kit-connect-0.1-0001.spk
|
||||
#
|
||||
# SPK structure (outer tar, UNCOMPRESSED):
|
||||
# INFO
|
||||
# PACKAGE_ICON.PNG (64x64)
|
||||
# PACKAGE_ICON.PNG (72x72)
|
||||
# PACKAGE_ICON_256.PNG (256x256)
|
||||
# scripts/
|
||||
# postinst
|
||||
# start-stop-status
|
||||
# preinst
|
||||
# preuninst
|
||||
# postuninst
|
||||
# postinst, start-stop-status, preinst, preuninst, postuninst
|
||||
# conf/
|
||||
# privilege
|
||||
# resource
|
||||
# package.tgz (gzipped inner tar of bin/ + conf/)
|
||||
# checksum (md5 of package.tgz)
|
||||
# privilege, resource
|
||||
# package.tgz (gzipped inner tar: bin/ + conf/connect.conf + wizard.sh + www/)
|
||||
|
||||
set -e
|
||||
|
||||
@@ -30,42 +25,34 @@ BUILD_DIR="/tmp/kit-connect-build-$$"
|
||||
|
||||
echo "=== Building ${PKG_NAME}-${VERSION}.spk ==="
|
||||
|
||||
# Clean and create build staging directory
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# ── 1. Create inner package.tgz (gzipped tar of bin/ + conf/) ─────────
|
||||
# ── 1. Create inner package.tgz ─────────────────────────────────
|
||||
echo " → Creating package.tgz"
|
||||
tar -czf "$BUILD_DIR/package.tgz" \
|
||||
--format=ustar \
|
||||
bin/ \
|
||||
conf/connect.conf
|
||||
--format=ustar \
|
||||
bin/ \
|
||||
conf/connect.conf \
|
||||
wizard.sh \
|
||||
www/
|
||||
|
||||
# ── 2. Generate checksum ──────────────────────────────────────────────
|
||||
echo " → Computing checksum"
|
||||
md5sum "$BUILD_DIR/package.tgz" | awk '{print $1}' > "$BUILD_DIR/checksum"
|
||||
echo " → package.tgz contents:"
|
||||
tar tzf "$BUILD_DIR/package.tgz"
|
||||
|
||||
# ── 2. Stage and assemble outer SPK (NO checksum — deprecated) ──
|
||||
STAGE="$BUILD_DIR/stage"
|
||||
mkdir -p "$STAGE"
|
||||
cp INFO PACKAGE_ICON.PNG PACKAGE_ICON_256.PNG "$STAGE/"
|
||||
cp -r scripts "$STAGE/"
|
||||
mkdir -p "$STAGE/conf"
|
||||
cp conf/privilege conf/resource "$STAGE/conf/"
|
||||
cp "$BUILD_DIR/package.tgz" "$STAGE/"
|
||||
|
||||
# ── 3. Assemble outer SPK (UNCOMPRESSED tar) ──────────────────────────
|
||||
echo " → Assembling SPK"
|
||||
tar -cf "${OUTPUT}" \
|
||||
--format=ustar \
|
||||
-C "$SCRIPT_DIR" \
|
||||
INFO \
|
||||
PACKAGE_ICON.PNG \
|
||||
PACKAGE_ICON_256.PNG \
|
||||
scripts/ \
|
||||
-C "$BUILD_DIR" \
|
||||
package.tgz \
|
||||
checksum
|
||||
tar -cf "${OUTPUT}" -C "$STAGE" --format=ustar \
|
||||
INFO PACKAGE_ICON.PNG PACKAGE_ICON_256.PNG scripts conf package.tgz
|
||||
|
||||
# SPK conf/ goes in the outer tar too
|
||||
tar -rf "${OUTPUT}" \
|
||||
--format=ustar \
|
||||
-C "$SCRIPT_DIR" \
|
||||
conf/privilege \
|
||||
conf/resource
|
||||
|
||||
# ── 4. Verify ─────────────────────────────────────────────────────────
|
||||
echo " → Verifying SPK contents"
|
||||
tar -tf "${OUTPUT}" | sort
|
||||
|
||||
@@ -75,5 +62,4 @@ echo "=== Build complete: ${OUTPUT} ==="
|
||||
echo " Size: ${SIZE} bytes"
|
||||
echo " MD5: $(md5sum "${OUTPUT}" | awk '{print $1}')"
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$BUILD_DIR"
|
||||
|
||||
@@ -1,43 +1,39 @@
|
||||
#!/bin/sh
|
||||
# kit-connect postinst — register with hub, set up config
|
||||
# kit-connect postinst — run setup wizard, register with hub, set up config.
|
||||
# Idempotent — safe to run on upgrades too.
|
||||
|
||||
PKG_DIR="/var/packages/kit-connect/target"
|
||||
CONF="/etc/kit-connect/connect.conf"
|
||||
LOG_TAG="kit-connect"
|
||||
|
||||
log() { logger -t "$LOG_TAG" -p local0.warn "$*"; }
|
||||
|
||||
mkdir -p /etc/kit-connect "$PKG_DIR/var"
|
||||
mkdir -p /etc/kit-connect /etc/busrouter "$PKG_DIR/var"
|
||||
|
||||
# If config doesn't exist, create from template
|
||||
if [ ! -f "$CONF" ]; then
|
||||
cp "$PKG_DIR/conf/connect.conf" "$CONF"
|
||||
fi
|
||||
# Make scripts and wizard executable
|
||||
chmod +x "$PKG_DIR/wizard.sh" 2>/dev/null || true
|
||||
chmod +x "$PKG_DIR/bin/serve-dashboard.sh" 2>/dev/null || true
|
||||
chmod 600 "$PKG_DIR/bin/connect_id_ed25519" 2>/dev/null || true
|
||||
|
||||
# Prompt for device ID if not set
|
||||
DEVICE_ID=$(cat /etc/busrouter/device-id 2>/dev/null || echo "")
|
||||
if [ -z "$DEVICE_ID" ]; then
|
||||
DEVICE_ID=$(hostname 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
# Run the Keylink IT setup wizard (non-interactive)
|
||||
# This detects device ID, writes config, registers with hub, starts Tailscale.
|
||||
if [ -x "$PKG_DIR/wizard.sh" ]; then
|
||||
log "Running Keylink IT fleet setup wizard..."
|
||||
"$PKG_DIR/wizard.sh" --auto 2>&1 | while read -r line; do log "$line"; done
|
||||
else
|
||||
# Fallback: minimal config
|
||||
DEVICE_ID=$(cat /etc/busrouter/device-id 2>/dev/null || hostname 2>/dev/null || echo "unknown")
|
||||
echo "$DEVICE_ID" > /etc/busrouter/device-id
|
||||
|
||||
# Write device ID into config
|
||||
sed -i "s/^DEVICE_ID=.*/DEVICE_ID=$DEVICE_ID/" "$CONF" 2>/dev/null
|
||||
|
||||
# Set hostname for Tailscale
|
||||
sed -i "s/^TAILSCALE_HOSTNAME=.*/TAILSCALE_HOSTNAME=$DEVICE_ID/" "$CONF" 2>/dev/null
|
||||
|
||||
# Install tailscale binaries if not present
|
||||
if [ ! -f /usr/local/bin/tailscale ]; then
|
||||
if [ -f "$PKG_DIR/bin/tailscale" ]; then
|
||||
cp "$PKG_DIR/bin/tailscale" "$PKG_DIR/bin/tailscaled" /usr/local/bin/ 2>/dev/null
|
||||
chmod +x /usr/local/bin/tailscale /usr/local/bin/tailscaled 2>/dev/null
|
||||
CONF="/etc/kit-connect/connect.conf"
|
||||
if [ ! -f "$CONF" ]; then
|
||||
cp "$PKG_DIR/conf/connect.conf" "$CONF" 2>/dev/null || touch "$CONF"
|
||||
fi
|
||||
sed -i "s/^DEVICE_ID=.*/DEVICE_ID=${DEVICE_ID}/" "$CONF" 2>/dev/null
|
||||
sed -i "s/^TAILSCALE_HOSTNAME=.*/TAILSCALE_HOSTNAME=${DEVICE_ID}/" "$CONF" 2>/dev/null
|
||||
|
||||
# Try hub registration
|
||||
curl -s --connect-timeout 10 "http://162.243.83.36:8080/api/register/${DEVICE_ID}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Set key permissions
|
||||
chmod 600 "$PKG_DIR/bin/connect_id_ed25519" 2>/dev/null
|
||||
|
||||
# Try hub registration (non-fatal — daemon retries)
|
||||
curl -s --connect-timeout 10 "http://162.243.83.36:8080/api/register/${DEVICE_ID}" 2>/dev/null || true
|
||||
|
||||
log "postinst complete — device=${DEVICE_ID}"
|
||||
log "postinst complete — device_id=$(cat /etc/busrouter/device-id 2>/dev/null || echo unknown)"
|
||||
exit 0
|
||||
|
||||
@@ -4,28 +4,64 @@ PKG="kit-connect"
|
||||
PKG_DIR="/var/packages/${PKG}/target"
|
||||
DAEMON="${PKG_DIR}/bin/connect-daemon.sh"
|
||||
DAEMON_PID="${PKG_DIR}/var/daemon.pid"
|
||||
DASHBOARD="${PKG_DIR}/bin/serve-dashboard.sh"
|
||||
DASHBOARD_PID="${PKG_DIR}/var/dashboard.pid"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
mkdir -p "${PKG_DIR}/var"
|
||||
|
||||
# Start main daemon
|
||||
if [ -f "$DAEMON_PID" ] && kill -0 "$(cat "$DAEMON_PID")" 2>/dev/null; then
|
||||
echo "Already running"
|
||||
exit 0
|
||||
echo "Daemon already running"
|
||||
else
|
||||
setsid "$DAEMON" &
|
||||
echo $! > "$DAEMON_PID"
|
||||
echo "Daemon started"
|
||||
fi
|
||||
|
||||
# Start dashboard server (port 8089)
|
||||
if [ -x "$DASHBOARD" ] && command -v busybox >/dev/null 2>&1; then
|
||||
if [ -f "$DASHBOARD_PID" ] && kill -0 "$(cat "$DASHBOARD_PID")" 2>/dev/null; then
|
||||
echo "Dashboard already running"
|
||||
else
|
||||
"$DASHBOARD" 8089 &
|
||||
echo $! > "$DASHBOARD_PID"
|
||||
echo "Dashboard started on port 8089"
|
||||
fi
|
||||
fi
|
||||
setsid "$DAEMON" &
|
||||
echo $! > "$DAEMON_PID"
|
||||
echo "Started"
|
||||
;;
|
||||
stop)
|
||||
# Stop dashboard
|
||||
if [ -f "$DASHBOARD_PID" ]; then
|
||||
kill "$(cat "$DASHBOARD_PID")" 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 "$(cat "$DASHBOARD_PID")" 2>/dev/null
|
||||
rm -f "$DASHBOARD_PID"
|
||||
echo "Dashboard stopped"
|
||||
fi
|
||||
|
||||
# Stop daemon
|
||||
if [ -f "$DAEMON_PID" ]; then
|
||||
kill "$(cat "$DAEMON_PID")" 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 "$(cat "$DAEMON_PID")" 2>/dev/null
|
||||
rm -f "$DAEMON_PID"
|
||||
echo "Daemon stopped"
|
||||
fi
|
||||
echo "Stopped"
|
||||
;;
|
||||
status)
|
||||
if [ -f "$DAEMON_PID" ] && kill -0 "$(cat "$DAEMON_PID")" 2>/dev/null; then
|
||||
echo "Daemon: running (PID $(cat "$DAEMON_PID"))"
|
||||
else
|
||||
echo "Daemon: stopped"
|
||||
fi
|
||||
if [ -f "$DASHBOARD_PID" ] && kill -0 "$(cat "$DASHBOARD_PID")" 2>/dev/null; then
|
||||
echo "Dashboard: running on port 8089 (PID $(cat "$DASHBOARD_PID"))"
|
||||
exit 0
|
||||
else
|
||||
echo "Dashboard: stopped"
|
||||
fi
|
||||
[ -f "$DAEMON_PID" ] && kill -0 "$(cat "$DAEMON_PID")" 2>/dev/null && exit 0
|
||||
exit 1
|
||||
;;
|
||||
|
||||
Executable
+137
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
# wizard.sh — One-button Keylink IT fleet setup for Synology routers.
|
||||
# Idempotent — safe to run multiple times.
|
||||
# Usage: wizard.sh [--auto] [--force]
|
||||
# --auto Non-interactive (for postinst / web trigger)
|
||||
# --force Re-register even if already assigned
|
||||
#
|
||||
# Compatible: RT2600ac, RT6600ax (all ipq806x SRM routers)
|
||||
|
||||
set -e
|
||||
|
||||
HUB_HOST="162.243.83.36"
|
||||
HUB_PORT="8080"
|
||||
CONF="/etc/kit-connect/connect.conf"
|
||||
AUTO=0
|
||||
FORCE=0
|
||||
[ "$1" = "--auto" ] && AUTO=1
|
||||
[ "$1" = "--force" ] || [ "$2" = "--force" ] && FORCE=1
|
||||
|
||||
banner() {
|
||||
echo ""
|
||||
echo " ╔══════════════════════════════════════════╗"
|
||||
echo " ║ KEYLINK IT — Pioneer Bus Fleet Setup ║"
|
||||
echo " ╚══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
}
|
||||
|
||||
step() { echo " ▶ $*"; }
|
||||
ok() { echo " ✓ $*"; }
|
||||
warn() { echo " ⚠ $*"; }
|
||||
|
||||
banner
|
||||
|
||||
# ── Step 1: Detect device ──────────────────────────────────────
|
||||
step "Detecting device..."
|
||||
DEVICE_ID=$(cat /etc/busrouter/device-id 2>/dev/null || true)
|
||||
if [ -z "$DEVICE_ID" ]; then
|
||||
DEVICE_ID=$(hostname 2>/dev/null | sed 's/[^a-zA-Z0-9_-]//g' || echo "unknown")
|
||||
fi
|
||||
mkdir -p /etc/busrouter
|
||||
echo "$DEVICE_ID" > /etc/busrouter/device-id
|
||||
ok "Device ID: ${DEVICE_ID}"
|
||||
|
||||
# ── Step 2: Check prerequisites ────────────────────────────────
|
||||
step "Checking prerequisites..."
|
||||
for cmd in curl ssh ssh-keygen; do
|
||||
command -v "$cmd" >/dev/null 2>&1 || { echo " ✗ Missing: $cmd"; exit 1; }
|
||||
done
|
||||
ok "All prerequisites found (curl, ssh)"
|
||||
|
||||
# ── Step 3: Config file ────────────────────────────────────────
|
||||
step "Configuring Keylink IT defaults..."
|
||||
mkdir -p /etc/kit-connect /var/packages/kit-connect/target/var
|
||||
|
||||
if [ ! -f "$CONF" ]; then
|
||||
# Create from template if available
|
||||
if [ -f /var/packages/kit-connect/target/conf/connect.conf ]; then
|
||||
cp /var/packages/kit-connect/target/conf/connect.conf "$CONF"
|
||||
else
|
||||
touch "$CONF"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Write Keylink IT defaults
|
||||
_write() {
|
||||
grep -q "^${1}=" "$CONF" 2>/dev/null && sed -i "s|^${1}=.*|${1}=${2}|" "$CONF" || echo "${1}=${2}" >> "$CONF"
|
||||
}
|
||||
|
||||
_write "DEVICE_ID" "$DEVICE_ID"
|
||||
_write "HUB_HOST" "$HUB_HOST"
|
||||
_write "HUB_PORT" "$HUB_PORT"
|
||||
_write "TUNNEL_ENABLE" "1"
|
||||
_write "TUNNEL_REMOTE_HOST" "$HUB_HOST"
|
||||
_write "TUNNEL_REMOTE_USER" "node"
|
||||
_write "TUNNEL_REMOTE_SSH_PORT" "22"
|
||||
_write "TUNNEL_REMOTE_PORT" "0" # auto-assign
|
||||
_write "TUNNEL_LOCAL_SSH_PORT" "2223"
|
||||
_write "TUNNEL_RETRY_DELAY" "30"
|
||||
_write "TAILSCALE_ENABLE" "1"
|
||||
_write "TAILSCALE_AUTH_KEY" "tskey-auth-kJz8wqNVo211CNTRL-GNL5EFjp5aWQcaWPSVn2aW9TNworKUNBV"
|
||||
_write "TAILSCALE_HOSTNAME" "$DEVICE_ID"
|
||||
_write "WATCHDOG_INTERVAL" "60"
|
||||
_write "FORWARD_FAIL_LIMIT" "2"
|
||||
ok "Config written to ${CONF}"
|
||||
|
||||
# ── Step 4: Register with hub ──────────────────────────────────
|
||||
step "Registering with fleet hub (${HUB_HOST}:${HUB_PORT})..."
|
||||
|
||||
resp=$(curl -s --connect-timeout 10 "http://${HUB_HOST}:${HUB_PORT}/api/register/${DEVICE_ID}" 2>/dev/null) || true
|
||||
|
||||
if [ -z "$resp" ]; then
|
||||
warn "Hub unreachable — will retry on next daemon start"
|
||||
ok "Config saved locally (port will auto-assign when hub is reachable)"
|
||||
else
|
||||
port=$(echo "$resp" | grep -o '"tunnel_port"[[:space:]]*:[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
||||
status=$(echo "$resp" | grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)
|
||||
tskey=$(echo "$resp" | grep -o '"tailscale_auth_key"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -n "$port" ] && [ "$port" != "0" ]; then
|
||||
sed -i "s/^TUNNEL_REMOTE_PORT=.*/TUNNEL_REMOTE_PORT=${port}/" "$CONF" 2>/dev/null
|
||||
ok "Hub assigned port ${port} (status: ${status:-ok})"
|
||||
echo "$port" > /var/packages/kit-connect/target/var/hub_port
|
||||
fi
|
||||
if [ -n "$tskey" ]; then
|
||||
sed -i "s|^TAILSCALE_AUTH_KEY=.*|TAILSCALE_AUTH_KEY=${tskey}|" "$CONF" 2>/dev/null
|
||||
ok "Tailscale key configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Step 5: Dashboard server ───────────────────────────────────
|
||||
step "Setting up local dashboard..."
|
||||
mkdir -p /var/packages/kit-connect/target/www
|
||||
# Dashboard is served by busybox httpd on port 8089
|
||||
ok "Dashboard will be available on port 8089"
|
||||
|
||||
# ── Step 6: Start services ─────────────────────────────────────
|
||||
step "Starting kit-connect service..."
|
||||
if [ -x /var/packages/kit-connect/scripts/start-stop-status ]; then
|
||||
/var/packages/kit-connect/scripts/start-stop-status start 2>/dev/null && \
|
||||
ok "Service started" || warn "Service start returned non-zero"
|
||||
fi
|
||||
|
||||
# ── Done ───────────────────────────────────────────────────────
|
||||
ASSIGNED=$(grep '^TUNNEL_REMOTE_PORT=' "$CONF" 2>/dev/null | cut -d= -f2 || echo "pending")
|
||||
echo ""
|
||||
echo " ╔══════════════════════════════════════════╗"
|
||||
echo " ║ SETUP COMPLETE ║"
|
||||
echo " ╠══════════════════════════════════════════╣"
|
||||
printf " ║ Device: %-30s ║\n" "$DEVICE_ID"
|
||||
printf " ║ Port: %-30s ║\n" "$ASSIGNED"
|
||||
printf " ║ Hub: %-30s ║\n" "${HUB_HOST}:${HUB_PORT}"
|
||||
echo " ╠══════════════════════════════════════════╣"
|
||||
echo " ║ Dashboard: http://${DEVICE_ID}:8089/ ║"
|
||||
echo " ╚══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kit Connect — Fleet Status</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #0d1117; color: #e6edf3; min-height: 100vh; }
|
||||
.header { background: linear-gradient(135deg, #2563EB, #1d4ed8); padding: 16px 24px;
|
||||
display: flex; align-items: center; gap: 14px; border-bottom: 2px solid #60a5fa; }
|
||||
.logo { font-size: 22px; font-weight: 700; color: #fff; }
|
||||
.logo span { color: #93c5fd; }
|
||||
.sub { font-size: 12px; color: #bfdbfe; letter-spacing: 2px; text-transform: uppercase; }
|
||||
.refresh { margin-left: auto; font-size: 11px; color: #93c5fd; }
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 20px 16px; }
|
||||
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px;
|
||||
padding: 20px; margin-bottom: 16px; }
|
||||
.card-title { font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
|
||||
color: #8b949e; margin-bottom: 14px; }
|
||||
.badge { display: inline-block; padding: 2px 10px; border-radius: 12px;
|
||||
font-size: 11px; font-weight: 700; }
|
||||
.badge-ok { background: #1a4a2e; color: #3fb950; border: 1px solid #3fb950; }
|
||||
.badge-warn { background: #3d2b00; color: #d29922; border: 1px solid #d29922; }
|
||||
.badge-err { background: #4a1a1a; color: #f85149; border: 1px solid #f85149; }
|
||||
.row { display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 8px 0; border-bottom: 1px solid #21262d; }
|
||||
.row:last-child { border: none; }
|
||||
.label { color: #8b949e; font-size: 14px; }
|
||||
.value { font-weight: 600; font-size: 14px; font-family: "SF Mono", "Consolas", monospace; }
|
||||
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 10px 24px;
|
||||
border-radius: 6px; border: none; cursor: pointer; font-size: 14px;
|
||||
font-weight: 600; transition: all 0.2s; }
|
||||
.btn-wizard { background: linear-gradient(135deg, #2563EB, #1d4ed8); color: #fff;
|
||||
width: 100%; justify-content: center; font-size: 15px; padding: 14px; }
|
||||
.btn-wizard:hover { background: linear-gradient(135deg, #3b82f6, #2563EB); }
|
||||
.btn-wizard:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.output { margin-top: 12px; padding: 12px; background: #0d1117; border-radius: 5px;
|
||||
font-family: "SF Mono", "Consolas", monospace; font-size: 12px;
|
||||
white-space: pre-wrap; color: #3fb950; display: none; max-height: 300px; overflow-y: auto; }
|
||||
.footer { text-align: center; padding: 12px; font-size: 11px; color: #484f58;
|
||||
border-top: 1px solid #21262d; margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="logo">KIT <span>CONNECT</span></div>
|
||||
<div class="sub">Keylink IT Fleet Connectivity</div>
|
||||
</div>
|
||||
<div class="refresh" id="ts">Loading…</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">⚡ One-Click Setup</div>
|
||||
<p style="font-size:13px;color:#8b949e;margin-bottom:12px">
|
||||
Configures Keylink IT fleet defaults — device ID, hub, Tailscale, reverse SSH.
|
||||
Safe to run multiple times.
|
||||
</p>
|
||||
<button class="btn btn-wizard" id="wiz-btn" onclick="runWizard()">
|
||||
🔧 Run Setup Wizard
|
||||
</button>
|
||||
<div class="output" id="wiz-out"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📡 Connectivity Status</div>
|
||||
<div id="status-body"><div class="row"><span class="label">Loading…</span></div></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
kit-connect v0.1-0001 · device: <span id="dev-id">-</span> ·
|
||||
<span id="footer-ts"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var ESC = function(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); };
|
||||
|
||||
function row(label, value) {
|
||||
return '<div class="row"><span class="label">' + ESC(label) + '</span>' +
|
||||
'<span class="value">' + ESC(value) + '</span></div>';
|
||||
}
|
||||
|
||||
function badge(label, type) {
|
||||
return '<span class="badge badge-' + (type||'ok') + '">' + ESC(label) + '</span>';
|
||||
}
|
||||
|
||||
function poll() {
|
||||
document.getElementById('ts').textContent = 'Updated ' + new Date().toLocaleTimeString();
|
||||
document.getElementById('footer-ts').textContent = new Date().toLocaleTimeString();
|
||||
|
||||
// Check daemon, tunnel, tailscale via status endpoint
|
||||
fetch('/cgi-bin/kitconnect-status')
|
||||
.then(function(r) { return r.ok ? r.json() : Promise.reject(r.status); })
|
||||
.then(function(d) {
|
||||
var html = '';
|
||||
html += row('Device', d.device_id || '-');
|
||||
html += row('Daemon', (d.daemon_running ? badge('RUNNING','ok') : badge('STOPPED','err')));
|
||||
html += row('Tunnel', d.tunnel_port ? ('port ' + d.tunnel_port + ' ' + badge('ACTIVE','ok')) : badge('PENDING','warn'));
|
||||
html += row('Tailscale', d.tailscale_ip ? (d.tailscale_ip + ' ' + badge('CONNECTED','ok')) : badge('NOT CONNECTED','warn'));
|
||||
html += row('Hub', d.hub_host + ':' + d.hub_port + ' ' + badge(d.hub_reachable ? 'REACHABLE':'UNREACHABLE', d.hub_reachable ? 'ok' : 'warn'));
|
||||
html += row('Config', d.config_path);
|
||||
html += row('Log', d.log_path);
|
||||
document.getElementById('status-body').innerHTML = html;
|
||||
document.getElementById('dev-id').textContent = d.device_id || '-';
|
||||
})
|
||||
.catch(function(e) {
|
||||
document.getElementById('status-body').innerHTML =
|
||||
'<div class="row"><span class="label" style="color:#f85149">Dashboard unavailable (' + ESC(String(e)) + ')</span></div>';
|
||||
});
|
||||
}
|
||||
|
||||
function runWizard() {
|
||||
var btn = document.getElementById('wiz-btn');
|
||||
var out = document.getElementById('wiz-out');
|
||||
btn.disabled = true; btn.textContent = '⏳ Running…';
|
||||
out.style.display = 'block'; out.textContent = 'Starting wizard...\n';
|
||||
|
||||
fetch('/cgi-bin/kitconnect-wizard', { method: 'POST' })
|
||||
.then(function(r) { return r.text(); })
|
||||
.then(function(t) {
|
||||
out.textContent = t;
|
||||
out.style.color = t.includes('COMPLETE') ? '#3fb950' : '#d29922';
|
||||
})
|
||||
.catch(function(e) { out.textContent = 'ERROR: ' + e; out.style.color = '#f85149'; })
|
||||
.finally(function() { btn.disabled = false; btn.textContent = '🔧 Run Setup Wizard'; setTimeout(poll, 2000); });
|
||||
}
|
||||
|
||||
poll();
|
||||
setInterval(poll, 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user