mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
Effective config refactor, remove dead parser path, add source line numbers
Effective config: - Add EffectiveConfigFieldDescriptor (metadata + value/source/items delegates) - Add EffectiveConfigFieldBuilder with Config/Sources/Items helpers; build all fields when EffectiveConfig present (empty = not set); empty list when null - Add EffectiveConfigSectionPanel + EffectiveConfigFieldDisplay; section CSS uses ::deep for child markup, open-state styling for headers - Remove Kind from descriptor (use DnsmasqConfOptionRegistry.GetKind(OptionName) when needed) Dead code removal: - Remove DnsmasqOptionModels, ParsedDirective (DnsmasqConfDirective), DnsmasqOptionKind, DnsmasqConfOptionRegistry - Remove ParseLine from DnsmasqConfDirectiveParser; keep StripComment and TryParseKeyValue (used by DnsmasqConfIncludeParser) - Simplify DnsmasqConfDirectiveParserTests; remove DnsmasqConfOptionRegistryTests ConfigValueSource: - Add LineNumber (int?); IncludeParser passes 1-based line to MakeSource - GetReadOnlyTooltip includes 'line N' when LineNumber set - Add ConfigValueSourceTests; assert LineNumber in IncludeParser WithSource tests Also: .github/workflows/release.yml, scripts/install.sh, script tweaks
This commit is contained in:
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env sh
|
||||
# Download and install dnsmasq-webui from a GitHub release. Picks the binary
|
||||
# matching the current OS/arch (RID). Run from a clone or via:
|
||||
# curl -sSL https://raw.githubusercontent.com/OWNER/REPO/main/scripts/install.sh | sh -s -- [OPTIONS]
|
||||
# When run from a clone, repo is detected from git remote origin. Otherwise set
|
||||
# GITHUB_REPO=owner/repo or pass --repo owner/repo.
|
||||
set -e
|
||||
|
||||
GITHUB_REPO="${GITHUB_REPO:-}"
|
||||
VERSION=""
|
||||
LIST=false
|
||||
INSTALL_DIR=""
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Download and install dnsmasq-webui from a GitHub release for this machine's OS/arch."
|
||||
echo ""
|
||||
echo "Repo (when not in a clone):"
|
||||
echo " --repo OWNER/REPO GitHub owner/repo (e.g. myuser/dnsmasq-webui)."
|
||||
echo " Or set GITHUB_REPO=owner/repo."
|
||||
echo " When run inside a git clone with origin, repo is detected automatically."
|
||||
echo ""
|
||||
echo "Release:"
|
||||
echo " --list List available releases (tag, name, published_at) and exit."
|
||||
echo " --version TAG Install from release with tag TAG (e.g. v1.0.0). Default: latest."
|
||||
echo " -h, -?, --help Show this help."
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo " --dir DIR Install into DIR (default: ./dnsmasq-webui in current directory)."
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # In clone: install latest into ./dnsmasq-webui"
|
||||
echo " $0 --repo owner/dnsmasq-webui # Install latest from owner/dnsmasq-webui"
|
||||
echo " $0 --repo owner/dnsmasq-webui --version v1.0.0"
|
||||
echo " $0 --repo owner/dnsmasq-webui --list # List releases"
|
||||
echo " GITHUB_REPO=owner/dnsmasq-webui $0 --dir /opt/dnsmasq-webui"
|
||||
echo ""
|
||||
echo "After install, configure via appsettings.json or Dnsmasq__* environment variables, then run the binary."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Detect owner/repo from git remote if we're in a clone.
|
||||
detect_repo() {
|
||||
if [ -n "$GITHUB_REPO" ]; then
|
||||
return
|
||||
fi
|
||||
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
origin="$(git remote get-url origin 2>/dev/null)" || true
|
||||
if [ -n "$origin" ]; then
|
||||
# https://github.com/owner/repo or git@github.com:owner/repo.git
|
||||
GITHUB_REPO="$(echo "$origin" | sed -E 's|^https://github\.com/||; s|^git@github\.com:||; s|\.git$||; s|/$||')"
|
||||
if [ -n "$GITHUB_REPO" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "Error: GitHub repo not set. Use --repo owner/repo or set GITHUB_REPO=owner/repo (or run from a clone)." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Require jq for parsing GitHub API JSON.
|
||||
check_jq() {
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
echo "Error: jq is required to parse GitHub API responses. Install jq (e.g. apt install jq)." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect RID (same logic as publish-self-contained.sh).
|
||||
detect_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) echo "x64" ;;
|
||||
aarch64|arm64) echo "arm64" ;;
|
||||
armv7l|armhf) echo "arm" ;;
|
||||
*) echo "x64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_rid() {
|
||||
local arch
|
||||
arch="$(detect_arch)"
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
case "${ID:-}" in
|
||||
ubuntu)
|
||||
case "${VERSION_ID:-}" in
|
||||
24.04) echo "ubuntu.24.04-$arch"; return ;;
|
||||
22.04) echo "ubuntu.22.04-$arch"; return ;;
|
||||
esac ;;
|
||||
alpine)
|
||||
case "$arch" in
|
||||
x64) echo "linux-musl-x64"; return ;;
|
||||
arm64) echo "linux-musl-arm64"; return ;;
|
||||
*) echo "linux-musl-x64"; return ;;
|
||||
esac ;;
|
||||
esac
|
||||
fi
|
||||
case "$arch" in
|
||||
arm64) echo "linux-arm64" ;;
|
||||
arm) echo "linux-arm" ;;
|
||||
*) echo "linux-x64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# GET release (latest or by tag). Output raw JSON to stdout.
|
||||
fetch_release() {
|
||||
local api_url
|
||||
if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then
|
||||
api_url="https://api.github.com/repos/$GITHUB_REPO/releases/latest"
|
||||
else
|
||||
api_url="https://api.github.com/repos/$GITHUB_REPO/releases/tags/$VERSION"
|
||||
fi
|
||||
resp="$(curl -sSL -A "dnsmasq-webui-install/1.0" -w "\n%{http_code}" "$api_url")"
|
||||
code="$(echo "$resp" | tail -n1)"
|
||||
body="$(echo "$resp" | sed '$d')"
|
||||
if [ "$code" != "200" ]; then
|
||||
echo "Error: GitHub API returned $code for $api_url" >&2
|
||||
echo "$body" | jq -r '.message // .' 2>/dev/null || echo "$body" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$body"
|
||||
}
|
||||
|
||||
# List releases and exit.
|
||||
list_releases() {
|
||||
check_jq
|
||||
detect_repo
|
||||
api_url="https://api.github.com/repos/$GITHUB_REPO/releases?per_page=20"
|
||||
resp="$(curl -sSL -A "dnsmasq-webui-install/1.0" -w "\n%{http_code}" "$api_url")"
|
||||
code="$(echo "$resp" | tail -n1)"
|
||||
body="$(echo "$resp" | sed '$d')"
|
||||
if [ "$code" != "200" ]; then
|
||||
echo "Error: GitHub API returned $code" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$body" | jq -r '.[] | "\(.tag_name) \(.name) \(.published_at // .created_at)"'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Find asset download URL whose name contains the given RID.
|
||||
find_asset_url() {
|
||||
local release_json rid
|
||||
release_json="$1"
|
||||
rid="$2"
|
||||
echo "$release_json" | jq -r --arg rid "$rid" '
|
||||
.assets[] | select(.name | test($rid)) | .browser_download_url
|
||||
' | head -n1
|
||||
}
|
||||
|
||||
# Main install: fetch release, find asset for RID, download, extract.
|
||||
do_install() {
|
||||
check_jq
|
||||
detect_repo
|
||||
rid="$(detect_rid)"
|
||||
echo "Detected RID: $rid"
|
||||
echo "Fetching release..."
|
||||
release_json="$(fetch_release)"
|
||||
tag="$(echo "$release_json" | jq -r '.tag_name')"
|
||||
echo "Release: $tag"
|
||||
url="$(find_asset_url "$release_json" "$rid")"
|
||||
if [ -z "$url" ] || [ "$url" = "null" ]; then
|
||||
echo "Error: No asset found for RID $rid in release $tag." >&2
|
||||
echo "Available assets:" >&2
|
||||
echo "$release_json" | jq -r '.assets[].name' | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$INSTALL_DIR" ]; then
|
||||
INSTALL_DIR="./dnsmasq-webui"
|
||||
fi
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
tmpzip="${TMPDIR:-/tmp}/dnsmasq-webui-$rid.zip"
|
||||
echo "Downloading $url ..."
|
||||
curl -sSL -A "dnsmasq-webui-install/1.0" -o "$tmpzip" "$url"
|
||||
echo "Extracting to $INSTALL_DIR ..."
|
||||
unzip -o -q "$tmpzip" -d "$INSTALL_DIR"
|
||||
rm -f "$tmpzip"
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR"
|
||||
echo "Run: $INSTALL_DIR/DnsmasqWebUI"
|
||||
echo "Configure via appsettings.json in that directory or Dnsmasq__* environment variables (e.g. Dnsmasq__MainConfigPath=/etc/dnsmasq.conf)."
|
||||
}
|
||||
|
||||
# Parse args
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|-?|--help)
|
||||
usage
|
||||
;;
|
||||
--repo)
|
||||
shift
|
||||
[ $# -gt 0 ] || { echo "Error: --repo requires OWNER/REPO" >&2; exit 1; }
|
||||
GITHUB_REPO="$1"
|
||||
shift
|
||||
;;
|
||||
--list)
|
||||
LIST=true
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
shift
|
||||
[ $# -gt 0 ] || { echo "Error: --version requires TAG" >&2; exit 1; }
|
||||
VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
--dir)
|
||||
shift
|
||||
[ $# -gt 0 ] || { echo "Error: --dir requires DIR" >&2; exit 1; }
|
||||
INSTALL_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: unknown option $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Error: unexpected argument $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$LIST" = true ]; then
|
||||
list_releases
|
||||
fi
|
||||
|
||||
do_install
|
||||
@@ -3,7 +3,7 @@
|
||||
# (app + dnsmasq + DHCP client). See testdata/README.md and docker-compose.test.yml.
|
||||
#
|
||||
# What this script does:
|
||||
# Start (default): clear mount (unless --no-clear), sync source -> mount, remove *dnsmasq-webui*.conf,
|
||||
# Start (default): clear mount (unless --no-clear), sync source -> mount, clean up previous test data,
|
||||
# then docker compose up -d [--build] [--force-recreate].
|
||||
# --stop: docker compose down (stop and remove containers/networks).
|
||||
# --tidy: docker compose down, then clear the mount directory for a clean next run.
|
||||
@@ -16,7 +16,7 @@ COMPOSE_FILE="docker-compose.test.yml"
|
||||
SOURCE_DIR=""
|
||||
MOUNT_DIR=""
|
||||
PREPARE_ONLY=false
|
||||
NO_BUILD=false
|
||||
BUILD=false
|
||||
RECREATE=false
|
||||
NO_CLEAR=false
|
||||
STOP=false
|
||||
@@ -25,13 +25,12 @@ TIDY=false
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS] [--]"
|
||||
echo ""
|
||||
echo "Prepare the testdata mount directory and optionally start the Docker test harness"
|
||||
echo "(app with dnsmasq in one container, plus a DHCP client). The mount is synced from"
|
||||
echo "testdata/ by default (includes sample leases file so the harness shows leases)."
|
||||
echo "Prepare the testdata mount directory and optionally start or stop the Docker test harness"
|
||||
echo "(app + dnsmasq + DHCP client). By default: clear mount, sync testdata -> mount, then up -d (no rebuild)."
|
||||
echo ""
|
||||
echo "Steps:"
|
||||
echo "Steps (when starting):"
|
||||
echo " 1. Clear mount dir (unless --no-clear), then sync source -> mount."
|
||||
echo " 2. Remove any leftover *dnsmasq-webui*.conf so dnsmasq starts clean."
|
||||
echo " 2. Clean up previous test data (e.g. managed config) so the harness starts clean."
|
||||
echo " 3. If not --prepare-only: docker compose -f $COMPOSE_FILE up -d [options]."
|
||||
echo ""
|
||||
echo "Path options:"
|
||||
@@ -47,31 +46,32 @@ usage() {
|
||||
echo "Compose behaviour:"
|
||||
echo " --prepare-only Only prepare the mount; do not run docker compose."
|
||||
echo " Use to inspect or edit the mount before starting containers."
|
||||
echo " --no-build Do not pass --build to docker compose (use existing images)."
|
||||
echo " Use for a quick restart when only the mount changed."
|
||||
echo " --recreate Pass --force-recreate to docker compose (recreate containers)."
|
||||
echo " --build Pass --build to docker compose (rebuild images before starting)."
|
||||
echo " Use after changing the app or Dockerfile. Default: use existing images."
|
||||
echo " --no-build Do not rebuild (default). Use existing images for a quick restart."
|
||||
echo " --recreate Pass --force-recreate to docker compose (recreate containers)."
|
||||
echo " Use to ensure fresh container state and mounts."
|
||||
echo ""
|
||||
echo "Stop / tidy:"
|
||||
echo "Stop / tidy (take precedence: other options are ignored when used):"
|
||||
echo " --stop Stop the test harness: docker compose down (no prepare, no start)."
|
||||
echo " --tidy Stop the harness and clear the mount directory for a clean next run."
|
||||
echo " Uses default mount dir unless --mount DIR is given."
|
||||
echo ""
|
||||
echo "Other:"
|
||||
echo " -h, --help Show this help and exit."
|
||||
echo " -h, -?, --help Show this help and exit."
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0"
|
||||
echo " Full run: clear mount, sync testdata, build and start containers."
|
||||
echo " Full run: clear mount, sync testdata, start containers (no rebuild)."
|
||||
echo ""
|
||||
echo " $0 --no-build"
|
||||
echo " Clear and sync, then start containers without rebuilding images."
|
||||
echo " $0 --build"
|
||||
echo " Clear and sync, then rebuild images and start containers."
|
||||
echo ""
|
||||
echo " $0 --recreate"
|
||||
echo " Clear and sync, then up --build --force-recreate (clean containers)."
|
||||
echo " Clear and sync, then up -d --force-recreate (fresh containers)."
|
||||
echo ""
|
||||
echo " $0 --no-clear --no-build"
|
||||
echo " Preserve mount contents, sync over it, start without rebuild."
|
||||
echo " $0 --no-clear"
|
||||
echo " Preserve mount contents, sync over it, start (no rebuild)."
|
||||
echo ""
|
||||
echo " $0 --prepare-only"
|
||||
echo " Only clear and sync testdata -> testdata-mount; no containers."
|
||||
@@ -90,7 +90,7 @@ usage() {
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
-h|-?|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
@@ -114,8 +114,12 @@ while [ $# -gt 0 ]; do
|
||||
PREPARE_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--build)
|
||||
BUILD=true
|
||||
shift
|
||||
;;
|
||||
--no-build)
|
||||
NO_BUILD=true
|
||||
BUILD=false
|
||||
shift
|
||||
;;
|
||||
--recreate)
|
||||
@@ -134,14 +138,25 @@ while [ $# -gt 0 ]; do
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
-?*)
|
||||
echo "Error: unknown option $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Error: unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
echo "Error: unexpected argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
: "${SOURCE_DIR:=testdata}"
|
||||
@@ -186,23 +201,28 @@ else
|
||||
rm -f "$MOUNT_DIR/leases"
|
||||
fi
|
||||
|
||||
# Remove any leftover managed config from previous runs so dnsmasq starts clean (app will create zz-dnsmasq-webui.conf on startup).
|
||||
# Clean up previous test data (e.g. managed config) so the harness starts clean (app will create zz-dnsmasq-webui.conf on startup).
|
||||
find "$MOUNT_DIR" -name '*dnsmasq-webui*.conf' -type f -delete 2>/dev/null || true
|
||||
|
||||
echo "Mount directory ready: $MOUNT_DIR (source: $SOURCE_DIR)."
|
||||
|
||||
if [ "$PREPARE_ONLY" = true ]; then
|
||||
echo "To start the harness: TESTDATA_MOUNT=./$MOUNT_DIR docker compose -f $COMPOSE_FILE up -d --build"
|
||||
case "$MOUNT_DIR" in
|
||||
/*) MOUNT_EXPORT="$MOUNT_DIR" ;;
|
||||
*) MOUNT_EXPORT="./$MOUNT_DIR" ;;
|
||||
esac
|
||||
echo "To start the harness: TESTDATA_MOUNT=$MOUNT_EXPORT docker compose -f $COMPOSE_FILE up -d [--build]"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compose uses TESTDATA_MOUNT for the data volume (default: ./testdata-mount)
|
||||
export TESTDATA_MOUNT="./$MOUNT_DIR"
|
||||
case "$MOUNT_DIR" in
|
||||
/*) export TESTDATA_MOUNT="$MOUNT_DIR" ;;
|
||||
*) export TESTDATA_MOUNT="./$MOUNT_DIR" ;;
|
||||
esac
|
||||
|
||||
COMPOSE_CMD="docker compose -f $COMPOSE_FILE up -d"
|
||||
if [ "$NO_BUILD" = true ]; then
|
||||
COMPOSE_CMD="$COMPOSE_CMD"
|
||||
else
|
||||
if [ "$BUILD" = true ]; then
|
||||
COMPOSE_CMD="$COMPOSE_CMD --build"
|
||||
fi
|
||||
if [ "$RECREATE" = true ]; then
|
||||
|
||||
@@ -59,16 +59,17 @@ default_rid() {
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
-h|-?|--help)
|
||||
echo "Usage: $0 [OPTIONS] [RID]"
|
||||
echo ""
|
||||
echo "Build a self-contained folder publish for Linux. If RID is omitted, the script"
|
||||
echo "auto-detects from the current OS and architecture (recommended on Ubuntu and Alpine)."
|
||||
echo "Pass options first, then RID if desired (e.g. $0 --trim ubuntu.24.04-x64)."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --trim Enable trimming (smaller output; can cause 404/routing issues with Blazor)"
|
||||
echo " --no-clean Skip clean before publish (faster; use only if same RID and options as last run)"
|
||||
echo " -h, --help Show this help"
|
||||
echo " -h, -?, --help Show this help"
|
||||
echo ""
|
||||
echo "Supported RIDs:"
|
||||
echo " Generic (glibc):"
|
||||
@@ -101,6 +102,11 @@ while [ $# -gt 0 ]; do
|
||||
CLEAN=false
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: unknown option $1" >&2
|
||||
echo "Use -h, -?, or --help for usage." >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
RID="$1"
|
||||
shift
|
||||
@@ -109,6 +115,12 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
echo "Error: unexpected argument: $1" >&2
|
||||
echo "Use -h, -?, or --help for usage." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$RID" ]; then
|
||||
RID="$(default_rid)"
|
||||
AUTO_RID=true
|
||||
|
||||
Reference in New Issue
Block a user