mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
Install script, static assets, UI and release workflow updates
- install.sh: default dirs, --update/--service/--uninstall/--purge, in-dir dnsmasq-webui symlink, ContentRootPath note - UseStaticFiles instead of MapStaticAssets (fix 0-byte assets) - Program: ContentRootPath when not Development for symlink/CWD - App.razor: direct asset paths - Dnsmasq page: remove redundant status pre, margin above Reload button - Sidebar: logo 64px via app.css, hamburger vertically centered, toggler inside top-row - ApplicationTitle documented in README - publish/release: RIDs, Docker build for glibc
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Build self-contained binaries for multiple RIDs when a version tag is pushed,
|
||||
# Build self-contained binaries for portable Linux RIDs when a version tag is pushed,
|
||||
# then create/update the release and attach the zips as release assets.
|
||||
# linux-x64 and linux-arm64 are built inside a container (Debian-based SDK image) so
|
||||
# the resulting binaries work across glibc-based distros (Ubuntu, Debian, etc.).
|
||||
# Run: push a tag (e.g. v1.0.0); the workflow builds that commit and uploads assets.
|
||||
name: Release
|
||||
|
||||
@@ -16,20 +18,28 @@ jobs:
|
||||
rid:
|
||||
- linux-x64
|
||||
- linux-arm64
|
||||
- ubuntu.24.04-x64
|
||||
- ubuntu.22.04-x64
|
||||
- linux-musl-x64
|
||||
- linux-musl-arm64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Publish for ${{ matrix.rid }} (glibc in Docker)
|
||||
if: matrix.rid == 'linux-x64' || matrix.rid == 'linux-arm64'
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}:/src" -w /src \
|
||||
mcr.microsoft.com/dotnet/sdk:9.0 \
|
||||
./scripts/publish-self-contained.sh ${{ matrix.rid }}
|
||||
|
||||
- name: Setup .NET
|
||||
if: matrix.rid == 'linux-musl-x64' || matrix.rid == 'linux-musl-arm64'
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Publish for ${{ matrix.rid }}
|
||||
- name: Publish for ${{ matrix.rid }} (musl on host)
|
||||
if: matrix.rid == 'linux-musl-x64' || matrix.rid == 'linux-musl-arm64'
|
||||
run: ./scripts/publish-self-contained.sh ${{ matrix.rid }}
|
||||
|
||||
- name: Zip publish output
|
||||
|
||||
@@ -50,7 +50,7 @@ By default the script installs to **`~/.local/share/dnsmasq-webui`** and creates
|
||||
```bash
|
||||
dnsmasq-webui
|
||||
# or
|
||||
~/.local/share/dnsmasq-webui/DnsmasqWebUI
|
||||
~/.local/share/dnsmasq-webui/dnsmasq-webui
|
||||
```
|
||||
|
||||
**System-wide install (requires sudo):**
|
||||
@@ -98,6 +98,8 @@ sudo ./scripts/install.sh --uninstall --purge --system # Also remove /opt/dnsm
|
||||
|
||||
**Installing from a fork:** Pass the repo explicitly: `./scripts/install.sh --repo owner/dnsmasq-webui` or set `GITHUB_REPO=owner/dnsmasq-webui`. The default repo is `alexhopeoconnor/dnsmasq-webui`.
|
||||
|
||||
**If the binary fails to run** (e.g. `TypeLoadException` or glibc errors): use the install script with `--build-from-source` so the app is built for your machine. This requires the .NET SDK and a **git clone** (it does not work when installing via `curl ... | sh`). Clone the repo, then run `./scripts/install.sh --build-from-source`. The script checks that .NET is installed, detects your OS/arch, and builds (on Ubuntu it may use a distro-specific RID for a better match). Other distros use the portable RID and may still hit runtime issues; building from source on the target machine is the most reliable.
|
||||
|
||||
**Configuration:** Set at least `Dnsmasq__MainConfigPath` to your main dnsmasq config. See [Configuration](#configuration) for all options.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: TERMINAL SHOWING INSTALL.SH OUTPUT AND "Run: dnsmasq-webui" -->
|
||||
@@ -200,9 +202,19 @@ The app is configured via **appsettings.json**, **environment variables**, and *
|
||||
| `StatusShowCommand` | Optional: full status output (e.g. systemctl status) | `systemctl status dnsmasq --no-pager` |
|
||||
| `LogsCommand` | Optional: recent logs (e.g. journalctl) | `journalctl -u dnsmasq -n 100 --no-pager` |
|
||||
|
||||
**Application options** (use `Application__` prefix for env, `Application` section in JSON):
|
||||
|
||||
| Option | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `ApplicationTitle` | Title shown in the sidebar brand and browser tab (default: "Local DNS") | `"ApplicationTitle": "My DNS"` or `Application__ApplicationTitle=My DNS` |
|
||||
|
||||
**Host / URLs:** `ASPNETCORE_URLS=http://0.0.0.0:8080` or `--urls=http://0.0.0.0:8080` to bind to a specific address and port.
|
||||
|
||||
Place `appsettings.json` in the same directory as the executable (or use the default project paths when running with `dotnet run`).
|
||||
Place `appsettings.json` in the same directory as the executable (or use the default project paths when running with `dotnet run`). You can override any option via environment variables using the `Dnsmasq__` prefix (e.g. `Dnsmasq__MainConfigPath=/path/to/dnsmasq.conf`). To use the repo test config:
|
||||
|
||||
```bash
|
||||
Dnsmasq__MainConfigPath=/path/to/dnsmasq-webui/testdata/dnsmasq-test.conf dnsmasq-webui
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
+137
-16
@@ -16,6 +16,7 @@ SERVICE=false
|
||||
UNINSTALL=false
|
||||
PURGE=false
|
||||
UPDATE=false
|
||||
BUILD_FROM_SOURCE=false
|
||||
|
||||
# Default install dir (user-writable, no sudo). Overridden by --dir or --system.
|
||||
default_install_dir() {
|
||||
@@ -36,6 +37,7 @@ usage() {
|
||||
echo " --list List available releases (tag, name, published_at) and exit."
|
||||
echo " --version TAG Install from release TAG (e.g. v1.0.0). Default: latest."
|
||||
echo " --update Reinstall latest into the default user directory (~/.local/share/dnsmasq-webui)."
|
||||
echo " --build-from-source Build from source instead of downloading (requires .NET SDK and a git clone; not supported when run via curl | sh). Auto-detects RID for your machine. Use if the prebuilt binary fails to start (e.g. TypeLoadException)."
|
||||
echo " -h, -?, --help Show this help."
|
||||
echo ""
|
||||
echo "Install location:"
|
||||
@@ -67,8 +69,9 @@ usage() {
|
||||
echo " $0 --uninstall --purge # Remove services, symlinks, and default install dir"
|
||||
echo " sudo $0 --uninstall --purge --system # Also remove /opt/dnsmasq-webui"
|
||||
echo " $0 --list # List releases"
|
||||
echo " $0 --build-from-source # Build locally (if prebuilt binary fails on your system)"
|
||||
echo ""
|
||||
echo "After install, configure via appsettings.json or Dnsmasq__* environment variables, then run the binary (or dnsmasq-webui if symlink created). If you used --service, enable/start with systemctl."
|
||||
echo "After install, configure via appsettings.json or Dnsmasq__* environment variables, then run the binary (or dnsmasq-webui if symlink created). If you used --service, enable/start with systemctl. If the app fails to start with TypeLoadException or a glibc error, try --build-from-source (requires .NET SDK)."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -113,17 +116,15 @@ detect_arch() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Map to portable RIDs only (linux-x64, linux-arm64, linux-musl-*). Ubuntu and other
|
||||
# glibc distros use linux-x64/linux-arm64; Alpine uses linux-musl-*. Used for downloading
|
||||
# release assets (CI builds portable RIDs only).
|
||||
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 ;;
|
||||
@@ -139,6 +140,18 @@ detect_rid() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Print detected environment (OS, arch) for user feedback.
|
||||
print_env_detection() {
|
||||
local rid
|
||||
rid="$1"
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
echo "Detected: ${ID:-unknown} ${VERSION_ID:-}, $(uname -m) -> RID $rid"
|
||||
else
|
||||
echo "Detected: $(uname -m) -> RID $rid"
|
||||
fi
|
||||
}
|
||||
|
||||
# GET release (latest or by tag). Output raw JSON to stdout.
|
||||
fetch_release() {
|
||||
local api_url
|
||||
@@ -270,8 +283,97 @@ do_uninstall() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Build from source and install to INSTALL_DIR. Requires .NET SDK and running from a git clone.
|
||||
do_install_from_source() {
|
||||
local script_dir repo_root publish_output rid publish_dir
|
||||
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo_root="$(cd "$script_dir/.." && pwd)"
|
||||
|
||||
if [ ! -f "$repo_root/scripts/publish-self-contained.sh" ]; then
|
||||
echo "Error: --build-from-source requires a git clone (run from the repo directory). Not found: $repo_root/scripts/publish-self-contained.sh" >&2
|
||||
echo "Do not use --build-from-source when installing via 'curl ... | sh'; clone the repo first, then run ./scripts/install.sh --build-from-source" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v dotnet >/dev/null 2>&1; then
|
||||
echo "Error: --build-from-source requires the .NET SDK. Install from https://dotnet.microsoft.com/download or your distro (e.g. apt install dotnet-sdk-9.0)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Error: --system installs to /opt and requires root. Run with sudo: sudo $0 --system --build-from-source" >&2
|
||||
exit 1
|
||||
fi
|
||||
INSTALL_DIR="/opt/dnsmasq-webui"
|
||||
elif [ -z "$INSTALL_DIR" ]; then
|
||||
INSTALL_DIR="$(default_install_dir)"
|
||||
fi
|
||||
|
||||
echo "Building from source (auto-detecting RID for your machine)..."
|
||||
publish_output="$(cd "$repo_root" && ./scripts/publish-self-contained.sh 2>&1)" || exit $?
|
||||
echo "$publish_output"
|
||||
rid="$(echo "$publish_output" | sed -n 's/^Detected RID: \([^ ]*\).*/\1/p')"
|
||||
if [ -z "$rid" ]; then
|
||||
rid="$(echo "$publish_output" | sed -n 's/^Publishing self-contained.* for \([^.]*\)\.\.\./\1/p')"
|
||||
fi
|
||||
if [ -z "$rid" ]; then
|
||||
echo "Error: Could not determine RID from build output." >&2
|
||||
exit 1
|
||||
fi
|
||||
publish_dir="$repo_root/src/DnsmasqWebUI/bin/Release/net9.0/$rid/publish"
|
||||
if [ ! -d "$publish_dir" ] || [ ! -f "$publish_dir/DnsmasqWebUI" ]; then
|
||||
echo "Error: Build output not found at $publish_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing to $INSTALL_DIR ..."
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cp -a "$publish_dir"/* "$INSTALL_DIR/"
|
||||
ln -sf DnsmasqWebUI "$INSTALL_DIR/dnsmasq-webui" 2>/dev/null || true
|
||||
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
ln -sf "$INSTALL_DIR/dnsmasq-webui" /usr/local/bin/dnsmasq-webui 2>/dev/null || true
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR (system-wide)"
|
||||
echo "Run: dnsmasq-webui (or $INSTALL_DIR/dnsmasq-webui)"
|
||||
else
|
||||
LOCAL_BIN="${HOME:-}/.local/bin"
|
||||
if [ -n "${HOME:-}" ] && [ -d "$(dirname "$LOCAL_BIN")" ]; then
|
||||
mkdir -p "$LOCAL_BIN"
|
||||
if [ -w "$LOCAL_BIN" ]; then
|
||||
ln -sf "$INSTALL_DIR/dnsmasq-webui" "$LOCAL_BIN/dnsmasq-webui" 2>/dev/null && echo "Symlink: $LOCAL_BIN/dnsmasq-webui -> $INSTALL_DIR/dnsmasq-webui" || true
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR"
|
||||
echo "Run: $INSTALL_DIR/dnsmasq-webui"
|
||||
if [ -f "$LOCAL_BIN/dnsmasq-webui" ] 2>/dev/null; then
|
||||
echo " or: dnsmasq-webui (if ~/.local/bin is in your PATH)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = true ]; then
|
||||
if [ "$SYSTEM_INSTALL" = true ] && [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Error: Installing a system service (--service with --system) requires root." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
echo "Error: systemd not found. --service is only supported on systemd-based systems." >&2
|
||||
exit 1
|
||||
fi
|
||||
install_systemd_unit "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
echo "Configure via appsettings.json in that directory or Dnsmasq__* environment variables (e.g. Dnsmasq__MainConfigPath=/etc/dnsmasq.conf)."
|
||||
}
|
||||
|
||||
# Main install: fetch release, find asset for RID, download, extract, optionally symlink.
|
||||
do_install() {
|
||||
if [ "$BUILD_FROM_SOURCE" = true ]; then
|
||||
do_install_from_source
|
||||
return
|
||||
fi
|
||||
|
||||
check_jq
|
||||
detect_repo
|
||||
|
||||
@@ -297,7 +399,7 @@ do_install() {
|
||||
fi
|
||||
|
||||
rid="$(detect_rid)"
|
||||
echo "Detected RID: $rid"
|
||||
print_env_detection "$rid"
|
||||
echo "Fetching release..."
|
||||
release_json="$(fetch_release)"
|
||||
tag="$(echo "$release_json" | jq -r '.tag_name')"
|
||||
@@ -317,24 +419,25 @@ do_install() {
|
||||
echo "Extracting to $INSTALL_DIR ..."
|
||||
unzip -o -q "$tmpzip" -d "$INSTALL_DIR"
|
||||
rm -f "$tmpzip"
|
||||
ln -sf DnsmasqWebUI "$INSTALL_DIR/dnsmasq-webui" 2>/dev/null || true
|
||||
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
ln -sf "$INSTALL_DIR/DnsmasqWebUI" /usr/local/bin/dnsmasq-webui 2>/dev/null || true
|
||||
ln -sf "$INSTALL_DIR/dnsmasq-webui" /usr/local/bin/dnsmasq-webui 2>/dev/null || true
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR (system-wide)"
|
||||
echo "Run: dnsmasq-webui (or $INSTALL_DIR/DnsmasqWebUI)"
|
||||
echo "Run: dnsmasq-webui (or $INSTALL_DIR/dnsmasq-webui)"
|
||||
else
|
||||
# User install: create ~/.local/bin symlink so `dnsmasq-webui` works if ~/.local/bin is in PATH
|
||||
LOCAL_BIN="${HOME:-}/.local/bin"
|
||||
if [ -n "${HOME:-}" ] && [ -d "$(dirname "$LOCAL_BIN")" ]; then
|
||||
mkdir -p "$LOCAL_BIN"
|
||||
if [ -w "$LOCAL_BIN" ]; then
|
||||
ln -sf "$INSTALL_DIR/DnsmasqWebUI" "$LOCAL_BIN/dnsmasq-webui" 2>/dev/null && echo "Symlink: $LOCAL_BIN/dnsmasq-webui -> $INSTALL_DIR/DnsmasqWebUI" || true
|
||||
ln -sf "$INSTALL_DIR/dnsmasq-webui" "$LOCAL_BIN/dnsmasq-webui" 2>/dev/null && echo "Symlink: $LOCAL_BIN/dnsmasq-webui -> $INSTALL_DIR/dnsmasq-webui" || true
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR"
|
||||
echo "Run: $INSTALL_DIR/DnsmasqWebUI"
|
||||
echo "Run: $INSTALL_DIR/dnsmasq-webui"
|
||||
if [ -f "$LOCAL_BIN/dnsmasq-webui" ] 2>/dev/null; then
|
||||
echo " or: dnsmasq-webui (if ~/.local/bin is in your PATH)"
|
||||
fi
|
||||
@@ -345,17 +448,21 @@ do_install() {
|
||||
fi
|
||||
|
||||
echo "Configure via appsettings.json in that directory or Dnsmasq__* environment variables (e.g. Dnsmasq__MainConfigPath=/etc/dnsmasq.conf)."
|
||||
echo "If the app fails to start with TypeLoadException or a glibc error, try: $0 --build-from-source (requires .NET SDK)."
|
||||
}
|
||||
|
||||
# Install systemd unit. Call only when SERVICE=true and systemctl exists.
|
||||
# Removes the other service type first (user vs system) so switching works cleanly.
|
||||
# $1 = INSTALL_DIR (where DnsmasqWebUI binary and appsettings.json live)
|
||||
# $1 = INSTALL_DIR (contains dnsmasq-webui runnable and appsettings.json)
|
||||
install_systemd_unit() {
|
||||
local dir bin
|
||||
dir="$1"
|
||||
if [ -f "$dir/dnsmasq-webui" ]; then
|
||||
bin="$dir/dnsmasq-webui"
|
||||
elif [ -f "$dir/DnsmasqWebUI" ]; then
|
||||
bin="$dir/DnsmasqWebUI"
|
||||
if [ ! -f "$bin" ]; then
|
||||
echo "Warning: Binary $bin not found; skipping systemd unit install." >&2
|
||||
else
|
||||
echo "Warning: No binary found in $dir; skipping systemd unit install." >&2
|
||||
return 0
|
||||
fi
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
@@ -470,6 +577,10 @@ while [ $# -gt 0 ]; do
|
||||
PURGE=true
|
||||
shift
|
||||
;;
|
||||
--build-from-source)
|
||||
BUILD_FROM_SOURCE=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: unknown option $1" >&2
|
||||
usage >&2
|
||||
@@ -489,8 +600,8 @@ if [ "$PURGE" = true ] && [ "$UNINSTALL" != true ]; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "$UNINSTALL" = true ]; then
|
||||
if [ "$UPDATE" = true ] || [ -n "$VERSION" ] || [ "$SERVICE" = true ]; then
|
||||
echo "Error: --uninstall cannot be combined with install/update options (--version, --update, --service). Run uninstall alone, then install if needed." >&2
|
||||
if [ "$UPDATE" = true ] || [ -n "$VERSION" ] || [ "$SERVICE" = true ] || [ "$BUILD_FROM_SOURCE" = true ]; then
|
||||
echo "Error: --uninstall cannot be combined with install/update options (--version, --update, --service, --build-from-source). Run uninstall alone, then install if needed." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$LIST" = true ]; then
|
||||
@@ -498,6 +609,16 @@ if [ "$UNINSTALL" = true ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ "$BUILD_FROM_SOURCE" = true ]; then
|
||||
if [ "$LIST" = true ]; then
|
||||
echo "Error: --build-from-source cannot be combined with --list." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$VERSION" ] || [ "$UPDATE" = true ]; then
|
||||
echo "Error: --build-from-source builds from current source; do not use --version or --update." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ "$LIST" = true ]; then
|
||||
if [ "$UNINSTALL" = true ] || [ "$UPDATE" = true ] || [ "$SERVICE" = true ] || [ -n "$VERSION" ]; then
|
||||
echo "Error: --list lists releases and exits; do not combine with install/uninstall options." >&2
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
# for ASP.NET Core). Copy the publish directory to the target host and run the binary.
|
||||
#
|
||||
# Platform auto-detection: if you do not pass a RID, the script picks one from /etc/os-release
|
||||
# and uname -m. On Ubuntu 24.04/22.04 we use ubuntu.<ver>-<arch> so the runtime matches the
|
||||
# host and avoids TypeLoadException seen with generic linux-x64 on those distros. Else we use
|
||||
# generic linux-* or linux-musl-* (Alpine). Trimming is off by default because PublishTrimmed
|
||||
# and uname -m. We use generic linux-* (glibc) or linux-musl-* (Alpine). Trimming is off by default because PublishTrimmed
|
||||
# can cause Blazor routing/404 issues. The script cleans the target RID before publish
|
||||
# so previous artefacts (e.g. from a different --trim or RID) cannot pollute the build.
|
||||
set -e
|
||||
@@ -14,6 +12,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PROJECT="$REPO_ROOT/src/DnsmasqWebUI/DnsmasqWebUI.csproj"
|
||||
|
||||
# Portable RIDs (built in CI). Ubuntu RIDs for local builds only (CI does not build these;
|
||||
# use when .NET from distro repo so the binary runs on this machine without TypeLoadException).
|
||||
VALID_RIDS="linux-x64 linux-arm64 linux-arm linux-musl-x64 linux-musl-arm64 ubuntu.24.04-x64 ubuntu.24.04-arm64 ubuntu.22.04-x64 ubuntu.22.04-arm64"
|
||||
TRIM=false
|
||||
CLEAN=true
|
||||
@@ -30,7 +30,9 @@ detect_arch() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Pick RID from OS and arch so runtime matches host (avoids TypeLoadException on Ubuntu).
|
||||
# Pick RID from OS and arch. We only add explicit distro RIDs where we've seen
|
||||
# TypeLoadException with portable linux-x64 (Ubuntu when .NET from distro). Alpine
|
||||
# uses musl. Other distros (Debian, Fedora, etc.) fall through to linux-x64/linux-arm64.
|
||||
default_rid() {
|
||||
local arch
|
||||
arch="$(detect_arch)"
|
||||
@@ -64,7 +66,7 @@ while [ $# -gt 0 ]; do
|
||||
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 "Pass options first, then RID if desired (e.g. $0 --trim linux-x64)."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --trim Enable trimming (smaller output; can cause 404/routing issues with Blazor)"
|
||||
@@ -79,7 +81,7 @@ while [ $# -gt 0 ]; do
|
||||
echo " Alpine (musl):"
|
||||
echo " linux-musl-x64 Alpine amd64"
|
||||
echo " linux-musl-arm64 Alpine aarch64"
|
||||
echo " Ubuntu (use on Ubuntu to avoid TypeLoadException with generic RID):"
|
||||
echo " Ubuntu (local builds only; not in CI releases; use if prebuilt linux-x64 fails):"
|
||||
echo " ubuntu.24.04-x64 ubuntu.24.04-arm64"
|
||||
echo " ubuntu.22.04-x64 ubuntu.22.04-arm64"
|
||||
echo ""
|
||||
@@ -87,11 +89,11 @@ while [ $# -gt 0 ]; do
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Publish for current machine (auto-detect RID)"
|
||||
echo " $0 ubuntu.24.04-x64 # Publish for Ubuntu 24.04 amd64"
|
||||
echo " $0 linux-x64 # Publish for glibc amd64 (Debian, Ubuntu, etc.)"
|
||||
echo " $0 linux-arm64 # Publish for Raspberry Pi 4/5 or other aarch64"
|
||||
echo " $0 linux-musl-x64 # Publish for Alpine (e.g. Docker)"
|
||||
echo " $0 --trim linux-x64 # Smaller build (not recommended for Blazor)"
|
||||
echo " $0 --no-clean ubuntu.24.04-x64 # Skip clean (faster; same RID/options as last run)"
|
||||
echo " $0 --no-clean linux-x64 # Skip clean (faster; same RID/options as last run)"
|
||||
exit 0
|
||||
;;
|
||||
--trim)
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["lib/bootstrap-icons/bootstrap-icons.min.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["DnsmasqWebUI.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="lib/bootstrap-icons/bootstrap-icons.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="DnsmasqWebUI.styles.css" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="favicon-48.png" />
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid d-flex align-items-center">
|
||||
<a class="navbar-brand d-flex align-items-center gap-2" href="" aria-label="@AppTitle home">
|
||||
<img src="logo.webp" alt="" class="brand-logo" />
|
||||
<span class="brand-logo-wrap"><img src="logo.webp" alt="" class="brand-logo" /></span>
|
||||
<span>@AppTitle</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler" id="nav-toggler" aria-label="Toggle navigation" />
|
||||
</div>
|
||||
|
||||
<div class="nav-scrollable" onclick="document.getElementById('nav-toggler')?.click()">
|
||||
<nav class="nav flex-column" aria-label="Main navigation">
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
top: 50%;
|
||||
right: 1rem;
|
||||
transform: translateY(-50%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@@ -16,6 +17,7 @@
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: relative;
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
@@ -31,13 +33,7 @@
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
height: 1.75rem;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* Logo size/alignment: see app.css .sidebar .navbar-brand .brand-logo-wrap / .brand-logo */
|
||||
|
||||
/* Bootstrap Icons in nav: white, sized to match link text */
|
||||
.nav-item .nav-icon {
|
||||
|
||||
@@ -48,13 +48,9 @@ else if (_status != null)
|
||||
</ul>
|
||||
}
|
||||
<EffectiveConfigSection Status="_status" />
|
||||
@if (_status.DnsmasqStatus != "active" && (_status.StatusCommandStdout != null || _status.StatusCommandStderr != null))
|
||||
{
|
||||
<pre class="pre-block mt-2 mb-0" style="max-height: 12rem;">@(_status.StatusCommandStdout != null ? _status.StatusCommandStdout : "")@(_status.StatusCommandStdout != null && _status.StatusCommandStderr != null ? "\n" : "")@(_status.StatusCommandStderr ?? "")</pre>
|
||||
}
|
||||
@if (_status.ReloadCommandConfigured)
|
||||
{
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap mt-3">
|
||||
<button class="btn btn-primary btn-sm" @onclick="ReloadDnsmasq" disabled="@_reloading">
|
||||
@if (_reloading)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- Avoid pre-compressed .br/.gz so MapStaticAssets serves uncompressed files (fixes 0-byte/empty response bug). -->
|
||||
<DisableBuildCompression>true</DisableBuildCompression>
|
||||
<PublishCompressionFormats></PublishCompressionFormats>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,7 +3,11 @@ using DnsmasqWebUI.Extensions;
|
||||
using DnsmasqWebUI.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
// When not in Development, use the app's directory (not CWD) so static assets work when run via symlink or from any CWD.
|
||||
var isDevelopment = string.Equals(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), "Development", StringComparison.OrdinalIgnoreCase);
|
||||
var builder = isDevelopment
|
||||
? WebApplication.CreateBuilder(args)
|
||||
: WebApplication.CreateBuilder(new WebApplicationOptions { ContentRootPath = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Args = args });
|
||||
|
||||
// ---- Application options (title, etc.) ----
|
||||
builder.Services.AddOptions<ApplicationOptions>()
|
||||
@@ -55,7 +59,8 @@ app.UseAntiforgery();
|
||||
|
||||
// ---- Endpoints ----
|
||||
app.MapControllers();
|
||||
app.MapStaticAssets();
|
||||
// UseStaticFiles: MapStaticAssets returns 0-byte responses for fingerprinted assets (known bug). Serve from wwwroot directly.
|
||||
app.UseStaticFiles();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
|
||||
@@ -209,3 +209,21 @@ h1:focus {
|
||||
.bi-gear-sm {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
/* Sidebar brand logo – 64px, header stays 3.5rem min-height */
|
||||
.sidebar .navbar-brand .brand-logo-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 4rem; /* 64px */
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.sidebar .navbar-brand .brand-logo {
|
||||
display: block;
|
||||
height: 4rem;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
Vendored
+3
-1
@@ -1,4 +1,4 @@
|
||||
# Config for the dnsmasq test container only (docker-compose.test.yml).
|
||||
# Config for the dnsmasq test container only (docker-compose.test.yml).
|
||||
# DNS + DHCP: forwards DNS to Cloudflare/Google with caching; hands out DHCP on testnet.
|
||||
# Uses same testdata layout so app and dnsmasq share hosts, dhcp.conf, leases.
|
||||
# Richer config so UI shows multi-value lists (server, addn-hosts, address, dhcp-range) and readonly sources.
|
||||
@@ -32,3 +32,5 @@ addn-hosts=/data/hosts.extra
|
||||
# Log to file so the harness can show real logs (LogsCommand tails this)
|
||||
log-facility=/data/dnsmasq.log
|
||||
log-queries
|
||||
|
||||
conf-file=zz-dnsmasq-webui.conf
|
||||
|
||||
Vendored
+3
-1
@@ -1,4 +1,6 @@
|
||||
# Sample main dnsmasq config (not managed by this app).
|
||||
# Sample main dnsmasq config (not managed by this app).
|
||||
# In production this would typically be /etc/dnsmasq.conf.
|
||||
# It includes all .conf files from dnsmasq.d/ (e.g. dhcp.conf managed by this app).
|
||||
conf-dir=/etc/dnsmasq.d/
|
||||
|
||||
conf-file=zz-dnsmasq-webui.conf
|
||||
|
||||
Reference in New Issue
Block a user