wireguard-ui/README.md

843 lines
74 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# wireguard-ui
## Contents
Quick links to the main documentation sections:
- [WireGuard UI v2](#wireguard-ui-v2) — what changed in this fork and how to switch language
- [Features](#features) — classic and v2 capabilities
- [HTTP API reference](#http-api-reference) — JSON routes, auth, and mobile-related endpoints
- [Run WireGuard-UI](#run-wireguard-ui) — binary and Docker Compose
- [Environment variables](#environment-variables) — configuration reference
- [Firebase Cloud Messaging (FCM)](#firebase-cloud-messaging-fcm) — push setup and registration API
- [Session idle timeout](#session-idle-timeout-settings--session--security) — minutes-based idle logout
- [Auto restart WireGuard daemon](#auto-restart-wireguard-daemon) — systemd / OpenRC / Docker
- [Build](#build) — assets, Docker image, binary
- [License](#license)
## WireGuard UI v2
This repository ships **version 2** of the WireGuard UI: an updated shell-style layout, richer monitoring and administration pages, Passkeys (WebAuthn) support, bilingual UI (English / Spanish via `locale/en.json` and `locale/es.json`), and extended optional OS integration (sysctl, `wg-quick` / `wg syncconf`, log tail) while keeping the same core purpose as the upstream project—manage peers, generate configs, and distribute them by QR, file, email, or Telegram.
**Notes**
- **Building from source**: run `./prepare_assets.sh` before `go build` when templates or static assets change (see **Build** below).
- **Changing UI language**: set **Language** under **Global settings**, save, click **Apply config** in the toolbar, then **reload the page** so server-rendered templates and client-side `WG_T` strings refresh.
---
A web user interface to manage your WireGuard setup.
## Features
### Classic capabilities (upstream parity)
- Web UI to manage WireGuard peers (create, edit, enable/disable, remove).
- Richer client records (name, email, notes, subnet ranges, Telegram user id, etc.).
- Distribute configs via **QR code**, **download**, **email**, or **Telegram**.
- Global defaults (endpoint, DNS, MTU, keepalive, config path) and server interface editor.
- Optional **Apply config** workflow to write `wg.conf` and optionally reload the kernel (`wg-quick` / `wg syncconf` when enabled).
### New in v2
- **Shell layout**: fixed sidebar + main content (`wgshell.css`), mobile-friendly nav, unified top bar with **Apply config** / pending-change handling.
- **Dashboard**: at-a-glance server/client KPIs, WireGuard presence, and actions that match live data (`/api/dashboard-stats`, restart helpers where configured).
- **Traffic**: bandwidth view backed by cached WireGuard counter samples (`/api/wg-traffic-series`), range presets, peer-aware charts.
- **Logs**: live sections when enabled (global "Logs" toggle)—optional file tail (`WGUI_LOG_TAIL_PATH`), `systemctl` / `journalctl` snippets for `wg-quick@...`, periodic refresh from `/api/system-logs`.
- **Status**: read-only peer table from `wgctrl` for quick inspection.
- **Global settings (expanded)**: configurable **session idle timeout** (minutes), **Passkeys** master toggle, **UI theme** (dark / light / auto), **UI language** (English / Spanish), **realtime stats** gate for Logs/Dashboard polling; staged save + apply flow with localStorage dirty tracking.
- **Internationalization**: strings in `locale/en.json` and `locale/es.json`; templates use `tr` / client bundle `WG_T` + `wgT()` for JS toasts and dynamic UI.
- **Multi-user auth**: **Users** admin page—create/edit/delete users, admin role, suspend account, revoke all sessions, inline Passkey add/remove/rename per user.
- **My account / Profile**: self-service display name, email, password change, own Passkeys.
- **Passkeys (WebAuthn)**: passwordless sign-in and registration flows; env knobs for RP ID / origins behind reverse proxies (`WGUI_WEBAUTHN_`*). Optional `**WGUI_ANDROID_PASSKEY_`*** + reverse-proxy wiring for `**/.well-known/assetlinks.json`** unlock the Flutter Android client's Credential Manager flows.
- **Server page extras** (Linux, when allowed): optional **IPv4 forwarding** via `sysctl`, **persist** / **auto-apply** preferences, `**wg-quick` down/up/restart** and `**wg syncconf`** after apply, optional **systemd**-based restarts.
- **Wake-on-LAN**: manage hosts and send magic packets from the UI.
- **Client list UX**: card layout with inline enable toggle, traffic chips fed by `**/api/wg-peer-stats`**, and "Apply config" integration after edits.
WireGuard UI v2
## HTTP API reference
Unless noted otherwise, paths are rooted at your configured `**BASE_PATH`** (empty at the site root, or e.g. `/wireguard`). Prefix every path below with that value.
**Sessions.** Most endpoints require a valid browser session cookie (`ValidSession`). A few JSON `POST` routes are public for Passkey login. JSON bodies expect `**Content-Type: application/json`** (this also mitigates simple CSRF from third-party sites).
**Admin-only** routes additionally require an administrator account (`NeedsAdmin` in the code).
### Well-known and health
| Method | Path | Auth | Purpose |
| ------------- | ------------------------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `GET`, `HEAD` | `/.well-known/assetlinks.json` | No | Digital Asset Links for Android Passkeys / Credential Manager (also mirrored under `BASE_PATH` when set; see comments in `main.go`). |
| `GET` | `{BASE}/_health` | No | Liveness probe. |
### Public and login
When login is **not** disabled:
| Method | Path | Auth | Purpose |
| ------ | ----------------------------------- | ---- | ---------------------------------------------- |
| `GET` | `{BASE}/login` | No | Login HTML. |
| `POST` | `{BASE}/login` | No | Password login (JSON). |
| `GET` | `{BASE}/api/public/login-wg-status` | No | WireGuard tunnel summary for the login banner. |
| `POST` | `{BASE}/api/passkeys/login/begin` | No | WebAuthn assertion options (JSON). |
| `POST` | `{BASE}/api/passkeys/login/finish` | No | Complete Passkey login (JSON). |
### Passkeys (authenticated)
| Method | Path | Notes |
| ------ | ----------------------------------------------- | ---------------------------------- |
| `POST` | `{BASE}/api/passkeys/register/:username/begin` | Start registration for `username`. |
| `POST` | `{BASE}/api/passkeys/register/:username/finish` | Finish registration. |
| `POST` | `{BASE}/api/passkeys/remove` | Remove a credential. |
| `POST` | `{BASE}/api/passkeys/rename` | Rename a credential. |
### Users and profile
| Method | Path | Notes |
| ------ | --------------------------------- | ------------------------------ |
| `POST` | `{BASE}/update-user` | Update profile fields. |
| `POST` | `{BASE}/create-user` | **Admin.** Create user. |
| `POST` | `{BASE}/remove-user` | **Admin.** Remove user. |
| `GET` | `{BASE}/get-users` | **Admin.** List users. |
| `GET` | `{BASE}/api/user/:username` | Fetch one user. |
| `GET` | `{BASE}/api/profile/passkeys` | Passkeys for the current user. |
| `POST` | `{BASE}/api/user/set-admin` | **Admin.** |
| `POST` | `{BASE}/api/user/set-disabled` | **Admin.** |
| `POST` | `{BASE}/api/user/revoke-sessions` | **Admin.** |
### Peers (clients)
| Method | Path | Notes |
| ------ | ----------------------------- | ---------------------------- |
| `GET` | `{BASE}/api/clients` | List all clients. |
| `GET` | `{BASE}/api/client/:id` | One client by numeric `id`. |
| `POST` | `{BASE}/new-client` | Create client. |
| `POST` | `{BASE}/update-client` | Update client. |
| `POST` | `{BASE}/remove-client` | Delete client. |
| `POST` | `{BASE}/client/set-status` | Enable/disable. |
| `POST` | `{BASE}/email-client` | Email configuration. |
| `POST` | `{BASE}/send-telegram-client` | Telegram delivery. |
| `GET` | `{BASE}/download` | Download one `.conf`. |
| `GET` | `{BASE}/download-all-configs` | **Admin.** ZIP of all peers. |
### Dashboard, stats, and traffic
| Method | Path | Notes |
| ------ | ------------------------------- | ------------------------------------------------------------------------------------ |
| `GET` | `{BASE}/api/dashboard-stats` | KPIs for the dashboard. |
| `GET` | `{BASE}/api/wg-peer-stats` | Per-peer counters for lists. |
| `GET` | `{BASE}/api/wg-traffic-series` | Cached series; query `range=24h` (default), `7d`, or `30d`. |
| `GET` | `{BASE}/api/machine-ips` | Suggested endpoint IPs. |
| `GET` | `{BASE}/api/subnet-ranges` | Ordered subnet ranges. |
| `GET` | `{BASE}/api/suggest-client-ips` | IP allocation hints. |
| `GET` | `{BASE}/api/ui-nav-hints` | Small JSON payload; useful as a **connectivity / session check** for native clients. |
### WireGuard server, apply, and tunnel control
| Method | Path | Notes |
| ------ | ------------------------------------ | -------------------------------------------- |
| `POST` | `{BASE}/wg-server/interfaces` | **Admin.** Save interface list from UI flow. |
| `POST` | `{BASE}/api/wg-server/save-page` | **Admin.** Combined “Server” tab JSON save. |
| `POST` | `{BASE}/wg-server/keypair` | **Admin.** Generate server keypair. |
| `POST` | `{BASE}/api/apply-wg-config` | Write `wg.conf` / apply workflow (JSON). |
| `GET` | `{BASE}/api/wireguard/tunnel-status` | Tunnel up/down and interface summary. |
| `POST` | `{BASE}/api/wireguard/wg-quick-down` | **Admin.** `wg-quick` down. |
| `POST` | `{BASE}/api/wireguard/wg-quick-up` | **Admin.** `wg-quick` up. |
### Push notifications (FCM)
| Method | Path | Body (JSON) |
| ------ | ---------------------------- | ----------------------------------------------------------- |
| `POST` | `{BASE}/api/push/register` | `{"token":"<FCM registration token>","platform":"android"}` |
| `POST` | `{BASE}/api/push/unregister` | `{"token":"<FCM registration token>"}` |
Requires a valid session. See [Firebase Cloud Messaging (FCM)](#firebase-cloud-messaging-fcm) for server env vars.
### Global settings and logs
| Method | Path | Notes |
| ------ | ------------------------------------------- | --------------------------------------- |
| `POST` | `{BASE}/global-settings` | **Admin.** Save global settings (JSON). |
| `POST` | `{BASE}/api/global-settings/realtime-stats` | **Admin.** Toggle realtime stats. |
| `GET` | `{BASE}/api/system-logs` | Log tail / snippets when enabled. |
### Wake-on-LAN
| Method | Path |
| -------- | -------------------------------------- |
| `POST` | `{BASE}/wake_on_lan_host` |
| `DELETE` | `{BASE}/wake_on_lan_host/:mac_address` |
| `PUT` | `{BASE}/wake_on_lan_host/:mac_address` |
### HTML pages (session)
These return HTML for the v2 shell, not JSON: `{BASE}/` (clients), `{BASE}/dashboard`, `{BASE}/traffic`, `{BASE}/logs`, `{BASE}/profile`, `{BASE}/users-settings` (**admin**), `{BASE}/wg-server` (**admin**), `{BASE}/global-settings` (**admin**), `{BASE}/status`, `{BASE}/wake_on_lan_hosts`, `{BASE}/about`. Use the JSON routes above for API integrations.
### Logout and misc
| Method | Path | Notes |
| ------ | ------------------ | ------------------------------------- |
| `GET` | `{BASE}/logout` | Ends session when login is enabled. |
| `GET` | `{BASE}/test-hash` | Internal/config hash probe (session). |
| `GET` | `{BASE}/favicon` | Favicon bytes. |
---
## Run WireGuard-UI
> The default username and password are `admin`. Please change it to secure your setup.
### Using binary file
Download the binary file from the release page and run it directly on the host machine
```
./wireguard-ui
```
For a **persistent** install on Linux, register **systemd** as described in **[systemd: install and enable the web service](#systemd-install-and-enable-the-web-service)** below (working directory, database, environment files, permissions).
### Using docker compose
The [examples/docker-compose](examples/docker-compose) folder contains example docker-compose files.
Choose the example which fits you the most, adjust the configuration for your needs, then run it like below:
```
docker-compose up
```
## Environment Variables
| Variable | Description | Default |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `BASE_PATH` | Set this variable if you run wireguard-ui under a subpath of your reverse proxy virtual host (e.g. /wireguard) | N/A |
| `BIND_ADDRESS` | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket. | 0.0.0.0:80 |
| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | N/A |
| `SESSION_SECRET_FILE` | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect | N/A |
| `SESSION_MAX_DURATION` | Max time in days a remembered session is refreshed and valid. Non-refreshed session is valid for 7 days max, regardless of this setting. | 90 |
| `SUBNET_RANGES` | The list of address subdivision ranges. Format: `SR Name:10.0.1.0/24; SR2:10.0.2.0/24,10.0.3.0/24` Each CIDR must be inside one of the server interfaces. | N/A |
| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` |
| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` |
| `WGUI_PASSWORD_FILE` | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect | N/A |
| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A |
| `WGUI_PASSWORD_HASH_FILE` | Optional filepath for the user login password hash. (alternative to `WGUI_PASSWORD_FILE`). Used for db initialization only. Leave `WGUI_PASSWORD_HASH` blank to take effect | N/A |
| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to. The endpoint can contain a port as well, useful when you are listening internally on the `WGUI_SERVER_LISTEN_PORT` port, but you forward on another port (ex 9000). Ex: myvpn.dyndns.com:9000 | Resolved to your public ip address |
| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo |
| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` |
| `WGUI_MTU` | The default MTU used in global settings | `1450` |
| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` |
| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) |
| `WGUI_TABLE` | The default WireGuard table value settings | `auto` |
| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` |
| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` |
| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A |
| `EMAIL_FROM_ADDRESS` | The sender email address | N/A |
| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` |
| `SENDGRID_API_KEY` | The SendGrid api key | N/A |
| `SENDGRID_API_KEY_FILE` | Optional filepath for the SendGrid api key. Leave `SENDGRID_API_KEY` blank to take effect | N/A |
| `SMTP_HOSTNAME` | The SMTP IP address or hostname | `127.0.0.1` |
| `SMTP_PORT` | The SMTP port | `25` |
| `SMTP_USERNAME` | The SMTP username | N/A |
| `SMTP_PASSWORD` | The SMTP user password | N/A |
| `SMTP_PASSWORD_FILE` | Optional filepath for the SMTP user password. Leave `SMTP_PASSWORD` blank to take effect | N/A |
| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` |
| `SMTP_ENCRYPTION` | The encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` |
| `SMTP_HELO` | Hostname to use for the HELO message. smtp-relay.gmail.com needs this set to anything but `localhost` | `localhost` |
| `TELEGRAM_TOKEN` | Telegram bot token for distributing configs to clients | N/A |
| `TELEGRAM_ALLOW_CONF_REQUEST` | Allow users to get configs from the bot by sending a message | `false` |
| `TELEGRAM_FLOOD_WAIT` | Time in minutes before the next conf request is processed | `60` |
| `FCM_CREDENTIALS_FILE` | Absolute path to the Firebase **service account** JSON used to send **FCM push** notifications (Android app tokens). If empty, `GOOGLE_APPLICATION_CREDENTIALS` is used instead. If neither resolves to a readable file, push is disabled. **Not** the same file as the app's `google-services.json`. | N/A |
| `GOOGLE_APPLICATION_CREDENTIALS` | Standard Google env: path to the **same** service account JSON as above. Used when `FCM_CREDENTIALS_FILE` is unset. | N/A |
### Firebase Cloud Messaging (FCM)
The server can send **push notifications** (e.g. peer created/removed/enabled/disabled, tunnel up/down) to devices that register an FCM token. Implementation lives in the `pushnotify` package. **FCM itself has no per-message charge** in typical Firebase usage; you still need a Firebase project and a service account for the Admin SDK.
#### Server setup
1. Open **[Firebase Console](https://console.firebase.google.com/)** → your project → **Project settings** (gear) → **Service accounts**.
2. Click **Generate new private key** and download the JSON. Store it on the server only (for example `/etc/wireguard-ui/firebase-adminsdk.json`). **Do not commit this file.** Restrict permissions (`chmod 600`, owned by the service user).
3. Point wireguard-ui at that file using `**FCM_CREDENTIALS_FILE`** (recommended) or `**GOOGLE_APPLICATION_CREDENTIALS`** (same path; used if `FCM_CREDENTIALS_FILE` is empty).
4. Restart wireguard-ui. You should see a log line such as `**FCM enabled`**. If credentials are missing or invalid, push stays off and an error is logged.
#### Project ID
The Firebase Go SDK needs a **Google Cloud / Firebase project ID**. It is normally taken from the `**project_id`** field inside the service account JSON. If you see `**project ID is required`** (or similar) in logs, set one of:
| Variable | Purpose |
| ---------------------- | -------------------------------------------------------------------- |
| `FIREBASE_PROJECT_ID` | Explicit Firebase/GCP project ID (highest precedence in app config). |
| `GOOGLE_CLOUD_PROJECT` | Same intent; common on GCP VMs. |
| `GCLOUD_PROJECT` | Same intent; legacy/alternate env name. |
Ensure the JSON file is the **service account key** from Firebase (it always contains `"type": "service_account"` and `"project_id"`).
#### Not the Android client file
- `**google-services.json`** is only for the **Android app** (Flutter `android/app/`). The **wireguard-ui server does not read it.** Server-side sending uses only the **service account** JSON from step 2.
#### Registration HTTP API
Authenticated JSON endpoints (same session cookies as the rest of the UI):
| Method | Path | Body |
| ------ | --------------------------------- | ----------------------------------------------------------- |
| `POST` | `{BASE_PATH}/api/push/register` | `{"token":"<FCM registration token>","platform":"android"}` |
| `POST` | `{BASE_PATH}/api/push/unregister` | `{"token":"<FCM registration token>"}` |
Registered tokens are persisted under the server DB directory (e.g. `**push_tokens.json`** next to other JSON store files).
#### Rate limiting
Outbound FCM sends are **rate-limited per device token** (for example **at most 3 notifications per minute per token**) to avoid flooding.
#### Flutter Android client
Configure Firebase for the app package name, place `**google-services.json`** in `**android/app/`**, and enable push in the app; see the companion repo `**wireguard-ui-android-client`** README.
### Session idle timeout (**Settings** → **Session & security**)
In the UI, **Session idle timeout (minutes)** is stored as `session_timeout_minutes` (integer). **Always use whole minutes—not seconds.**
| Item | Detail |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Unit** | **Minutes**, range **5-1440** (about 24 h max). Example: enter `30` for ~30 minutes. |
| **Behavior** | **Idle logout:** after no authenticated HTTP request for longer than this time, the session is invalid (each request resets the idle clock). Applies to browsing and API endpoints that enforce `ValidSession`. |
| **When it applies** | After saving from **Settings** and confirming **Apply config**, new sessions use this value when users **log in again**. Log out or wait for expiry to observe the change immediately. |
| **Remember-me** | If a finite timeout is set in global settings, the login checkbox no longer lengthens the session to 7 days. |
| `**SESSION_MAX_DURATION`** | Separate hard cap on how long any session identity may persist (days from login), independent of idle timeout. See the env table above. |
### Defaults for server configuration
These environment variables are used to control the default server settings used when initializing the database.
| Variable | Description | Default |
| --------------------------------- | --------------------------------------------------------------------------------------------- | --------------- |
| `WGUI_SERVER_INTERFACE_ADDRESSES` | The default interface addresses (comma-separated-list) for the WireGuard server configuration | `10.252.1.0/24` |
| `WGUI_SERVER_LISTEN_PORT` | The default server listen port | `51820` |
| `WGUI_SERVER_POST_UP_SCRIPT` | The default server post-up script | N/A |
| `WGUI_SERVER_POST_DOWN_SCRIPT` | The default server post-down script | N/A |
### Defaults for new clients
These environment variables are used to set the defaults used in `New Client` dialog.
| Variable | Description | Default |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------- |
| `WGUI_DEFAULT_CLIENT_ALLOWED_IPS` | Comma-separated-list of CIDRs for the `Allowed IPs` field. (default ) | `0.0.0.0/0` |
| `WGUI_DEFAULT_CLIENT_EXTRA_ALLOWED_IPS` | Comma-separated-list of CIDRs for the `Extra Allowed IPs` field. (default empty) | N/A |
| `WGUI_DEFAULT_CLIENT_USE_SERVER_DNS` | Boolean value [`0`, `f`, `F`, `false`, `False`, `FALSE`, `1`, `t`, `T`, `true`, `True`, `TRUE`] | `true` |
| `WGUI_DEFAULT_CLIENT_ENABLE_AFTER_CREATION` | Boolean value [`0`, `f`, `F`, `false`, `False`, `FALSE`, `1`, `t`, `T`, `true`, `True`, `TRUE`] | `true` |
### Docker only
These environment variables only apply to the docker container.
| Variable | Description | Default |
| --------------------- | ------------------------------------------------------------- | ------- |
| `WGUI_MANAGE_START` | Start/stop WireGuard when the container is started/stopped | `false` |
| `WGUI_MANAGE_RESTART` | Auto restart WireGuard when we Apply Config changes in the UI | `false` |
### Server UI (optional OS integration)
Gate optional privileged actions invoked from the **Server** page (binary or Docker—the process must run on Linux with adequate permissions where needed):
| Variable | Description | Default |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WGUI_ALLOW_SYSCTL_IP_FORWARD` | When `true`, saving with **IPv4 forwarding (ip_forward)** may run `sysctl -w net.ipv4.ip_forward=1` / `...=0` on Linux. Without it, only the preference is stored in the database. Ignored outside Linux. | `false` |
| `WGUI_WG_SYNCCONF_AFTER_APPLY` | When `true`, **Apply config** runs `**wg-quick strip | wg syncconf `** on Linux so the running WireGuard matches the written file (e.g. disabling a client removes its peer from the server without` wg-quick down/up`). Requires` wg`and`wg-quick`on`$PATH`. If unset or` false`, Apply only writes the file/hash and does not reload kernel state. |
| `WGUI_ALLOW_WG_QUICK` | When `true`, **Apply** can run `wg-quick` down/up and the **Server** page shows **Stop** / **Start** / **Restart**. If unset, wg-quick controls are **off**. Start with `WGUI_ALLOW_WG_QUICK=true` when you intend to restart the tunnel from the UI. Env values are trimmed before parsing. | `false` |
| `WGUI_WG_RESTART_VIA_SYSTEMD` | On Linux, **Apply** prefers `systemctl restart wg-quick@ifac` when that unit exists (`LoadState=loaded`), so `**journalctl -u wg-quick@wg0`** shows restarts like a manual systemd restart. If `false` or no systemd, uses `wg-quick down`/`up`. | `true` |
| `WGUI_WGCONF_PENDING_WHEN_TUNNEL_STOPPED` | Linux: when Apply does **not** restart WireGuard while the netdev is absent/down (e.g. after **Stop**), the UI writes a side file next to `wg.conf` (suffix `.wgui-pending`) instead of overwriting the live `**WGUI_CONFIG_FILE_PATH`**. That avoids systemd `**.path`** units watching `wg.conf` that restart `wg-quick` on every save. `**wg-quick up**` or **Server Start** merges the pending file into `wg.conf` first. Set `false` to always write `wg.conf` directly (legacy). | `true` |
| `WGUI_LOG_TAIL_PATH` | Optional absolute path to a log file shown in the **Logs** page. This variable is read-only: wireguard-ui does not write this file automatically. | *(unset)* |
| `WGUI_WEBAUTHN_RP_ID` | Optional fixed WebAuthn RP ID (recommended behind reverse proxy/public domain). If unset, it is inferred from request host. | *(auto)* |
| `WGUI_WEBAUTHN_RP_ORIGINS` | Optional comma-separated allowed origins for Passkeys (example: `https://vpn.example.com,https://admin.example.com`). If unset, origin is inferred per request. | *(auto)* |
| `WGUI_WEBAUTHN_RP_DISPLAY_NAME` | Optional WebAuthn RP display name shown by authenticators. | `WireGuard UI` |
| `WGUI_ANDROID_PASSKEY_SHA256` | One or more SHA-256 **signing-certificate fingerprints** of the Flutter/Android app (`./gradlew signingReport`), hex with or without colons, comma-separated. You may set this variable to an **absolute path** of a regular file (e.g. `/etc/wireguard-ui/android-SHA.secret`); the server reads the file contents as the same fingerprint string. The **wireguard-ui process user** must be able to read that file (e.g. `chgrp wireguard-ui` + `chmod 640`, or root-only if the service runs as root). Powers `**/.well-known/assetlinks.json`** (Digital Asset Links) and derives matching `**android:apk-key-hash:`** WebAuthn origins for native Android assertions. Unset ⇒ assetlinks endpoint returns 404 / no Android APK origins appended. | |
| `WGUI_ANDROID_PASSKEY_PACKAGE` | Android `applicationId` embedded in `**assetlinks.json`**. Omit to use `**com.wireguardui.wireguard_ui_client`**. | |
#### Troubleshooting: `wg-quick up` fails on `ip -6 route` / «Cannot find device wg0»
After toggling peers and **Start**, a failed half-bridge can leave routing in an odd state; the UI now runs `**wg-quick down`** (ignored if already down), waits briefly, then `**wg-quick up`**, and retries once if the first `up` still errors. If it persists, exclude `**wg0`** from **NetworkManager** / **systemd-networkd**, and ensure IPv6 is consistent (either working or intentionally off) with the `**Address`** line in `**wg.conf`**.
#### `WGUI_LOG_TAIL_PATH` quick setup (systemd)
Use this when you want the **Logs** page to also show a custom application log file.
1. Add environment variable to your wireguard-ui service:
```ini
[Service]
Environment="WGUI_LOG_TAIL_PATH=/var/log/wireguard-ui.log"
```
1. Ensure file exists and is readable by the service user:
```bash
sudo touch /var/log/wireguard-ui.log
sudo chmod 640 /var/log/wireguard-ui.log
```
1. (Recommended) append service stdout/stderr to that file:
```ini
[Service]
StandardOutput=append:/var/log/wireguard-ui.log
StandardError=append:/var/log/wireguard-ui.log
```
1. Reload and restart:
```bash
sudo systemctl daemon-reload
sudo systemctl restart wireguard-ui
```
1. Verify:
```bash
sudo systemctl show wireguard-ui -p Environment
sudo tail -n 50 /var/log/wireguard-ui.log
```
> Note: The Logs page now also includes `systemctl status wg-quick@<iface>` and recent `journalctl` output. `WGUI_LOG_TAIL_PATH` is only for the optional file section.
#### Passkeys (WebAuthn) behind reverse proxy (systemd example)
If you use a public domain and/or reverse proxy (Nginx, Caddy, Traefik, Cloudflare Tunnel), define a fixed WebAuthn RP ID and allowed origins. Add the following under `**[Service]**` (for example with `**systemctl edit wireguard-ui**` or entries in the same `**EnvironmentFile=**` you use in **[systemd: install and enable the web service](#systemd-install-and-enable-the-web-service)**):
```ini
[Service]
Environment="WGUI_WEBAUTHN_RP_ID=vpn.example.com"
Environment="WGUI_WEBAUTHN_RP_ORIGINS=https://vpn.example.com"
Environment="WGUI_WEBAUTHN_RP_DISPLAY_NAME=WireGuard UI"
```
Then reload and restart:
```bash
sudo systemctl daemon-reload
sudo systemctl restart wireguard-ui
```
Notes:
- `WGUI_WEBAUTHN_RP_ID` must match your effective login domain.
- `WGUI_WEBAUTHN_RP_ORIGINS` accepts comma-separated values for multi-origin setups.
- Passkeys require `https://` in production (browsers only allow non-HTTPS for localhost).
##### Android app passkeys (companion Flutter client — Credential Manager)
The Flutter Android companion app calls the same WireGuard UI WebAuthn JSON endpoints (`**POST**` `**{BASE_PATH}/api/passkeys/login/begin**`, `**/finish**`). Android **Credential Manager** still verifies **Digital Asset Links** independently: it downloads `**https://<rpId>/.well-known/assetlinks.json`** and checks package name + signing certificate fingerprints against your app install.
Assertion signatures include `**Origin`** inside `**clientDataJSON`**. Browser users send origins like `**https://vpn.example.com`**. Native Android sends `**android:apk-key-hash:<base64url(SHA-256(signing-cert-digest))>**`. The `**go-webauthn**` verifier must therefore allow both your HTTPS origins and the APK-hash origin — WireGuard UI appends APK-hash origins whenever `**WGUI_ANDROID_PASSKEY_SHA256**` is set.
**Suggested end-to-end flow**
1. **Browser first:** In WireGuard UI, enable **Passkeys** under Global settings and **Apply config**. Open the panel URL (example: `**https://vpn.example.net/wg`** if `BASE_PATH` is `**/wg`**), visit Profile (or Administrator → Users), and enroll at least one passkey per account that should unlock the Android app. That registers credentials bound to `**WGUI_WEBAUTHN_RP_ID`** (or inferred host).
2. **Server env:** Set `**WGUI_WEBAUTHN_RP_ORIGINS`** (comma-separated `**https://`** origins visitors actually see) plus `**WGUI_WEBAUTHN_RP_ID`** when you rely on proxies or internal hostnames — they must mirror the HTTPS hostname tied to `**assetlinks.json`**. Populate `**WGUI_ANDROID_PASSKEY_SHA256**` from `./gradlew signingReport`, matching whichever keystore ships on devices, and set `**WGUI_ANDROID_PASSKEY_PACKAGE**` if Gradle `**applicationId**` deviates from the default `**com.wireguardui.wireguard_ui_client**`.
3. **Reverse proxy:** Route `**/.well-known/assetlinks.json`** on the panel hostname back to WireGuard UI (see examples below).
4. **App:** Configure base URL/base path matching your API prefix. Fill **Passkey origin** if the HTTPS hostname where you enrolled passkeys differs from the API `**Host`** (LAN IP/API gateway case). Prefer **Username** + passkey together if the credential is not discoverable (common for web-created passkeys unless you mandated resident/discoverable enrollment).
**Symptoms resolved by proper setup**
| Symptom | Typical cause |
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `**RP ID cannot be validated`** (Credential Manager DOM error) | Missing or inaccessible `**https://<rpId>/.well-known/assetlinks.json`** (**404**, auth wall, redirects). Proxies forwarding only `**{BASE_PATH}`** strand this path unless you terminate `**/.well-known`** upstream—see **[Digital Asset Links](https://developers.google.com/digital-asset-links/v1/getting-started)** tooling to validate statements externally. |
| `**Invalid passkey`** after tapping a credential but **Digital Asset Links** already pass | Older servers missing APK-hash `**RPOrigins`**, malformed assertion body, mismatched `**WGUI_ANDROID_PASSKEY_SHA256`** vs APK, or `**begin`/`finish` session loss** — see cookie note below. |
| `**HTTP 405`** (`curl -I` only) against assetlinks | `curl -I` issues **HEAD**. WireGuard UI implements **HEAD** + **GET**; if you proxy strips HEAD pick **GET** (`**curl -sS`**). |
**Server configuration checklist**
1. `**WGUI_ANDROID_PASSKEY_SHA256`** — fingerprint(s) comma-separated (**hex**, with or without colons), matching Android Studio / `./gradlew signingReport` (**SHA-256**) for whatever build ships to devices; or an **absolute path** to a file whose contents are that string (readable by the **wireguard-ui** process user).
2. `**WGUI_ANDROID_PASSKEY_PACKAGE`** — optional `**applicationId`**, defaults to `**com.wireguardui.wireguard_ui_client`** inside `**assetlinks.json**`.
3. **Reverse proxy exposes host-root asset links** proxying `**https://<hostname>/.well-known/assetlinks.json`** to WireGuard UI's `**http://backend:PORT/.well-known/assetlinks.json`**. `**https://hostname/wg/.well-known`** is **ignored** by Android's association crawler.
4. **Mirror workaround:** If you truly cannot terminate `**/.well-known`** directly on WireGuard UI, `**GET https://vpn.example.net/wg/.well-known/assetlinks.json`** (duplicate route emitted when `**BASE_PATH=/wg`**; adjust path for your `**BASE_PATH`**) returns the identical JSON blob you can synchronize to `**https://vpn.example.net/.well-known/assetlinks.json**` elsewhere.
**Behavior implemented in WireGuard UI**
| Mechanism | Role |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `**GET` / `HEAD`** `**/.well-known/assetlinks.json`** | Issues Digital Asset Links JSON including `**delegate_permission/common.get_login_creds`** plus `**delegate_permission/common.handle_all_urls`**. |
| `**X-WGUI-WebAuthn-Public-Origin**` | Optional HTTPS-only hint validated against configured origins; forces rp host alignment when mobiles talk to `**https://LAN:port/wg**` but passkeys bind to `**https://vpn.example.com**`. If it disagrees with `**WGUI_WEBAUTHN_RP_ID**`, the hinted public host wins **for configuring WebAuthn** so Credential Manager `**rp.id`** verification matches `**assetlinks`**. Never trust arbitrary hosts blindly — values must already be admitted via `**WGUI_WEBAUTHN_RP_ORIGINS`**, `**WGUI_WEBAUTHN_RP_ID` hostname match**, or the inferred default origin header. |
| `**android:apk-key-hash`** derived origins | For every SHA-256 entry in `**WGUI_ANDROID_PASSKEY_SHA256`**, append `**android:apk-key-hash:`** + URL-safe Base64 (no padding) of the raw digest to `**RPOrigins**`. |
| `**RPAllowCrossOrigin`** | Enabled whenever `**WGUI_ANDROID_PASSKEY_SHA256`** is non-empty so Credential Manager payloads that declare `**crossOrigin`** in `**clientDataJSON**` pass verification once origins match. |
**Reverse proxy snippets**
**Caddy (keep `/.well-known` on the apex host while `{BASE_PATH}` serves the UI — e.g. `/wg`)**
```caddyfile
vpn.example.net {
handle /.well-known/assetlinks.json {
reverse_proxy 127.0.0.1:5000
}
handle /wg* {
reverse_proxy 127.0.0.1:5000
}
}
```
Replace `**127.0.0.1:5000**` with your `**BIND_ADDRESS**` target (`docker` internal hostname, upstream socket, etc.).
**Apache / legacy `ProxyPass`**
Enable `**mod_proxy**` + `**proxy_http**` (Debian/Ubuntu: `a2enmod proxy proxy_http`). Example:
```apache
SSLProxyEngine on
ProxyPass /.well-known/assetlinks.json http://127.0.0.1:5000/.well-known/assetlinks.json
ProxyPassReverse /.well-known/assetlinks.json http://127.0.0.1:5000/.well-known/assetlinks.json
```
Place specific statements **above** wildcard `**ProxyPass /`** directives.
Equivalent **Nginx** pattern:
```nginx
location = /.well-known/assetlinks.json {
proxy_pass http://127.0.0.1:5000/.well-known/assetlinks.json;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Verification (expect `**200**` and JSON body):
```bash
curl -sSIL https://vpn.example.net/.well-known/assetlinks.json
curl -sS https://vpn.example.net/.well-known/assetlinks.json
# pipe through jq locally if installed for readability
```
**Login session cookie**
The `**login/begin`** response sets a `**session`** cookie tying server-side `**SessionData`** to `**login/finish`**. The companion Flutter client keeps a Dio cookie jar; ensure reverse proxies propagate `**Cookie` / `Set-Cookie**` without stripping attributes. Tune `**WGUI_SESSION_COOKIE_***` when browsers or SPA clients authenticate cross-site (`SameSite=None` + `Secure`).
Logs: failing `**finish**` emits `**WARN [passkeys] login finish rejected ...**` with the verifier error (**origin**, **challenge**, **signature**) — correlate with timestamps while reproducing mobile flows.
Companion UX reminders:
- `**Passkey origin`** on the Flutter login sheet maps straight to `**X-WGUI-WebAuthn-Public-Origin`** whenever the HTTPS hostname used during browser enrollment differs from the API `**Host`**.
- Supply `**Username`** + passkey for accounts whose authenticators are **non-discoverable** (typical hybrid web enrollments unless you forced resident keys).
- After rotating TLS/proxy fingerprints, run `**adb shell pm verify-app-links --re-verify com.wireguardui.wireguard_ui_client`** so Android re-fetches statements (swap package id if customized).
Additional documentation lives in `**wireguard-ui-android-client`** `README.md` (mobile-focused recap).
##### Caddy + Dynamic DNS (No-IP): quick HTTPS so Passkeys work
Browsers treat Passkeys/WebAuthn as **[secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)** (`https://` on a hostname, or `http://localhost`). Plain `http://<your-ip>` is **not** enough. Use a hostname (No-IP, DuckDNS, etc.), forward ports **80** and **443**, and terminate TLS with Caddy.
1. **No-IP (or similar)**
Create `yourhost.ddns.net` (example), install the updater or rely on No-IP so the **A record** points to your **WAN** public IP.
2. **Firewall / router**
Forward **TCP 80** and **TCP 443** from the internet to the machine that runs Caddy (required for Let's Encrypt HTTP-01 by default).
3. **Install Caddy**
Follow [Caddy install docs](https://caddyserver.com/docs/install) for your distro (official repo or package).
4. `**Caddyfile`** (minimal reverse proxy to WireGuard UI on loopback):
```caddyfile
yourhost.ddns.net {
encode gzip
reverse_proxy 127.0.0.1:5000
}
```
Replace `yourhost.ddns.net` with your hostname and `**5000**` with the port where `wireguard-ui` listens (`BIND_ADDRESS`, e.g. `:5000` or `127.0.0.1:5000`).
5. **(Optional, recommended)** Listen only on localhost so only Caddy exposes HTTPS:
```bash
BIND_ADDRESS=127.0.0.1:5000 ./wireguard-ui
```
6. **Restart Caddy**, then open `**https://yourhost.ddns.net`** and confirm the browser shows a **valid lock** (no certificate warnings).
7. `**wireguard-ui` systemd** — set RP ID/origin to match **exactly** what users type in the browser:
```ini
[Service]
Environment="WGUI_WEBAUTHN_RP_ID=yourhost.ddns.net"
Environment="WGUI_WEBAUTHN_RP_ORIGINS=https://yourhost.ddns.net"
```
Then `daemon-reload` and `restart wireguard-ui`.
8. **Inside the UI****Settings** → enable **Passkeys****Apply config**. Then **Administration → Users**: register a passkey per user. The login page will offer **Sign in with Passkey** once enabled.
If HTTPS still fails behind NAT, verify port 80 reaches Caddy on first certificate issuance; use `journalctl -u caddy -f` on errors.
## Auto restart WireGuard daemon
WireGuard-UI only takes care of configuration generation. On Linux you can enable in-process `wg syncconf` after apply (see variables above), or use systemd to watch for changes and restart the
service. Following is an example:
> **Note:** The **systemd** block below does **not** start the `wireguard-ui` web process. It only runs `systemctl restart wg-quick@wg0` when `wg0.conf` is modified on disk. The UI binary is a separate program (see **Run WireGuard-UI** above and **[systemd: install and enable the web service](#systemd-install-and-enable-the-web-service)**).
### systemd: install and enable the web service
This section is about the **wireguard-ui HTTP process** (the web UI), not the optional `**wg-quick@`** watcher described later.
#### What systemd must provide
1. **Working directory** — The app opens its JSON store at `**./db`** relative to the current working directory (`jsondb.New("./db")` in `main.go`). The unit **must** set `WorkingDirectory` to a persistent directory owned by the service user (e.g. `/var/lib/wireguard-ui`). If you omit this, the database lands wherever systemds default cwd is (often `/` or `/root`), which is easy to misplace or permission incorrectly.
2. **Binary** — Install the release binary (or your own build after `./prepare_assets.sh`) to a fixed path, e.g. `**/usr/local/bin/wireguard-ui`**, mode `0755`.
3. **Environment** — All knobs (`BASE_PATH`, `BIND_ADDRESS`, `SESSION_SECRET`, `WGUI_`*, `FCM_CREDENTIALS_FILE`, etc.) are ordinary **process environment variables**. Set them with `Environment=` lines in the unit, or load a file with `**EnvironmentFile=`**.
#### Register the service (step by step)
1. **Create an unprivileged account and data directory** (recommended). The home directory doubles as `**WorkingDirectory`** / database location:
```bash
sudo useradd --system --create-home --home-dir /var/lib/wireguard-ui \
--shell /usr/sbin/nologin --user-group wireguard-ui
sudo chmod 750 /var/lib/wireguard-ui
```
If the user already exists, ensure `**/var/lib/wireguard-ui**` exists and is owned by `**wireguard-ui:wireguard-ui**` with mode `**0750**`.
2. **Install the binary**:
```bash
sudo install -m 0755 wireguard-ui /usr/local/bin/wireguard-ui
```
3. **Optional config directory** for secrets on disk (session key, Firebase JSON, Android SHA file, etc.):
```bash
sudo mkdir -p /etc/wireguard-ui
sudo chown root:wireguard-ui /etc/wireguard-ui
sudo chmod 750 /etc/wireguard-ui
```
Place secret files here and grant the **service user** read access (e.g. `chmod 640` and group `wireguard-ui`, or ownership `wireguard-ui:wireguard-ui` as appropriate). If a path is unreadable by the process user, features that read that file (session encryption, FCM, passkey asset links) will fail at runtime.
4. **Environment file** — systemd reads `**KEY=value`** lines from `EnvironmentFile=` (comments with `#` allowed). You do **not** need `export`. Example `**/etc/default/wireguard-ui`** (Debian/Ubuntu naming is common; the path is arbitrary as long as the unit references it):
```text
BIND_ADDRESS=127.0.0.1:5000
BASE_PATH=wg
SESSION_SECRET_FILE=/etc/wireguard-ui/session.secret
```
Point `SESSION_SECRET` **or** `SESSION_SECRET_FILE` at a strong secret (see the environment table above). Same idea for `WGUI_PASSWORD_FILE`, `FCM_CREDENTIALS_FILE`, and `**WGUI_ANDROID_PASSKEY_SHA256`** (inline hex **or** absolute path to a file whose contents are the fingerprint string).
5. **Unit file** — Create `**/etc/systemd/system/wireguard-ui.service`**:
```ini
[Unit]
Description=WireGuard UI
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=wireguard-ui
Group=wireguard-ui
WorkingDirectory=/var/lib/wireguard-ui
EnvironmentFile=-/etc/default/wireguard-ui
ExecStart=/usr/local/bin/wireguard-ui
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
```
The `-` prefix on `**EnvironmentFile=-/etc/default/wireguard-ui**` means “ignore if missing” so the unit still parses before you create the file. You can instead use `**/etc/wireguard-ui.env**` or multiple `Environment="KEY=value"` lines for a minimal setup.
1. **Reload systemd and start**:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now wireguard-ui
sudo systemctl status wireguard-ui
```
2. **Logs**:
```bash
journalctl -u wireguard-ui -f
```
#### WireGuard config and `wg-quick` / `systemctl`
If the UI should **write** `wg0.conf` (default `**/etc/wireguard/wg0.conf`**) or run `**wg-quick`** / `**systemctl restart wg-quick@…**`, the `**wireguard-ui` user** must be allowed to do so on your distribution (group membership on `/etc/wireguard`, `sudoers` for specific commands, or a documented choice to run the service as root — discouraged). There is no single recipe across distros; tighten permissions after verifying **Apply config** and optional `**WGUI_ALLOW_WG_QUICK`** / `**WGUI_WG_RESTART_VIA_SYSTEMD`** behaviour.
#### Example unit without `EnvironmentFile` (inline bind only)
The app stores its JSON database under `**./db` relative to the process working directory**, so `WorkingDirectory` is mandatory for a predictable data path.
```ini
[Unit]
Description=WireGuard UI
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=wireguard-ui
Group=wireguard-ui
WorkingDirectory=/var/lib/wireguard-ui
Environment="BIND_ADDRESS=127.0.0.1:5000"
ExecStart=/usr/local/bin/wireguard-ui
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
```
Then `systemctl daemon-reload` and `systemctl enable --now wireguard-ui` as above.
### Using systemd (restart `wg-quick` when config file changes)
Create `/etc/systemd/system/wgui.service`
```bash
cd /etc/systemd/system/
cat << EOF > wgui.service
[Unit]
Description=Restart WireGuard
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart wg-quick@wg0.service
[Install]
RequiredBy=wgui.path
EOF
```
Create `/etc/systemd/system/wgui.path`
```bash
cd /etc/systemd/system/
cat << EOF > wgui.path
[Unit]
Description=Watch /etc/wireguard/wg0.conf for changes
[Path]
PathModified=/etc/wireguard/wg0.conf
[Install]
WantedBy=multi-user.target
EOF
```
Apply it
```sh
systemctl enable wgui.{path,service}
systemctl start wgui.{path,service}
```
### Using openrc
Create `/usr/local/bin/wgui` file and make it executable
```sh
cd /usr/local/bin/
cat << EOF > wgui
#!/bin/sh
wg-quick down wg0
wg-quick up wg0
EOF
chmod +x wgui
```
Create `/etc/init.d/wgui` file and make it executable
```sh
cd /etc/init.d/
cat << EOF > wgui
#!/sbin/openrc-run
command=/sbin/inotifyd
command_args="/usr/local/bin/wgui /etc/wireguard/wg0.conf:w"
pidfile=/run/${RC_SVCNAME}.pid
command_background=yes
EOF
chmod +x wgui
```
Apply it
```sh
rc-service wgui start
rc-update add wgui default
```
### Using Docker
Set `WGUI_MANAGE_RESTART=true` to manage Wireguard interface restarts.
Using `WGUI_MANAGE_START=true` can also replace the function of `wg-quick@wg0` service, to start Wireguard at boot, by
running the container with `restart: unless-stopped`. These settings can also pick up changes to Wireguard Config File
Path, after restarting the container. Please make sure you have `--cap-add=NET_ADMIN` in your container config to make
this feature work.
## Build
### Build docker image
Go to the project root directory and run the following command:
```sh
docker build --build-arg=GIT_COMMIT=$(git rev-parse --short HEAD) -t wireguard-ui .
```
or
```sh
docker compose build --build-arg=GIT_COMMIT=$(git rev-parse --short HEAD)
```
:information_source: A container image is available on [Docker Hub](https://hub.docker.com/r/ngoduykhanh/wireguard-ui)
which you can pull and use
```
docker pull ngoduykhanh/wireguard-ui
```
### Build binary file
Prepare the assets directory
```sh
./prepare_assets.sh
```
Then build your executable
```sh
go build -o wireguard-ui
```
## License
MIT. See [LICENSE](https://github.com/ngoduykhanh/wireguard-ui/blob/master/LICENSE).
## Support
If you like the project and want to support it, you can *buy me a coffee*