mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
Add LICENSE, README, logo; update install script, nav, favicon, App
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 dnsmasq-webui contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,434 @@
|
||||
# dnsmasq-webui
|
||||
|
||||
<div align="center">
|
||||
<img src="src/DnsmasqWebUI/wwwroot/logo.png" alt="dnsmasq-webui logo" width="200">
|
||||
</div>
|
||||
|
||||
A self-hosted web UI for managing [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) configuration and hosts. It runs alongside your existing dnsmasq setup and edits the same config files; it does not replace dnsmasq.
|
||||
|
||||
---
|
||||
|
||||
## Purpose and features
|
||||
|
||||
**Purpose:** Point the UI at your dnsmasq config (and optionally hosts). Use the browser to view and edit config, managed hosts, and (if you use DHCP) reservations. Reload dnsmasq after changes via a configurable command.
|
||||
|
||||
**Features:**
|
||||
|
||||
- View and edit **dnsmasq config** (main config + conf-dir), with per-option source (which file or managed block).
|
||||
- **Managed hosts file** (addn-hosts) editable in the UI; app writes a single managed hosts file and includes it via the managed config.
|
||||
- **DHCP host entries** (reservations) if you use DHCP; edit in the UI, written into the managed config.
|
||||
- **Reload dnsmasq** after config changes (configurable command, e.g. `systemctl reload dnsmasq` or `pkill -HUP -x dnsmasq`).
|
||||
- Optional **status** and **recent logs** commands (e.g. systemctl/journalctl or custom scripts) shown on the Dnsmasq page.
|
||||
- **Self-contained Linux binaries** per OS/arch (RID); no .NET install required. Can run in Docker or directly on the host.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: MAIN CONFIG OR CONFIG EDITOR PAGE IN THE BROWSER -->
|
||||
|
||||
---
|
||||
|
||||
## Quick start and install
|
||||
|
||||
**Prerequisites:** Linux. dnsmasq (or a dnsmasq container) should already be set up. For the install script: `curl`, `jq`, `unzip`. (WSL may work but is untested.)
|
||||
|
||||
### Install from release
|
||||
|
||||
The recommended way to get a prebuilt binary is the **install script**. It picks the right build for your OS/arch, installs to a user or system directory, and can create a `dnsmasq-webui` symlink so you can run it from the terminal.
|
||||
|
||||
**Quick start:**
|
||||
|
||||
```bash
|
||||
curl -sSL https://raw.githubusercontent.com/alexhopeoconnor/dnsmasq-webui/main/scripts/install.sh | sh
|
||||
```
|
||||
|
||||
**From a git clone** (repo is detected from `git remote origin`):
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
```
|
||||
|
||||
By default the script installs to **`~/.local/share/dnsmasq-webui`** and creates a symlink at `~/.local/bin/dnsmasq-webui` if that directory exists and is writable (so you can run `dnsmasq-webui` if `~/.local/bin` is in your PATH). Then configure (see [Configuration](#configuration)) and run:
|
||||
|
||||
```bash
|
||||
dnsmasq-webui
|
||||
# or
|
||||
~/.local/share/dnsmasq-webui/DnsmasqWebUI
|
||||
```
|
||||
|
||||
**System-wide install (requires sudo):**
|
||||
|
||||
```bash
|
||||
sudo ./scripts/install.sh --system
|
||||
```
|
||||
|
||||
Installs to `/opt/dnsmasq-webui` and symlinks `/usr/local/bin/dnsmasq-webui`. Run with `dnsmasq-webui` from any terminal.
|
||||
|
||||
**Install as a systemd service (start on boot or at login):**
|
||||
|
||||
On systemd-based systems you can install a unit so the app runs as a service. Use `--service` alone for a **user** service (no sudo; starts when you log in), or with `--system` for a **system** service (requires sudo; starts at boot).
|
||||
|
||||
```bash
|
||||
./scripts/install.sh --service # User service (no sudo)
|
||||
sudo ./scripts/install.sh --system --service # System service (starts at boot)
|
||||
```
|
||||
|
||||
After install: configure the app (see [Configuration](#configuration)), then start the service: `systemctl --user start dnsmasq-webui` or `sudo systemctl start dnsmasq-webui`. User services run only when you’re logged in unless you enable linger: `loginctl enable-linger`.
|
||||
|
||||
**Update to latest release:**
|
||||
|
||||
```bash
|
||||
./scripts/install.sh --update
|
||||
```
|
||||
|
||||
Reinstalls the latest release into the default user directory (~/.local/share/dnsmasq-webui).
|
||||
|
||||
**Custom directory or specific version:**
|
||||
|
||||
```bash
|
||||
./scripts/install.sh --dir /opt/dnsmasq-webui
|
||||
./scripts/install.sh --version v1.0.0
|
||||
```
|
||||
|
||||
**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`.
|
||||
|
||||
**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" -->
|
||||
|
||||
### Switching to a different release
|
||||
|
||||
Re-run the install script with the same `--dir` (or default) and the desired `--version`. The script overwrites that directory with the chosen release. To go back to latest, use `--update` or omit `--version`.
|
||||
|
||||
---
|
||||
|
||||
## Running with dnsmasq in Docker
|
||||
|
||||
If you run dnsmasq in a container and want the UI in the **same** container, you have two main options.
|
||||
|
||||
### Option A: Use this repo’s image (app + dnsmasq in one image)
|
||||
|
||||
Build the image from this repo’s **Dockerfile**. It builds the app and installs dnsmasq in one image. The **entrypoint** (`scripts/entrypoint.sh`) starts dnsmasq when `DNSMASQ_CONF` is set, then runs the app.
|
||||
|
||||
**Build and run:**
|
||||
|
||||
```bash
|
||||
docker build -t dnsmasq-webui .
|
||||
docker run -d -p 8080:8080 \
|
||||
-e DNSMASQ_CONF=/data/dnsmasq.conf \
|
||||
-e Dnsmasq__MainConfigPath=/data/dnsmasq.conf \
|
||||
-e Dnsmasq__ReloadCommand="pkill -HUP -x dnsmasq" \
|
||||
-v /path/on/host:/data \
|
||||
--cap-add=NET_ADMIN \
|
||||
dnsmasq-webui
|
||||
```
|
||||
|
||||
Mount your dnsmasq config directory as `/data` (or adjust paths and env vars). The app and dnsmasq both use files under `/data`.
|
||||
|
||||
### Option B: Build your own image (self-contained binary from a release)
|
||||
|
||||
The self-contained publish **works in a container** (it’s a Linux binary). You can add it to your own dnsmasq image by copying a release zip and unzipping into the image (no need to run the install script inside the container).
|
||||
|
||||
**Example Dockerfile** (extend your dnsmasq base):
|
||||
|
||||
```dockerfile
|
||||
FROM your-dnsmasq-image:tag
|
||||
# Install unzip if not present
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends unzip curl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Download and extract the release for linux-x64 (or match your base image: linux-musl-x64 for Alpine, etc.)
|
||||
ARG RID=linux-x64
|
||||
ARG VERSION=v1.0.0
|
||||
RUN curl -sSL "https://github.com/alexhopeoconnor/dnsmasq-webui/releases/download/${VERSION}/dnsmasq-webui-${RID}.zip" -o /tmp/app.zip \
|
||||
&& unzip -o /tmp/app.zip -d /app && rm /tmp/app.zip
|
||||
|
||||
WORKDIR /app
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
|
||||
# Start dnsmasq (if your image doesn’t), then the app. Set DNSMASQ_CONF and Dnsmasq__* as needed.
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
```
|
||||
|
||||
Set `Dnsmasq__MainConfigPath`, `Dnsmasq__ReloadCommand`, and other options via environment variables or an `appsettings.json` in `/app`. Mount your config dir and expose 8080.
|
||||
|
||||
**Config summary for containers:**
|
||||
|
||||
| Env var | Example | Purpose |
|
||||
|--------|--------|--------|
|
||||
| `DNSMASQ_CONF` | `/data/dnsmasq.conf` | Path used by entrypoint to start dnsmasq (optional) |
|
||||
| `Dnsmasq__MainConfigPath` | `/data/dnsmasq.conf` | Main dnsmasq config path (required) |
|
||||
| `Dnsmasq__ReloadCommand` | `pkill -HUP -x dnsmasq` | Run after config changes |
|
||||
| `Dnsmasq__StatusShowCommand` | `/app/dnsmasq-status.sh` | Optional status output (e.g. our script in test harness) |
|
||||
| `ASPNETCORE_URLS` | `http://+:8080` | Port the app listens on |
|
||||
|
||||
See [Configuration](#configuration) for all options.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: EXAMPLE DOCKERFILE SNIPPET OR DOCKER RUN COMMAND SHOWING DNSMASQ + APP IN ONE CONTAINER -->
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
The app is configured via **appsettings.json**, **environment variables**, and **command-line arguments**. Later sources override earlier ones (e.g. CLI overrides env over appsettings).
|
||||
|
||||
**Required:** Set at least the main dnsmasq config path.
|
||||
|
||||
| Source | Example |
|
||||
|--------|--------|
|
||||
| **Environment** | `Dnsmasq__MainConfigPath=/etc/dnsmasq.conf` |
|
||||
| **appsettings.json** | `"Dnsmasq": { "MainConfigPath": "/etc/dnsmasq.conf" }` |
|
||||
| **CLI** | `--Dnsmasq:MainConfigPath=/etc/dnsmasq.conf` |
|
||||
|
||||
**Dnsmasq options** (use `Dnsmasq__` prefix for env, `Dnsmasq:` section in JSON, `--Dnsmasq:OptionName=value` for CLI):
|
||||
|
||||
| Option | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `MainConfigPath` | Path to main dnsmasq config (required) | `/etc/dnsmasq.conf` |
|
||||
| `ManagedFileName` | Filename of the managed config file the app writes | `zz-dnsmasq-webui.conf` |
|
||||
| `ManagedHostsFileName` | Filename of the managed hosts file the app writes | `zz-dnsmasq-webui.hosts` |
|
||||
| `SystemHostsPath` | Optional path to system hosts (read-only in UI) | `/etc/hosts` |
|
||||
| `ReloadCommand` | Command run after config changes | `systemctl reload dnsmasq` or `pkill -HUP -x dnsmasq` |
|
||||
| `StatusCommand` | Optional: check if dnsmasq is running | `pgrep -x dnsmasq` |
|
||||
| `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` |
|
||||
|
||||
**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`).
|
||||
|
||||
---
|
||||
|
||||
## Scripts
|
||||
|
||||
All scripts live under `scripts/` and are intended for Linux (WSL may work but is untested).
|
||||
|
||||
### scripts/install.sh
|
||||
|
||||
**Purpose:** Download and install dnsmasq-webui from a GitHub release for the current OS/arch (RID). Supports install, **update** (reinstall latest to default dir), and switch-release (re-run with same `--dir` and different `--version`). Default install is user-writable (~/.local/share/dnsmasq-webui) with an optional symlink so you can run `dnsmasq-webui` from the terminal; use `--system` for a system-wide install to /opt (requires sudo). Use `--service` to install a systemd unit (user service without sudo, or system service with `--system`); only supported on systemd-based systems.
|
||||
|
||||
**Usage:** `./scripts/install.sh [OPTIONS]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--repo OWNER/REPO` | GitHub owner/repo (or set `GITHUB_REPO`). When run from a clone, repo is detected from `git remote origin`. Default repo: `alexhopeoconnor/dnsmasq-webui`. |
|
||||
| `--list` | List available releases (tag, name, published_at) and exit. |
|
||||
| `--version TAG` | Install from release TAG (e.g. v1.0.0). Default: latest. |
|
||||
| `--update` | Reinstall latest into the default user directory (~/.local/share/dnsmasq-webui). |
|
||||
| `--dir DIR` | Install into DIR instead of default. |
|
||||
| `--system` | Install to /opt/dnsmasq-webui and symlink /usr/local/bin/dnsmasq-webui. Requires root (run with sudo). |
|
||||
| `--service` | Install a systemd unit so the app runs as a service. With `--system`: system unit (requires root, starts at boot). Without: user unit (no sudo, starts at login). Only on systemd-based systems. |
|
||||
| `-h`, `-?`, `--help` | Show help. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Install latest (from clone: repo auto-detected; from curl: uses alexhopeoconnor/dnsmasq-webui by default, or use --repo)
|
||||
./scripts/install.sh
|
||||
|
||||
# Update to latest release
|
||||
./scripts/install.sh --update
|
||||
|
||||
# Install a specific version to default dir
|
||||
./scripts/install.sh --version v1.0.0
|
||||
|
||||
# System-wide install (requires sudo)
|
||||
sudo ./scripts/install.sh --system
|
||||
|
||||
# Install + systemd service (user service: no sudo; system service: starts at boot)
|
||||
./scripts/install.sh --service
|
||||
sudo ./scripts/install.sh --system --service
|
||||
|
||||
# Install to custom dir
|
||||
./scripts/install.sh --dir /opt/dnsmasq-webui
|
||||
|
||||
# Switch to another release (overwrites that dir)
|
||||
./scripts/install.sh --dir ~/.local/share/dnsmasq-webui --version v0.9.0
|
||||
|
||||
# List releases (from fork)
|
||||
./scripts/install.sh --repo alexhopeoconnor/dnsmasq-webui --list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### scripts/publish-self-contained.sh
|
||||
|
||||
**Purpose:** Build a self-contained folder publish for Linux (no single-file). Used locally or in CI to produce the binaries that get zipped and attached to releases. Auto-detects RID when not specified.
|
||||
|
||||
**Usage:** `./scripts/publish-self-contained.sh [OPTIONS] [RID]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--trim` | Enable trimming (smaller output; can cause 404/routing issues with Blazor). |
|
||||
| `--no-clean` | Skip clean before publish (faster; use only if same RID and options as last run). |
|
||||
| `-h`, `-?`, `--help` | Show help. |
|
||||
|
||||
**RID:** Optional. If omitted, script picks one from `/etc/os-release` and `uname -m` (e.g. Ubuntu 24.04 → `ubuntu.24.04-x64`, Alpine → `linux-musl-x64`, else `linux-x64` / `linux-arm64` / `linux-arm`).
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Publish for current machine (auto-detect RID)
|
||||
./scripts/publish-self-contained.sh
|
||||
|
||||
# Publish for a specific RID
|
||||
./scripts/publish-self-contained.sh ubuntu.24.04-x64
|
||||
./scripts/publish-self-contained.sh linux-arm64
|
||||
./scripts/publish-self-contained.sh linux-musl-x64
|
||||
|
||||
# Smaller build (not recommended for Blazor)
|
||||
./scripts/publish-self-contained.sh --trim linux-x64
|
||||
|
||||
# Skip clean for a faster rebuild
|
||||
./scripts/publish-self-contained.sh --no-clean ubuntu.24.04-x64
|
||||
```
|
||||
|
||||
**Output:** `src/DnsmasqWebUI/bin/Release/net9.0/<RID>/publish/`
|
||||
|
||||
---
|
||||
|
||||
### scripts/prepare-test-mount.sh
|
||||
|
||||
**Purpose:** Prepare the testdata mount and optionally start or stop the Docker test harness (app + dnsmasq + DHCP clients). Syncs a source dir (default: `testdata`) into a mount dir (default: `testdata-mount`), cleans up previous test data, then runs `docker compose -f docker-compose.test.yml up -d` (or only prepares the mount with `--prepare-only`).
|
||||
|
||||
**Usage:** `./scripts/prepare-test-mount.sh [OPTIONS] [--]`
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--source DIR` | Source to copy from (default: `testdata`). |
|
||||
| `--mount DIR` | Target mount directory (default: `testdata-mount`). Compose uses `TESTDATA_MOUNT`; script exports it when you use `--mount`. |
|
||||
| `--no-clear` | Do not clear mount dir; only sync over existing contents (e.g. preserve leases between runs). |
|
||||
| `--prepare-only` | Only prepare the mount; do not run docker compose. |
|
||||
| `--build` | Pass `--build` to docker compose (rebuild images). Default: use existing images. |
|
||||
| `--no-build` | Do not rebuild (default). |
|
||||
| `--recreate` | Pass `--force-recreate` to docker compose. |
|
||||
| `--stop` | Stop the harness: `docker compose down` (no prepare, no start). |
|
||||
| `--tidy` | Stop the harness and clear the mount directory. |
|
||||
| `-h`, `-?`, `--help` | Show help. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Full run: clear mount, sync testdata, start containers (no rebuild)
|
||||
./scripts/prepare-test-mount.sh
|
||||
|
||||
# Rebuild images then start
|
||||
./scripts/prepare-test-mount.sh --build
|
||||
|
||||
# Only prepare the mount; start manually later
|
||||
./scripts/prepare-test-mount.sh --prepare-only
|
||||
# Then: TESTDATA_MOUNT=./testdata-mount docker compose -f docker-compose.test.yml up -d
|
||||
|
||||
# Preserve mount contents, sync over it, start
|
||||
./scripts/prepare-test-mount.sh --no-clear
|
||||
|
||||
# Stop the harness
|
||||
./scripts/prepare-test-mount.sh --stop
|
||||
|
||||
# Stop and clear the mount for a clean next run
|
||||
./scripts/prepare-test-mount.sh --tidy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### scripts/dnsmasq-status.sh
|
||||
|
||||
**Purpose:** Simulates `systemctl status dnsmasq`-style output when run inside a container that has no systemd. Used by the test harness (and any similar setup) so the Dnsmasq page can show a status block. Uses `pgrep`/`ps` to show dnsmasq process info. No arguments.
|
||||
|
||||
**Usage:** Invoked by the app when `Dnsmasq__StatusShowCommand` points at this script (e.g. in `docker-compose.test.yml`).
|
||||
|
||||
---
|
||||
|
||||
### scripts/entrypoint.sh
|
||||
|
||||
**Purpose:** Container entrypoint when the app and dnsmasq run in the same container. If `DNSMASQ_CONF` is set, starts dnsmasq in the background with that config file; then exec’s the app (`dotnet DnsmasqWebUI.dll`). Used by this repo’s Dockerfile and by the test harness.
|
||||
|
||||
**Usage:** Set `DNSMASQ_CONF` to the path of the dnsmasq config file inside the container (e.g. `/data/dnsmasq-test.conf`). No CLI arguments.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: TERMINAL WITH PREPARE-TEST-MOUNT.SH USAGE OR DOCKER COMPOSE UP OUTPUT -->
|
||||
|
||||
---
|
||||
|
||||
## Project structure and codebase overview
|
||||
|
||||
**Top-level:**
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `.github/workflows/` | GitHub Actions (e.g. release workflow: build on tag push, attach assets). |
|
||||
| `scripts/` | Install, publish, test harness, entrypoint, and helper scripts. |
|
||||
| `src/DnsmasqWebUI/` | Main ASP.NET Core Blazor app. |
|
||||
| `src/DnsmasqWebUI.Tests/` | Unit tests. |
|
||||
| `testdata/` | Fixtures and config for the Docker test harness. |
|
||||
| `Dockerfile` | Multi-stage build: app + dnsmasq in one image. |
|
||||
| `docker-compose.test.yml` | Test harness: app + dnsmasq + DHCP clients. |
|
||||
| `DnsmasqWebUI.sln` | Solution file. |
|
||||
|
||||
**Codebase overview:**
|
||||
|
||||
- **Entry and config:** `Program.cs` wires the host; configuration (e.g. `Dnsmasq__*`) is in `Configuration/` (`DnsmasqOptions`, `ApplicationOptions`). Options are validated at startup.
|
||||
- **Config and hosts:** Services in `Services/` and `Services/Abstractions/` read and write dnsmasq config and hosts. A **managed** config file and optional managed hosts file are written by the app; the main config must include the managed file (e.g. via a final `conf-file=` line). Caches (`ConfigSetCache`, `HostsCache`) keep in-memory snapshots and refresh on file changes or staleness.
|
||||
- **Parsers and models:** `Parsers/` parses dnsmasq config lines and files (`DnsmasqConfIncludeParser`, `DnsmasqConfDirectiveParser`, etc.). Models in `Models/` represent config sets, config, sources, DHCP entries, hosts, leases.
|
||||
- **API and UI:** `Controllers/` expose API endpoints; `Components/` contains Blazor Server components for config editor, hosts, DHCP, Dnsmasq status, and app settings. Static assets in `wwwroot/`.
|
||||
- **Tests:** `DnsmasqWebUI.Tests` contains unit tests for parsers, config set service, config, and related logic. Run with `dotnet test`.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: SOLUTION EXPLORER OR FOLDER TREE OF SRC/ -->
|
||||
|
||||
---
|
||||
|
||||
## Development and test harness
|
||||
|
||||
**Prerequisites:** .NET 9 SDK. Docker (and Docker Compose) for the test harness.
|
||||
|
||||
**Build and run locally:**
|
||||
|
||||
```bash
|
||||
cd src/DnsmasqWebUI
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Use `appsettings.json` or launch settings to set `Dnsmasq__MainConfigPath` and other options for your environment.
|
||||
|
||||
**Test harness:** Run the app and dnsmasq (plus DHCP clients) in Docker against a mounted testdata directory so you can develop and test without touching system dnsmasq.
|
||||
|
||||
1. From the repo root, run:
|
||||
```bash
|
||||
./scripts/prepare-test-mount.sh
|
||||
```
|
||||
This clears the mount dir (by default `testdata-mount`), syncs `testdata/` into it, and starts the stack with `docker-compose.test.yml`. Use `--prepare-only` to only prepare the mount, then start compose manually; use `--build` after changing the app or Dockerfile.
|
||||
|
||||
2. Open the UI (e.g. http://localhost:8080). The app and dnsmasq use the mounted config; you can edit config/hosts in the UI and trigger reload.
|
||||
|
||||
3. Stop: `./scripts/prepare-test-mount.sh --stop`. Clean mount for next run: `./scripts/prepare-test-mount.sh --tidy`.
|
||||
|
||||
**Unit tests:**
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
|
||||
Run from the repo root or the solution path. Tests do not require Docker.
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: BROWSER SHOWING UI AGAINST TEST HARNESS -->
|
||||
|
||||
<!-- SUGGESTED SCREENSHOT: TERMINAL WITH PREPARE-TEST-MOUNT.SH AND DOCKER COMPOSE UP -->
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for the full text.
|
||||
|
||||
**dnsmasq** (the DNS/DHCP server this UI manages) is licensed under the **GNU GPL v2 or v3** (see [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html)); this project is independent and does not include dnsmasq code.
|
||||
|
||||
To add or change the repository license (e.g. with GitHub CLI): `gh repo license view MIT > LICENSE` (or use the GitHub web UI: Add file → Create new file → Choose a license template).
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This codebase was developed with the help of AI-assisted tooling (including LLM-based development tools). It has been reviewed and is maintained by humans. Use it at your own risk; no warranty is provided. See the [LICENSE](LICENSE) for terms.
|
||||
+171
-24
@@ -1,46 +1,66 @@
|
||||
#!/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.
|
||||
# curl -sSL https://raw.githubusercontent.com/alexhopeoconnor/dnsmasq-webui/main/scripts/install.sh | sh
|
||||
# When run from a clone, repo is detected from git remote origin.
|
||||
set -e
|
||||
|
||||
GITHUB_REPO="${GITHUB_REPO:-}"
|
||||
REPO_DEFAULT="${REPO_DEFAULT:-alexhopeoconnor/dnsmasq-webui}"
|
||||
|
||||
VERSION=""
|
||||
LIST=false
|
||||
INSTALL_DIR=""
|
||||
SYSTEM_INSTALL=false
|
||||
SERVICE=false
|
||||
UPDATE=false
|
||||
|
||||
# Default install dir (user-writable, no sudo). Overridden by --dir or --system.
|
||||
default_install_dir() {
|
||||
echo "${HOME}/.local/share/dnsmasq-webui"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Download and install dnsmasq-webui from a GitHub release for this machine's OS/arch."
|
||||
echo "Supports install, update, and switch-release (re-run with same --dir and different --version)."
|
||||
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 " --repo OWNER/REPO GitHub owner/repo. Or set GITHUB_REPO. Or set REPO_DEFAULT in the script."
|
||||
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 " --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 " -h, -?, --help Show this help."
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo " --dir DIR Install into DIR (default: ./dnsmasq-webui in current directory)."
|
||||
echo "Install location:"
|
||||
echo " (default) Install to ~/.local/share/dnsmasq-webui; create ~/.local/bin/dnsmasq-webui symlink if possible."
|
||||
echo " --dir DIR Install into DIR instead."
|
||||
echo " --system Install to /opt/dnsmasq-webui and symlink /usr/local/bin/dnsmasq-webui. Requires root (run with sudo)."
|
||||
echo ""
|
||||
echo "Service (systemd):"
|
||||
echo " --service Install a systemd unit so the app can run as a service (start on boot)."
|
||||
echo " With --system: installs system unit (requires root). Without: installs user unit (no sudo)."
|
||||
echo " Only supported on systemd-based systems; errors if systemd is not available."
|
||||
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 " $0 # Install latest (clone: auto repo; curl: use REPO_DEFAULT or --repo)"
|
||||
echo " $0 --update # Reinstall latest to default dir (upgrade)"
|
||||
echo " $0 --version v1.0.0 # Install v1.0.0 to default dir"
|
||||
echo " $0 --dir /opt/dnsmasq-webui # Install to custom dir"
|
||||
echo " sudo $0 --system # System-wide install to /opt, runnable as dnsmasq-webui"
|
||||
echo " $0 --service # Install + user systemd service (starts when you log in)"
|
||||
echo " sudo $0 --system --service # Install + system systemd service (starts at boot)"
|
||||
echo " $0 --list # List releases"
|
||||
echo ""
|
||||
echo "After install, configure via appsettings.json or Dnsmasq__* environment variables, then run the binary."
|
||||
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."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Detect owner/repo from git remote if we're in a clone.
|
||||
# Detect owner/repo from git remote, REPO_DEFAULT, or require --repo/GITHUB_REPO.
|
||||
detect_repo() {
|
||||
if [ -n "$GITHUB_REPO" ]; then
|
||||
return
|
||||
@@ -48,14 +68,17 @@ detect_repo() {
|
||||
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
|
||||
if [ -n "$REPO_DEFAULT" ]; then
|
||||
GITHUB_REPO="$REPO_DEFAULT"
|
||||
return
|
||||
fi
|
||||
echo "Error: GitHub repo not set. Use --repo owner/repo, set GITHUB_REPO, or run from a clone. For the one-liner, set REPO_DEFAULT in the script." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -149,10 +172,32 @@ find_asset_url() {
|
||||
' | head -n1
|
||||
}
|
||||
|
||||
# Main install: fetch release, find asset for RID, download, extract.
|
||||
# Main install: fetch release, find asset for RID, download, extract, optionally symlink.
|
||||
do_install() {
|
||||
check_jq
|
||||
detect_repo
|
||||
|
||||
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" >&2
|
||||
exit 1
|
||||
fi
|
||||
INSTALL_DIR="/opt/dnsmasq-webui"
|
||||
elif [ -z "$INSTALL_DIR" ]; then
|
||||
INSTALL_DIR="$(default_install_dir)"
|
||||
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. Run with sudo: sudo $0 --system --service" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
echo "Error: systemd not found (no systemctl). --service is only supported on systemd-based systems." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rid="$(detect_rid)"
|
||||
echo "Detected RID: $rid"
|
||||
echo "Fetching release..."
|
||||
@@ -166,9 +211,7 @@ do_install() {
|
||||
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 ..."
|
||||
@@ -176,12 +219,99 @@ do_install() {
|
||||
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"
|
||||
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
ln -sf "$INSTALL_DIR/DnsmasqWebUI" /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)"
|
||||
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
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo "Installed to $INSTALL_DIR"
|
||||
echo "Run: $INSTALL_DIR/DnsmasqWebUI"
|
||||
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
|
||||
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)."
|
||||
}
|
||||
|
||||
# Install systemd unit. Call only when SERVICE=true and systemctl exists.
|
||||
# $1 = INSTALL_DIR (where DnsmasqWebUI binary and appsettings.json live)
|
||||
install_systemd_unit() {
|
||||
local dir bin
|
||||
dir="$1"
|
||||
bin="$dir/DnsmasqWebUI"
|
||||
if [ ! -f "$bin" ]; then
|
||||
echo "Warning: Binary $bin not found; skipping systemd unit install." >&2
|
||||
return 0
|
||||
fi
|
||||
if [ "$SYSTEM_INSTALL" = true ]; then
|
||||
cat > /etc/systemd/system/dnsmasq-webui.service << EOF
|
||||
[Unit]
|
||||
Description=dnsmasq-webui - Web UI for dnsmasq
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
WorkingDirectory=$dir
|
||||
ExecStart=$bin
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable dnsmasq-webui.service
|
||||
echo ""
|
||||
echo "System service installed: /etc/systemd/system/dnsmasq-webui.service"
|
||||
echo " sudo systemctl start dnsmasq-webui # start now"
|
||||
echo " sudo systemctl enable dnsmasq-webui # already enabled for boot"
|
||||
echo " sudo systemctl status dnsmasq-webui # check status"
|
||||
else
|
||||
mkdir -p "${HOME:-}/.config/systemd/user"
|
||||
cat > "${HOME:-}/.config/systemd/user/dnsmasq-webui.service" << EOF
|
||||
[Unit]
|
||||
Description=dnsmasq-webui - Web UI for dnsmasq
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
WorkingDirectory=$dir
|
||||
ExecStart=$bin
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable dnsmasq-webui.service
|
||||
echo ""
|
||||
echo "User service installed: ~/.config/systemd/user/dnsmasq-webui.service"
|
||||
echo " systemctl --user start dnsmasq-webui # start now"
|
||||
echo " systemctl --user enable dnsmasq-webui # already enabled (starts when you log in)"
|
||||
echo " systemctl --user status dnsmasq-webui # check status"
|
||||
echo " To run at boot without login: loginctl enable-linger"
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse args
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -204,12 +334,24 @@ while [ $# -gt 0 ]; do
|
||||
VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
--update)
|
||||
UPDATE=true
|
||||
shift
|
||||
;;
|
||||
--dir)
|
||||
shift
|
||||
[ $# -gt 0 ] || { echo "Error: --dir requires DIR" >&2; exit 1; }
|
||||
INSTALL_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
--system)
|
||||
SYSTEM_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--service)
|
||||
SERVICE=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Error: unknown option $1" >&2
|
||||
usage >&2
|
||||
@@ -227,4 +369,9 @@ if [ "$LIST" = true ]; then
|
||||
list_releases
|
||||
fi
|
||||
|
||||
if [ "$UPDATE" = true ]; then
|
||||
INSTALL_DIR="$(default_install_dir)"
|
||||
VERSION="latest"
|
||||
fi
|
||||
|
||||
do_install
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["DnsmasqWebUI.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" href="logo.png" />
|
||||
<title>@_appTitle</title>
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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">
|
||||
<i class="bi bi-globe brand-icon" aria-hidden="true"></i>
|
||||
<img src="logo.png" alt="" class="brand-logo" />
|
||||
<span>@AppTitle</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -31,12 +31,12 @@
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
display: inline-flex;
|
||||
.brand-logo {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
font-size: 1.5rem;
|
||||
vertical-align: -0.125em;
|
||||
height: 1.75rem;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Bootstrap Icons in nav: white, sized to match link text */
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.4 MiB |
Reference in New Issue
Block a user