From 637047e98dbc6e7f046c4e5ed0beee60dbd0bd5b Mon Sep 17 00:00:00 2001 From: okxlin Date: Thu, 27 Jul 2023 01:17:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0wireguard-ui=E5=88=B0?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wireguard-ui/0.5.2/.env.sample | 6 + apps/wireguard-ui/0.5.2/data.yml | 41 ++++ apps/wireguard-ui/0.5.2/docker-compose.yml | 51 +++++ apps/wireguard-ui/README.md | 233 ++++++++++++++++++++ apps/wireguard-ui/data.yml | 20 ++ apps/wireguard-ui/latest/.env.sample | 6 + apps/wireguard-ui/latest/data.yml | 41 ++++ apps/wireguard-ui/latest/docker-compose.yml | 51 +++++ apps/wireguard-ui/logo.png | Bin 0 -> 9651 bytes 9 files changed, 449 insertions(+) create mode 100644 apps/wireguard-ui/0.5.2/.env.sample create mode 100644 apps/wireguard-ui/0.5.2/data.yml create mode 100644 apps/wireguard-ui/0.5.2/docker-compose.yml create mode 100644 apps/wireguard-ui/README.md create mode 100644 apps/wireguard-ui/data.yml create mode 100644 apps/wireguard-ui/latest/.env.sample create mode 100644 apps/wireguard-ui/latest/data.yml create mode 100644 apps/wireguard-ui/latest/docker-compose.yml create mode 100644 apps/wireguard-ui/logo.png diff --git a/apps/wireguard-ui/0.5.2/.env.sample b/apps/wireguard-ui/0.5.2/.env.sample new file mode 100644 index 00000000..9258399b --- /dev/null +++ b/apps/wireguard-ui/0.5.2/.env.sample @@ -0,0 +1,6 @@ +CONTAINER_NAME="wireguard-ui" +DATA_PATH="./data" +PANEL_APP_PORT_HTTP="40073" +WEBUI_PWD="password" +WEBUI_USER="admin" +WIREGUARD_PORT="51820" diff --git a/apps/wireguard-ui/0.5.2/data.yml b/apps/wireguard-ui/0.5.2/data.yml new file mode 100644 index 00000000..d24dad4b --- /dev/null +++ b/apps/wireguard-ui/0.5.2/data.yml @@ -0,0 +1,41 @@ +additionalProperties: + formFields: + - default: 40073 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: WebUI Port + labelZh: 网页端口 + required: true + rule: paramPort + type: number + - default: 51820 + edit: true + envKey: WIREGUARD_PORT + labelEn: Wireguard port + labelZh: Wireguard端口 + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: DATA_PATH + labelEn: Data folder path + labelZh: 数据文件夹路径 + required: true + type: text + - default: "admin" + edit: true + envKey: WEBUI_USER + labelEn: Webui user + labelZh: 网页用户 + required: true + type: text + - default: "password" + edit: true + envKey: WEBUI_PWD + labelEn: Webui password + labelZh: 网页密码 + random: false + required: false + rule: paramComplexity + type: password diff --git a/apps/wireguard-ui/0.5.2/docker-compose.yml b/apps/wireguard-ui/0.5.2/docker-compose.yml new file mode 100644 index 00000000..67ab4d8f --- /dev/null +++ b/apps/wireguard-ui/0.5.2/docker-compose.yml @@ -0,0 +1,51 @@ +version: "3" +services: + wireguard: + container_name: ${CONTAINER_NAME}-wireguard + restart: always + networks: + - 1panel-network + cap_add: + - NET_ADMIN + volumes: + - ${DATA_PATH}/config:/config + ports: + - "${PANEL_APP_PORT_HTTP}:5000" + - "${WIREGUARD_PORT}:51820/udp" + image: linuxserver/wireguard:latest + labels: + createdBy: "Apps" + + wireguard-ui: + container_name: ${CONTAINER_NAME} + restart: always + depends_on: + - wireguard + cap_add: + - NET_ADMIN + network_mode: service:wireguard + environment: + - SENDGRID_API_KEY + - EMAIL_FROM_ADDRESS + - EMAIL_FROM_NAME + - SESSION_SECRET + - WGUI_USERNAME=${WEBUI_USER} + - WGUI_PASSWORD=${WEBUI_PWD} + - WG_CONF_TEMPLATE + - WGUI_MANAGE_START=true + - WGUI_MANAGE_RESTART=true + volumes: + - ${DATA_PATH}/db:/app/db + - ${DATA_PATH}/config:/etc/wireguard + image: ngoduykhanh/wireguard-ui:0.5.2 + logging: + driver: json-file + options: + max-size: 50m + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true + diff --git a/apps/wireguard-ui/README.md b/apps/wireguard-ui/README.md new file mode 100644 index 00000000..0da4652b --- /dev/null +++ b/apps/wireguard-ui/README.md @@ -0,0 +1,233 @@ +![](https://github.com/ngoduykhanh/wireguard-ui/workflows/wireguard-ui%20build%20release/badge.svg) + +# wireguard-ui + +A web user interface to manage your WireGuard setup. + +## Features + +- Friendly UI +- Authentication +- Manage extra client information (name, email, etc) +- Retrieve client config using QR code / file / email + +![wireguard-ui 0.3.7](https://user-images.githubusercontent.com/37958026/177041280-e3e7ca16-d4cf-4e95-9920-68af15e780dd.png) + +## 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 +``` + +### 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 | 0.0.0.0:80 | +| `SESSION_SECRET` | The secret key used to encrypt the session cookies. Set this to a random value | 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_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A | +| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | 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 | +| `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_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` | + +### 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` | + +## Auto restart WireGuard daemon + +WireGuard-UI only takes care of configuration generation. You can use systemd to watch for the changes and restart the +service. Following is an example: + +### Using systemd + +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 avaialble 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* ☕ + +Buy Me A Coffee diff --git a/apps/wireguard-ui/data.yml b/apps/wireguard-ui/data.yml new file mode 100644 index 00000000..6b3d0b47 --- /dev/null +++ b/apps/wireguard-ui/data.yml @@ -0,0 +1,20 @@ +name: wireguard-ui +tags: + - 工具 +title: Wireguard网络界面 +type: 工具 +description: Wireguard网络界面 +additionalProperties: + key: wireguard-ui + name: wireguard-ui + tags: + - Tool + shortDescZh: Wireguard网络界面 + shortDescEn: Wireguard web interface + type: tool + crossVersionUpdate: true + limit: 1 + recommend: 0 + website: https://www.wireguard.com/ + github: https://github.com/ngoduykhanh/wireguard-ui + document: https://www.wireguard.com/ \ No newline at end of file diff --git a/apps/wireguard-ui/latest/.env.sample b/apps/wireguard-ui/latest/.env.sample new file mode 100644 index 00000000..9258399b --- /dev/null +++ b/apps/wireguard-ui/latest/.env.sample @@ -0,0 +1,6 @@ +CONTAINER_NAME="wireguard-ui" +DATA_PATH="./data" +PANEL_APP_PORT_HTTP="40073" +WEBUI_PWD="password" +WEBUI_USER="admin" +WIREGUARD_PORT="51820" diff --git a/apps/wireguard-ui/latest/data.yml b/apps/wireguard-ui/latest/data.yml new file mode 100644 index 00000000..d24dad4b --- /dev/null +++ b/apps/wireguard-ui/latest/data.yml @@ -0,0 +1,41 @@ +additionalProperties: + formFields: + - default: 40073 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: WebUI Port + labelZh: 网页端口 + required: true + rule: paramPort + type: number + - default: 51820 + edit: true + envKey: WIREGUARD_PORT + labelEn: Wireguard port + labelZh: Wireguard端口 + required: true + rule: paramPort + type: number + - default: ./data + edit: true + envKey: DATA_PATH + labelEn: Data folder path + labelZh: 数据文件夹路径 + required: true + type: text + - default: "admin" + edit: true + envKey: WEBUI_USER + labelEn: Webui user + labelZh: 网页用户 + required: true + type: text + - default: "password" + edit: true + envKey: WEBUI_PWD + labelEn: Webui password + labelZh: 网页密码 + random: false + required: false + rule: paramComplexity + type: password diff --git a/apps/wireguard-ui/latest/docker-compose.yml b/apps/wireguard-ui/latest/docker-compose.yml new file mode 100644 index 00000000..5ae90756 --- /dev/null +++ b/apps/wireguard-ui/latest/docker-compose.yml @@ -0,0 +1,51 @@ +version: "3" +services: + wireguard: + container_name: ${CONTAINER_NAME}-wireguard + restart: always + networks: + - 1panel-network + cap_add: + - NET_ADMIN + volumes: + - ${DATA_PATH}/config:/config + ports: + - "${PANEL_APP_PORT_HTTP}:5000" + - "${WIREGUARD_PORT}:51820/udp" + image: linuxserver/wireguard:latest + labels: + createdBy: "Apps" + + wireguard-ui: + container_name: ${CONTAINER_NAME} + restart: always + depends_on: + - wireguard + cap_add: + - NET_ADMIN + network_mode: service:wireguard + environment: + - SENDGRID_API_KEY + - EMAIL_FROM_ADDRESS + - EMAIL_FROM_NAME + - SESSION_SECRET + - WGUI_USERNAME=${WEBUI_USER} + - WGUI_PASSWORD=${WEBUI_PWD} + - WG_CONF_TEMPLATE + - WGUI_MANAGE_START=true + - WGUI_MANAGE_RESTART=true + volumes: + - ${DATA_PATH}/db:/app/db + - ${DATA_PATH}/config:/etc/wireguard + image: ngoduykhanh/wireguard-ui:latest + logging: + driver: json-file + options: + max-size: 50m + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true + diff --git a/apps/wireguard-ui/logo.png b/apps/wireguard-ui/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cf4b8c488540048c331c3c0fbb8a30189ea4ff36 GIT binary patch literal 9651 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 LlqG9E8$