diff --git a/packages/advantech/.gitignore b/packages/advantech/.gitignore new file mode 100644 index 0000000..0377a32 --- /dev/null +++ b/packages/advantech/.gitignore @@ -0,0 +1,3 @@ +.build +*.tgz +bin \ No newline at end of file diff --git a/packages/advantech/Makefile b/packages/advantech/Makefile new file mode 100644 index 0000000..2250025 --- /dev/null +++ b/packages/advantech/Makefile @@ -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." diff --git a/packages/advantech/merge/etc/defaults b/packages/advantech/merge/etc/defaults new file mode 100644 index 0000000..e566be4 --- /dev/null +++ b/packages/advantech/merge/etc/defaults @@ -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= diff --git a/packages/advantech/merge/etc/description b/packages/advantech/merge/etc/description new file mode 100644 index 0000000..04f09a4 --- /dev/null +++ b/packages/advantech/merge/etc/description @@ -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. diff --git a/packages/advantech/merge/etc/init b/packages/advantech/merge/etc/init new file mode 100644 index 0000000..cf90ae0 --- /dev/null +++ b/packages/advantech/merge/etc/init @@ -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 diff --git a/packages/advantech/merge/etc/install b/packages/advantech/merge/etc/install new file mode 100644 index 0000000..f83fb05 --- /dev/null +++ b/packages/advantech/merge/etc/install @@ -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 diff --git a/packages/advantech/merge/etc/name b/packages/advantech/merge/etc/name new file mode 100644 index 0000000..0656222 --- /dev/null +++ b/packages/advantech/merge/etc/name @@ -0,0 +1 @@ +Pangolin Site diff --git a/packages/advantech/merge/etc/summary b/packages/advantech/merge/etc/summary new file mode 100644 index 0000000..64f982c --- /dev/null +++ b/packages/advantech/merge/etc/summary @@ -0,0 +1 @@ +Tunnel client for Pangolin secure remote access. diff --git a/packages/advantech/merge/etc/uninstall b/packages/advantech/merge/etc/uninstall new file mode 100644 index 0000000..70a9e38 --- /dev/null +++ b/packages/advantech/merge/etc/uninstall @@ -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 diff --git a/packages/advantech/merge/etc/version b/packages/advantech/merge/etc/version new file mode 100644 index 0000000..e0a6b34 --- /dev/null +++ b/packages/advantech/merge/etc/version @@ -0,0 +1 @@ +1.12.5 diff --git a/packages/advantech/merge/www/index.cgi b/packages/advantech/merge/www/index.cgi new file mode 100644 index 0000000..c21233e --- /dev/null +++ b/packages/advantech/merge/www/index.cgi @@ -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' +} + +# 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='
Stop Newt first to edit settings.
' +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') +fi + +# ── Output ────────────────────────────────────────────────────────────────── +printf 'Content-type: text/html\r\n\r\n' + +# Static head — split around the conditional refresh meta tag +cat << 'HEAD_START' + + + + +HEAD_START + +# Only auto-refresh while newt is running (avoids wiping settings form mid-edit) +[ "$STATUS_CLASS" = "running" ] && printf '\n' + +cat << 'STATIC_HEAD' +Pangolin Site + + + +
+« Back to Router +
+
Router Apps - Pangolin Site
+
+STATIC_HEAD + +# Status card (double-quoted heredoc — variables expand) +cat << STATUS_CARD +
+
Status
+
+
${STATUS_TEXT}
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+STATUS_CARD + +# Settings card +cat << SETTINGS_CARD +
+
Settings
+
+${SETTINGS_HINT}
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+SETTINGS_CARD + +# Log card header +cat << 'LOG_HEADER' +
+
+Log (last 100 lines, auto-refreshes every 10s while running) + +
+ + +
+
+ +
+
+
+
+
+LOG_HEADER + +# Log content — printed with printf to prevent any shell expansion +printf '%s' "$LOG_HTML" + +# Close tags (static) +cat << 'STATIC_FOOTER' +
+
+
+ + +STATIC_FOOTER diff --git a/packages/openwrt/.gitkeep b/packages/openwrt/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/teltonika/.gitkeep b/packages/teltonika/.gitkeep new file mode 100644 index 0000000..e69de29