mirror of
https://github.com/fosrl/newt.git
synced 2026-07-17 03:19:53 +00:00
3
packages/advantech/.gitignore
vendored
Normal file
3
packages/advantech/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.build
|
||||
*.tgz
|
||||
bin
|
||||
53
packages/advantech/Makefile
Normal file
53
packages/advantech/Makefile
Normal file
@@ -0,0 +1,53 @@
|
||||
MODNAME := newt
|
||||
VERSION := 1.12.5
|
||||
RELEASE_URL := https://github.com/fosrl/newt/releases/download/$(VERSION)
|
||||
PLATFORM ?= v4
|
||||
|
||||
# Map Advantech platform to newt release binary name
|
||||
arch_v4 := arm64
|
||||
arch_v4i := arm64
|
||||
arch_v3 := arm32
|
||||
arch_v2i := arm32
|
||||
|
||||
NEWT_ARCH := $(arch_$(PLATFORM))
|
||||
ifeq ($(NEWT_ARCH),)
|
||||
$(error Unknown platform '$(PLATFORM)'. Supported: v4, v4i, v3, v2i)
|
||||
endif
|
||||
|
||||
BINARY := newt_linux_$(NEWT_ARCH)
|
||||
BINDIR := bin
|
||||
OUTFILE := $(MODNAME).$(PLATFORM).tgz
|
||||
STAGEDIR := .build/$(MODNAME)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUTFILE)
|
||||
|
||||
# Cache the downloaded binary in bin/ (re-download only if missing)
|
||||
$(BINDIR)/$(BINARY):
|
||||
mkdir -p $(BINDIR)
|
||||
@echo "Downloading $(BINARY) $(VERSION) for platform $(PLATFORM)..."
|
||||
wget -q -O $@ "$(RELEASE_URL)/$(BINARY)" 2>/dev/null || \
|
||||
curl -fsSL -o $@ "$(RELEASE_URL)/$(BINARY)"
|
||||
chmod +x $@
|
||||
@echo "Binary ready: $@"
|
||||
|
||||
# Build the package
|
||||
$(OUTFILE): $(BINDIR)/$(BINARY) $(shell find merge -type f 2>/dev/null)
|
||||
@rm -rf $(STAGEDIR)
|
||||
@mkdir -p $(STAGEDIR)/bin
|
||||
@cp $(BINDIR)/$(BINARY) $(STAGEDIR)/bin/newt
|
||||
@chmod +x $(STAGEDIR)/bin/newt
|
||||
@cp -r merge/. $(STAGEDIR)/
|
||||
@chmod +x \
|
||||
$(STAGEDIR)/etc/init \
|
||||
$(STAGEDIR)/etc/install \
|
||||
$(STAGEDIR)/etc/uninstall \
|
||||
$(STAGEDIR)/www/index.cgi
|
||||
tar -c --owner=0 --group=0 --mtime="2001-01-01 UTC" \
|
||||
-C .build $(MODNAME) | gzip -n > $@
|
||||
@echo "Created: $@"
|
||||
|
||||
clean:
|
||||
rm -rf .build $(MODNAME).*.tgz
|
||||
@echo "Cleaned."
|
||||
61
packages/advantech/merge/etc/defaults
Normal file
61
packages/advantech/merge/etc/defaults
Normal file
@@ -0,0 +1,61 @@
|
||||
MOD_PANGOLIN_SITE_ENABLED=0
|
||||
MOD_PANGOLIN_SITE_ENDPOINT=
|
||||
MOD_PANGOLIN_SITE_ID=
|
||||
MOD_PANGOLIN_SITE_SECRET=
|
||||
|
||||
# Core networking
|
||||
MOD_PANGOLIN_SITE_MTU=
|
||||
MOD_PANGOLIN_SITE_DNS=
|
||||
MOD_PANGOLIN_SITE_INTERFACE=
|
||||
MOD_PANGOLIN_SITE_PORT=
|
||||
MOD_PANGOLIN_SITE_UPDOWN_SCRIPT=
|
||||
MOD_PANGOLIN_SITE_PREFER_ENDPOINT=
|
||||
|
||||
# Logging
|
||||
MOD_PANGOLIN_SITE_LOG_LEVEL=
|
||||
|
||||
# Behavior toggles (true/false)
|
||||
MOD_PANGOLIN_SITE_DISABLE_CLIENTS=
|
||||
MOD_PANGOLIN_SITE_USE_NATIVE_INTERFACE=
|
||||
MOD_PANGOLIN_SITE_ENFORCE_HC_CERT=
|
||||
MOD_PANGOLIN_SITE_NO_CLOUD=
|
||||
|
||||
# Timers
|
||||
MOD_PANGOLIN_SITE_PING_INTERVAL=
|
||||
MOD_PANGOLIN_SITE_PING_TIMEOUT=
|
||||
MOD_PANGOLIN_SITE_UDP_PROXY_IDLE_TIMEOUT=
|
||||
|
||||
# Docker integration
|
||||
MOD_PANGOLIN_SITE_DOCKER_SOCKET=
|
||||
MOD_PANGOLIN_SITE_DOCKER_ENFORCE_NETWORK_VALIDATION=
|
||||
|
||||
# Health / files
|
||||
MOD_PANGOLIN_SITE_HEALTH_FILE=
|
||||
MOD_PANGOLIN_SITE_BLUEPRINT_FILE=
|
||||
MOD_PANGOLIN_SITE_PROVISIONING_BLUEPRINT_FILE=
|
||||
MOD_PANGOLIN_SITE_CONFIG_FILE=
|
||||
|
||||
# Provisioning
|
||||
MOD_PANGOLIN_SITE_PROVISIONING_KEY=
|
||||
MOD_PANGOLIN_SITE_NAME=
|
||||
|
||||
# Auth daemon
|
||||
MOD_PANGOLIN_SITE_AUTH_DAEMON_ENABLED=
|
||||
MOD_PANGOLIN_SITE_AD_GENERATE_RANDOM_PASSWORD=
|
||||
MOD_PANGOLIN_SITE_AD_KEY=
|
||||
MOD_PANGOLIN_SITE_AD_PRINCIPALS_FILE=
|
||||
MOD_PANGOLIN_SITE_AD_CA_CERT_PATH=
|
||||
|
||||
# Metrics / observability
|
||||
MOD_PANGOLIN_SITE_METRICS_PROMETHEUS_ENABLED=
|
||||
MOD_PANGOLIN_SITE_METRICS_OTLP_ENABLED=
|
||||
MOD_PANGOLIN_SITE_ADMIN_ADDR=
|
||||
MOD_PANGOLIN_SITE_REGION=
|
||||
MOD_PANGOLIN_SITE_METRICS_ASYNC_BYTES=
|
||||
MOD_PANGOLIN_SITE_PPROF_ENABLED=
|
||||
|
||||
# mTLS
|
||||
MOD_PANGOLIN_SITE_TLS_CLIENT_CERT=
|
||||
MOD_PANGOLIN_SITE_TLS_CLIENT_KEY=
|
||||
MOD_PANGOLIN_SITE_TLS_CLIENT_CAS=
|
||||
MOD_PANGOLIN_SITE_TLS_CLIENT_CERT_PKCS12=
|
||||
4
packages/advantech/merge/etc/description
Normal file
4
packages/advantech/merge/etc/description
Normal file
@@ -0,0 +1,4 @@
|
||||
Pangolin Site (newt) is a WireGuard-based tunnel client that connects this router to a
|
||||
Pangolin server, enabling secure remote access to local resources without opening
|
||||
firewall ports. Configure the server endpoint, ID, and secret via the web
|
||||
interface to establish the tunnel automatically on startup.
|
||||
124
packages/advantech/merge/etc/init
Normal file
124
packages/advantech/merge/etc/init
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODNAME=newt
|
||||
PIDFILE=/tmp/newt.pid
|
||||
LOGFILE=/tmp/newt.log
|
||||
|
||||
set_setting_raw() {
|
||||
key="$1"
|
||||
value="$2"
|
||||
[ -f "/opt/$MODNAME/etc/settings" ] || cp "/opt/$MODNAME/etc/defaults" "/opt/$MODNAME/etc/settings" 2>/dev/null
|
||||
awk -v k="$key" -v v="$value" '
|
||||
BEGIN { done=0 }
|
||||
index($0, k "=") == 1 { print k "=" v; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print k "=" v }
|
||||
' "/opt/$MODNAME/etc/settings" > "/tmp/${MODNAME}.settings.tmp" && mv "/tmp/${MODNAME}.settings.tmp" "/opt/$MODNAME/etc/settings"
|
||||
}
|
||||
|
||||
/usr/bin/logger -t $MODNAME "DEBUG: $0 $@"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
. /opt/$MODNAME/etc/settings
|
||||
if [ "$MOD_PANGOLIN_SITE_ENABLED" != "1" ]; then
|
||||
echo "$MODNAME is disabled in settings, skipping start"
|
||||
exit 0
|
||||
fi
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
|
||||
echo "$MODNAME is already running (PID: $(cat "$PIDFILE"))"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Starting newt implies enable at boot.
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "1"
|
||||
|
||||
# Map module settings to Newt environment variables.
|
||||
[ -n "$MOD_PANGOLIN_SITE_ENDPOINT" ] && export PANGOLIN_ENDPOINT="$MOD_PANGOLIN_SITE_ENDPOINT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_ID" ] && export NEWT_ID="$MOD_PANGOLIN_SITE_ID"
|
||||
[ -n "$MOD_PANGOLIN_SITE_SECRET" ] && export NEWT_SECRET="$MOD_PANGOLIN_SITE_SECRET"
|
||||
[ -n "$MOD_PANGOLIN_SITE_MTU" ] && export MTU="$MOD_PANGOLIN_SITE_MTU"
|
||||
[ -n "$MOD_PANGOLIN_SITE_DNS" ] && export DNS="$MOD_PANGOLIN_SITE_DNS"
|
||||
[ -n "$MOD_PANGOLIN_SITE_LOG_LEVEL" ] && export LOG_LEVEL="$MOD_PANGOLIN_SITE_LOG_LEVEL"
|
||||
[ -n "$MOD_PANGOLIN_SITE_UPDOWN_SCRIPT" ] && export UPDOWN_SCRIPT="$MOD_PANGOLIN_SITE_UPDOWN_SCRIPT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_INTERFACE" ] && export INTERFACE="$MOD_PANGOLIN_SITE_INTERFACE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PORT" ] && export PORT="$MOD_PANGOLIN_SITE_PORT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_DISABLE_CLIENTS" ] && export DISABLE_CLIENTS="$MOD_PANGOLIN_SITE_DISABLE_CLIENTS"
|
||||
[ -n "$MOD_PANGOLIN_SITE_USE_NATIVE_INTERFACE" ] && export USE_NATIVE_INTERFACE="$MOD_PANGOLIN_SITE_USE_NATIVE_INTERFACE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_ENFORCE_HC_CERT" ] && export ENFORCE_HC_CERT="$MOD_PANGOLIN_SITE_ENFORCE_HC_CERT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_DOCKER_SOCKET" ] && export DOCKER_SOCKET="$MOD_PANGOLIN_SITE_DOCKER_SOCKET"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PING_INTERVAL" ] && export PING_INTERVAL="$MOD_PANGOLIN_SITE_PING_INTERVAL"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PING_TIMEOUT" ] && export PING_TIMEOUT="$MOD_PANGOLIN_SITE_PING_TIMEOUT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_UDP_PROXY_IDLE_TIMEOUT" ] && export NEWT_UDP_PROXY_IDLE_TIMEOUT="$MOD_PANGOLIN_SITE_UDP_PROXY_IDLE_TIMEOUT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_DOCKER_ENFORCE_NETWORK_VALIDATION" ] && export DOCKER_ENFORCE_NETWORK_VALIDATION="$MOD_PANGOLIN_SITE_DOCKER_ENFORCE_NETWORK_VALIDATION"
|
||||
[ -n "$MOD_PANGOLIN_SITE_HEALTH_FILE" ] && export HEALTH_FILE="$MOD_PANGOLIN_SITE_HEALTH_FILE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_AUTH_DAEMON_ENABLED" ] && export AUTH_DAEMON_ENABLED="$MOD_PANGOLIN_SITE_AUTH_DAEMON_ENABLED"
|
||||
[ -n "$MOD_PANGOLIN_SITE_AD_GENERATE_RANDOM_PASSWORD" ] && export AD_GENERATE_RANDOM_PASSWORD="$MOD_PANGOLIN_SITE_AD_GENERATE_RANDOM_PASSWORD"
|
||||
[ -n "$MOD_PANGOLIN_SITE_AD_KEY" ] && export AD_KEY="$MOD_PANGOLIN_SITE_AD_KEY"
|
||||
[ -n "$MOD_PANGOLIN_SITE_AD_PRINCIPALS_FILE" ] && export AD_PRINCIPALS_FILE="$MOD_PANGOLIN_SITE_AD_PRINCIPALS_FILE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_AD_CA_CERT_PATH" ] && export AD_CA_CERT_PATH="$MOD_PANGOLIN_SITE_AD_CA_CERT_PATH"
|
||||
[ -n "$MOD_PANGOLIN_SITE_METRICS_PROMETHEUS_ENABLED" ] && export NEWT_METRICS_PROMETHEUS_ENABLED="$MOD_PANGOLIN_SITE_METRICS_PROMETHEUS_ENABLED"
|
||||
[ -n "$MOD_PANGOLIN_SITE_METRICS_OTLP_ENABLED" ] && export NEWT_METRICS_OTLP_ENABLED="$MOD_PANGOLIN_SITE_METRICS_OTLP_ENABLED"
|
||||
[ -n "$MOD_PANGOLIN_SITE_ADMIN_ADDR" ] && export NEWT_ADMIN_ADDR="$MOD_PANGOLIN_SITE_ADMIN_ADDR"
|
||||
[ -n "$MOD_PANGOLIN_SITE_REGION" ] && export NEWT_REGION="$MOD_PANGOLIN_SITE_REGION"
|
||||
[ -n "$MOD_PANGOLIN_SITE_METRICS_ASYNC_BYTES" ] && export NEWT_METRICS_ASYNC_BYTES="$MOD_PANGOLIN_SITE_METRICS_ASYNC_BYTES"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PPROF_ENABLED" ] && export NEWT_PPROF_ENABLED="$MOD_PANGOLIN_SITE_PPROF_ENABLED"
|
||||
[ -n "$MOD_PANGOLIN_SITE_TLS_CLIENT_CERT" ] && export TLS_CLIENT_CERT="$MOD_PANGOLIN_SITE_TLS_CLIENT_CERT"
|
||||
[ -n "$MOD_PANGOLIN_SITE_TLS_CLIENT_KEY" ] && export TLS_CLIENT_KEY="$MOD_PANGOLIN_SITE_TLS_CLIENT_KEY"
|
||||
[ -n "$MOD_PANGOLIN_SITE_TLS_CLIENT_CAS" ] && export TLS_CLIENT_CAS="$MOD_PANGOLIN_SITE_TLS_CLIENT_CAS"
|
||||
[ -n "$MOD_PANGOLIN_SITE_TLS_CLIENT_CERT_PKCS12" ] && export TLS_CLIENT_CERT_PKCS12="$MOD_PANGOLIN_SITE_TLS_CLIENT_CERT_PKCS12"
|
||||
[ -n "$MOD_PANGOLIN_SITE_BLUEPRINT_FILE" ] && export BLUEPRINT_FILE="$MOD_PANGOLIN_SITE_BLUEPRINT_FILE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PROVISIONING_BLUEPRINT_FILE" ] && export PROVISIONING_BLUEPRINT_FILE="$MOD_PANGOLIN_SITE_PROVISIONING_BLUEPRINT_FILE"
|
||||
[ -n "$MOD_PANGOLIN_SITE_NO_CLOUD" ] && export NO_CLOUD="$MOD_PANGOLIN_SITE_NO_CLOUD"
|
||||
[ -n "$MOD_PANGOLIN_SITE_PROVISIONING_KEY" ] && export NEWT_PROVISIONING_KEY="$MOD_PANGOLIN_SITE_PROVISIONING_KEY"
|
||||
[ -n "$MOD_PANGOLIN_SITE_NAME" ] && export NEWT_NAME="$MOD_PANGOLIN_SITE_NAME"
|
||||
[ -n "$MOD_PANGOLIN_SITE_CONFIG_FILE" ] && export CONFIG_FILE="$MOD_PANGOLIN_SITE_CONFIG_FILE"
|
||||
|
||||
export NEWT_SYSTEM_SUBSTRATE="ADVANTECH_ROUTER_APP"
|
||||
export NEWT_PID_FILE="$PIDFILE"
|
||||
|
||||
echo "Starting $MODNAME..."
|
||||
if [ -n "$MOD_PANGOLIN_SITE_PREFER_ENDPOINT" ]; then
|
||||
/opt/$MODNAME/bin/newt --prefer-endpoint "$MOD_PANGOLIN_SITE_PREFER_ENDPOINT" >> "$LOGFILE" 2>&1 &
|
||||
else
|
||||
/opt/$MODNAME/bin/newt >> "$LOGFILE" 2>&1 &
|
||||
fi
|
||||
echo $! > "$PIDFILE"
|
||||
echo "Started $MODNAME (PID: $(cat "$PIDFILE"))"
|
||||
exit 0
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $MODNAME..."
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
kill "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null
|
||||
rm -f "$PIDFILE"
|
||||
fi
|
||||
killall newt 2>/dev/null
|
||||
# Stopping newt implies disable at boot.
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "0"
|
||||
echo "Stopped $MODNAME"
|
||||
exit 0
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
sleep 1
|
||||
# Restart should remain enabled after reboot.
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "1"
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
|
||||
echo "$MODNAME is running (PID: $(cat "$PIDFILE"))"
|
||||
exit 0
|
||||
else
|
||||
echo "$MODNAME is not running"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
defaults)
|
||||
cp /opt/$MODNAME/etc/defaults /opt/$MODNAME/etc/settings 2>/dev/null
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status|defaults}"
|
||||
exit 1
|
||||
esac
|
||||
15
packages/advantech/merge/etc/install
Normal file
15
packages/advantech/merge/etc/install
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODNAME=newt
|
||||
|
||||
# Ensure scripts are executable
|
||||
chmod +x /opt/$MODNAME/etc/init
|
||||
chmod +x /opt/$MODNAME/etc/install
|
||||
chmod +x /opt/$MODNAME/etc/uninstall
|
||||
chmod +x /opt/$MODNAME/bin/newt
|
||||
chmod +x /opt/$MODNAME/www/index.cgi
|
||||
|
||||
# Secure the web interface with router authentication
|
||||
ln -sf /etc/htpasswd /opt/$MODNAME/www/.htpasswd
|
||||
|
||||
exit 0
|
||||
1
packages/advantech/merge/etc/name
Normal file
1
packages/advantech/merge/etc/name
Normal file
@@ -0,0 +1 @@
|
||||
Pangolin Site
|
||||
1
packages/advantech/merge/etc/summary
Normal file
1
packages/advantech/merge/etc/summary
Normal file
@@ -0,0 +1 @@
|
||||
Tunnel client for Pangolin secure remote access.
|
||||
9
packages/advantech/merge/etc/uninstall
Normal file
9
packages/advantech/merge/etc/uninstall
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODNAME=newt
|
||||
|
||||
rm -f /opt/$MODNAME/www/.htpasswd
|
||||
rm -f /tmp/newt.pid
|
||||
rm -f /tmp/newt.log
|
||||
|
||||
exit 0
|
||||
1
packages/advantech/merge/etc/version
Normal file
1
packages/advantech/merge/etc/version
Normal file
@@ -0,0 +1 @@
|
||||
1.12.5
|
||||
282
packages/advantech/merge/www/index.cgi
Normal file
282
packages/advantech/merge/www/index.cgi
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/bin/sh
|
||||
|
||||
MODNAME=newt
|
||||
SETTINGS=/opt/$MODNAME/etc/settings
|
||||
LOGFILE=/tmp/newt.log
|
||||
PIDFILE=/tmp/newt.pid
|
||||
|
||||
# Escape special HTML characters
|
||||
htmlesc() {
|
||||
printf '%s' "$1" | sed \
|
||||
-e 's/&/\&/g' \
|
||||
-e 's/</\</g' \
|
||||
-e 's/>/\>/g' \
|
||||
-e 's/"/\"/g'
|
||||
}
|
||||
|
||||
# Decode URL-encoded form values (handles common chars in endpoints/ids/secrets)
|
||||
urldecode() {
|
||||
printf '%s' "$1" | sed \
|
||||
-e 's/+/ /g' \
|
||||
-e 's/%3[Aa]/:/g' -e 's/%3[aa]/:/g' \
|
||||
-e 's/%2[Ff]/\//g' -e 's/%2[ff]/\//g' \
|
||||
-e 's/%40/@/g' \
|
||||
-e 's/%2[Ee]/./g' \
|
||||
-e 's/%2[Dd]/-/g' \
|
||||
-e 's/%5[Ff]/_/g' \
|
||||
-e 's/%3[Dd]/=/g' \
|
||||
-e 's/%3[Ff]/?/g' \
|
||||
-e 's/%23/#/g' \
|
||||
-e 's/%25/%/g'
|
||||
}
|
||||
|
||||
# Extract a named field from URL-encoded POST data (awk splits on & without tr)
|
||||
get_field() {
|
||||
printf '%s' "$2" | awk -v f="${1}=" 'BEGIN{RS="&"} index($0,f)==1 {print substr($0,length(f)+1); exit}'
|
||||
}
|
||||
|
||||
# Check whether newt process is alive
|
||||
is_running() {
|
||||
[ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null
|
||||
}
|
||||
|
||||
ensure_settings_file() {
|
||||
[ -f "$SETTINGS" ] && return
|
||||
if [ -f "/opt/$MODNAME/etc/defaults" ]; then
|
||||
cp "/opt/$MODNAME/etc/defaults" "$SETTINGS" 2>/dev/null
|
||||
else
|
||||
printf 'MOD_PANGOLIN_SITE_ENABLED=0\n' > "$SETTINGS"
|
||||
fi
|
||||
}
|
||||
|
||||
set_setting_raw() {
|
||||
key="$1"
|
||||
value="$2"
|
||||
ensure_settings_file
|
||||
awk -v k="$key" -v v="$value" '
|
||||
BEGIN { done=0 }
|
||||
index($0, k "=") == 1 { print k "=" v; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print k "=" v }
|
||||
' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
||||
}
|
||||
|
||||
# ── Read POST body ──────────────────────────────────────────────────────────
|
||||
POST_DATA=""
|
||||
if [ "$REQUEST_METHOD" = "POST" ] && [ -n "$CONTENT_LENGTH" ] && [ "$CONTENT_LENGTH" -gt 0 ] 2>/dev/null; then
|
||||
POST_DATA=$(dd bs="$CONTENT_LENGTH" count=1 2>/dev/null)
|
||||
fi
|
||||
|
||||
# ── Load current settings ───────────────────────────────────────────────────
|
||||
MOD_PANGOLIN_SITE_ENABLED=0
|
||||
MOD_PANGOLIN_SITE_ENDPOINT=""
|
||||
MOD_PANGOLIN_SITE_ID=""
|
||||
MOD_PANGOLIN_SITE_SECRET=""
|
||||
[ -f "$SETTINGS" ] && . "$SETTINGS"
|
||||
|
||||
# ── Handle actions ──────────────────────────────────────────────────────────
|
||||
if [ -n "$POST_DATA" ]; then
|
||||
ACTION=$(get_field "action" "$POST_DATA")
|
||||
case "$ACTION" in
|
||||
save)
|
||||
EP=$(urldecode "$(get_field "endpoint" "$POST_DATA")")
|
||||
ID=$(urldecode "$(get_field "id" "$POST_DATA")")
|
||||
SEC=$(urldecode "$(get_field "secret" "$POST_DATA")")
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENDPOINT" "\"$(quote_sh_value "$EP")\""
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ID" "\"$(quote_sh_value "$ID")\""
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_SECRET" "\"$(quote_sh_value "$SEC")\""
|
||||
;;
|
||||
start)
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "1"
|
||||
/opt/$MODNAME/etc/init start >/dev/null 2>&1
|
||||
;;
|
||||
stop)
|
||||
/opt/$MODNAME/etc/init stop >/dev/null 2>&1
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "0"
|
||||
;;
|
||||
restart)
|
||||
set_setting_raw "MOD_PANGOLIN_SITE_ENABLED" "1"
|
||||
/opt/$MODNAME/etc/init restart >/dev/null 2>&1
|
||||
;;
|
||||
clearlog)
|
||||
printf '' > "$LOGFILE"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Reload settings after actions.
|
||||
MOD_PANGOLIN_SITE_ENABLED=0
|
||||
MOD_PANGOLIN_SITE_ENDPOINT=""
|
||||
MOD_PANGOLIN_SITE_ID=""
|
||||
MOD_PANGOLIN_SITE_SECRET=""
|
||||
[ -f "$SETTINGS" ] && . "$SETTINGS"
|
||||
|
||||
# ── Status ──────────────────────────────────────────────────────────────────
|
||||
if is_running; then
|
||||
STATUS_TEXT="Running"
|
||||
STATUS_CLASS="running"
|
||||
DISABLED="disabled"
|
||||
SETTINGS_HINT='<div class="notice">Stop Newt first to edit settings.</div>'
|
||||
else
|
||||
STATUS_TEXT="Stopped"
|
||||
STATUS_CLASS="stopped"
|
||||
DISABLED=""
|
||||
SETTINGS_HINT=""
|
||||
fi
|
||||
|
||||
EP_ESC=$(htmlesc "$MOD_PANGOLIN_SITE_ENDPOINT")
|
||||
ID_ESC=$(htmlesc "$MOD_PANGOLIN_SITE_ID")
|
||||
SEC_ESC=$(htmlesc "$MOD_PANGOLIN_SITE_SECRET")
|
||||
EP_INPUT_ESC="$EP_ESC"
|
||||
[ -z "$MOD_PANGOLIN_SITE_ENDPOINT" ] && EP_INPUT_ESC="https://app.pangolin.net"
|
||||
|
||||
# ── Log (escape HTML and dollar signs to prevent shell expansion in heredoc) ─
|
||||
LOG_HTML=""
|
||||
if [ -f "$LOGFILE" ]; then
|
||||
LOG_HTML=$(tail -100 "$LOGFILE" 2>/dev/null | sed \
|
||||
-e 's/&/\&/g' \
|
||||
-e 's/</\</g' \
|
||||
-e 's/>/\>/g' \
|
||||
-e 's/\$/\$/g')
|
||||
fi
|
||||
|
||||
# ── Output ──────────────────────────────────────────────────────────────────
|
||||
printf 'Content-type: text/html\r\n\r\n'
|
||||
|
||||
# Static head — split around the conditional refresh meta tag
|
||||
cat << 'HEAD_START'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
HEAD_START
|
||||
|
||||
# Only auto-refresh while newt is running (avoids wiping settings form mid-edit)
|
||||
[ "$STATUS_CLASS" = "running" ] && printf '<meta http-equiv="refresh" content="10">\n'
|
||||
|
||||
cat << 'STATIC_HEAD'
|
||||
<title>Pangolin Site</title>
|
||||
<style>
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;padding:8px;border-top:3px solid #79BD28;background:#fff;color:#000}
|
||||
body,table,tr,td,a,input,select,textarea,button{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:12px}
|
||||
a{color:#004280;text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.topline{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-bottom:6px}
|
||||
.app-title{background:#004280;color:#fff;font-weight:bold;padding:6px 8px;border:1px solid #00315f;border-bottom:none}
|
||||
.window{background:#f4f4f4;border:2px solid #004280;padding:0}
|
||||
.section{border-top:1px solid #9fb7d5}
|
||||
.section:first-child{border-top:none}
|
||||
.section-head{background:#c0e0ff;color:#000;font-weight:bold;padding:5px 8px;border-bottom:1px solid #9fb7d5;display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}
|
||||
.section-body{padding:10px 8px}
|
||||
.badge{display:inline-block;padding:2px 10px;border-radius:2px;color:#fff;font-weight:bold;line-height:1.4}
|
||||
.running{background:#008000}
|
||||
.stopped{background:#800000}
|
||||
.row{display:flex;align-items:center;gap:8px;margin:6px 0}
|
||||
.row label{width:90px;font-weight:bold}
|
||||
.row input{width:360px;max-width:100%;padding:2px 4px;border:1px solid #7f9db9;background:#fff;height:23px}
|
||||
.btn{height:24px;padding:0 10px;border:1px solid #7f9db9;background:#efefef;color:#000;cursor:pointer}
|
||||
.btn:disabled{color:#777;background:#e5e5e5}
|
||||
.btn + .btn{margin-left:6px}
|
||||
.btn-start{border-color:#2d7a2d;background:#dff0df}
|
||||
.btn-stop{border-color:#8a2c2c;background:#f7dddd}
|
||||
.btn-restart{border-color:#9a6a1f;background:#f7ecd8}
|
||||
.btn-save{border-color:#2e5f92;background:#dce9f8}
|
||||
.hint{color:#808080;font-weight:normal}
|
||||
.notice{margin:0 0 10px 0;color:#ff0000;font-style:italic}
|
||||
.log{background:#fff;border:1px solid #7f9db9;color:#000;font-family:monospace;font-size:12px;line-height:1.35;padding:6px;height:300px;overflow-y:auto;white-space:pre-wrap;word-break:break-word}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topline">
|
||||
<a href="/">« Back to Router</a>
|
||||
</div>
|
||||
<div class="app-title">Router Apps - Pangolin Site</div>
|
||||
<div class="window">
|
||||
STATIC_HEAD
|
||||
|
||||
# Status card (double-quoted heredoc — variables expand)
|
||||
cat << STATUS_CARD
|
||||
<div class="section">
|
||||
<div class="section-head">Status</div>
|
||||
<div class="section-body">
|
||||
<div><span class="badge ${STATUS_CLASS}">${STATUS_TEXT}</span></div>
|
||||
<div style="margin-top:8px">
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="action" value="start">
|
||||
<button class="btn btn-start" type="submit">Start</button>
|
||||
</form>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="action" value="stop">
|
||||
<button class="btn btn-stop" type="submit">Stop</button>
|
||||
</form>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="action" value="restart">
|
||||
<button class="btn btn-restart" type="submit">Restart</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
STATUS_CARD
|
||||
|
||||
# Settings card
|
||||
cat << SETTINGS_CARD
|
||||
<div class="section">
|
||||
<div class="section-head">Settings</div>
|
||||
<div class="section-body">
|
||||
${SETTINGS_HINT}<form method="post">
|
||||
<input type="hidden" name="action" value="save">
|
||||
<div class="row">
|
||||
<label>Endpoint</label>
|
||||
<input type="text" name="endpoint" value="${EP_INPUT_ESC}" ${DISABLED}>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>ID</label>
|
||||
<input type="text" name="id" value="${ID_ESC}" ${DISABLED}>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Secret</label>
|
||||
<input type="password" name="secret" value="${SEC_ESC}" ${DISABLED}>
|
||||
</div>
|
||||
<div style="margin-top:12px">
|
||||
<button class="btn btn-save" type="submit" ${DISABLED}>Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
SETTINGS_CARD
|
||||
|
||||
# Log card header
|
||||
cat << 'LOG_HEADER'
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<span>Log <span class="hint">(last 100 lines, auto-refreshes every 10s while running)</span></span>
|
||||
<span style="display:inline-flex;gap:6px">
|
||||
<form method="post" style="margin:0">
|
||||
<input type="hidden" name="action" value="clearlog">
|
||||
<button class="btn" type="submit">Clear Log</button>
|
||||
</form>
|
||||
<form method="get" style="margin:0">
|
||||
<button class="btn" type="submit">Refresh</button>
|
||||
</form>
|
||||
</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="log">
|
||||
LOG_HEADER
|
||||
|
||||
# Log content — printed with printf to prevent any shell expansion
|
||||
printf '%s' "$LOG_HTML"
|
||||
|
||||
# Close tags (static)
|
||||
cat << 'STATIC_FOOTER'
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
STATIC_FOOTER
|
||||
0
packages/openwrt/.gitkeep
Normal file
0
packages/openwrt/.gitkeep
Normal file
0
packages/teltonika/.gitkeep
Normal file
0
packages/teltonika/.gitkeep
Normal file
Reference in New Issue
Block a user