Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4890bb28e5 | |||
| c3dbd3a815 | |||
| fc480df910 | |||
| b3bd2502af | |||
| eb5ad91022 | |||
| f2955a1278 | |||
| 1b76c066e0 | |||
| 5b68cc7311 | |||
| 0597470f4c | |||
| 159a51cff4 | |||
| 9fc6ebafb3 | |||
| 9a029eeb23 | |||
| e5fb6ff3a6 | |||
| fcb5049dab | |||
| 93db67bab6 | |||
| 842475f799 | |||
| f4d3608da7 |
@@ -24,6 +24,8 @@ This update is an entire rewrite to make it even easier to set up your own VPN.
|
||||
- Deprecated Dockerless Installations
|
||||
- Added Docker Volume Mount (`/lib/modules`)
|
||||
- Removed ARMv6 and ARMv7 support
|
||||
- Connections over HTTP require setting the `INSECURE` env var
|
||||
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
|
||||
|
||||
## [14.0.0] - 2024-09-04
|
||||
|
||||
|
||||
@@ -42,8 +42,21 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
|
||||
> [!NOTE]
|
||||
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest)
|
||||
|
||||
<!-- TODO: remove after release -->
|
||||
|
||||
> [!WARNING]
|
||||
> As the Docs are still in Pre-release, you can access them here [https://wg-easy.github.io/wg-easy/Pre-release](https://wg-easy.github.io/wg-easy/Pre-release)
|
||||
|
||||
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/)
|
||||
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/)
|
||||
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/)
|
||||
- [Nginx](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/nginx/)
|
||||
- [Traefik](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/traefik/)
|
||||
- [Podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman/)
|
||||
- [AdGuard Home](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/adguard/)
|
||||
|
||||
> [!NOTE]
|
||||
> If you want to migrate from the old version to the new version, you can find the migration guide here: [Migration Guide](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/)
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -89,52 +102,7 @@ Now setup a reverse proxy to be able to access the Web UI from the internet.
|
||||
|
||||
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing
|
||||
|
||||
<!-- TOOD: add to docs: Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/) -->
|
||||
|
||||
<!-- TOOD: add to docs
|
||||
To setup the IPv6 Network, simply run once:
|
||||
|
||||
```bash
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
-d default \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 wg \
|
||||
```
|
||||
|
||||
To automatically install & run wg-easy, simply run:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--net wg \
|
||||
-e PORT=51821 \
|
||||
--name wg-easy \
|
||||
--ip6 fdcc:ad94:bacf:61a3::2a \
|
||||
--ip 10.42.42.42 \
|
||||
-v ~/.wg-easy:/etc/wireguard \
|
||||
-v /lib/modules:/lib/modules:ro \
|
||||
-p 51820:51820/udp \
|
||||
-p 51821:51821/tcp \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add SYS_MODULE \
|
||||
--sysctl net.ipv4.ip_forward=1 \
|
||||
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
--sysctl net.ipv6.conf.all.forwarding=1 \
|
||||
--sysctl net.ipv6.conf.default.forwarding=1 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/wg-easy/wg-easy
|
||||
```
|
||||
|
||||
The Web UI will now be available on `http://0.0.0.0:51821`.
|
||||
|
||||
The Prometheus metrics will now be available on `http://0.0.0.0:51821/api/metrics`. Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/)
|
||||
|
||||
> 💡 Your configuration files will be saved in `~/.wg-easy`
|
||||
|
||||
-->
|
||||
|
||||
### 3. Sponsor
|
||||
### Donate
|
||||
|
||||
Are you enjoying this project? Consider donating.
|
||||
|
||||
@@ -142,46 +110,30 @@ Founder: [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
|
||||
|
||||
Maintainer: [Buy kaaax0815 a coffee!](https://github.com/sponsors/kaaax0815) ☕
|
||||
|
||||
<!-- TOOD: add to docs
|
||||
## Development
|
||||
|
||||
## Options
|
||||
### Prerequisites
|
||||
|
||||
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
|
||||
- Docker
|
||||
- Node LTS & corepack enabled
|
||||
- Visual Studio Code
|
||||
|
||||
| Env | Default | Example | Description |
|
||||
| ---------- | --------- | ----------- | ------------------------------ |
|
||||
| `PORT`. | `51821` | `6789` | TCP port for Web UI. |
|
||||
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
||||
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
||||
### Dev Server
|
||||
|
||||
## Updating
|
||||
|
||||
To update to the latest version, simply run:
|
||||
This starts the development server with docker
|
||||
|
||||
```shell
|
||||
docker stop wg-easy
|
||||
docker rm wg-easy
|
||||
docker pull ghcr.io/wg-easy/wg-easy
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
And then run the `docker run -d \ ...` command above again.
|
||||
### Update Auto Imports
|
||||
|
||||
With Docker Compose WireGuard Easy can be updated with a single command:
|
||||
`docker compose up --detach --pull always` (if an image tag is specified in the
|
||||
Compose file and it is not `latest`, make sure that it is changed to the desired
|
||||
one; by default it is omitted and
|
||||
[defaults to `latest`](https://docs.docker.com/engine/reference/run/#image-references)). \
|
||||
The WireGuard Easy container will be automatically recreated if a newer image
|
||||
was pulled.
|
||||
If you add something that should be auto-importable and VSCode complains, run:
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
- [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole)
|
||||
- [Using WireGuard-Easy with nginx/SSL](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-nginx-SSL)
|
||||
|
||||
For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki).
|
||||
|
||||
-->
|
||||
```shell
|
||||
cd src
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ services:
|
||||
# - HOST=0.0.0.0
|
||||
# - INSECURE=false
|
||||
|
||||
image: ghcr.io/wg-easy/wg-easy
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
wg:
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
# - NET_RAW # ⚠️ Uncomment if using Podman
|
||||
# - NET_RAW # ⚠️ Uncomment if using Podman Compose
|
||||
sysctls:
|
||||
- net.ipv4.ip_forward=1
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
|
||||
@@ -3,3 +3,9 @@ title: Optional Configuration
|
||||
---
|
||||
|
||||
TODO
|
||||
|
||||
| Env | Default | Example | Description |
|
||||
| ---------- | --------- | ----------- | ------------------------------ |
|
||||
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
|
||||
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
|
||||
| `INSECURE` | `false` | `true` | If access over http is allowed |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Prometheus
|
||||
---
|
||||
|
||||
TODO
|
||||
|
||||
<!-- TOOD: add to docs: Grafana dashboard [21733](https://grafana.com/grafana/dashboards/21733-wireguard/) -->
|
||||
@@ -6,7 +6,9 @@ This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
|
||||
|
||||
## Changes
|
||||
|
||||
This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
|
||||
- This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
|
||||
- If you use armv6 or armv7, you can't migrate to `v15` yet. We are working on it.
|
||||
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
|
||||
|
||||
## Migration
|
||||
|
||||
@@ -38,10 +40,13 @@ docker-compose down
|
||||
|
||||
### Start new container
|
||||
|
||||
Follow the instructions in the [Getting Started](../../usage.md) or [Basic Installation](../../examples/tutorials/basic-installation.md) guide to start the new container.
|
||||
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
|
||||
|
||||
In the setup wizard, select that you already already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
|
||||
|
||||
[docs-getting-started]: ../../getting-started.md
|
||||
[docs-examples]: ../../examples/tutorials/basic-installation.md
|
||||
|
||||
### Done
|
||||
|
||||
You have now successfully migrated to `v15` of `wg-easy`.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Migrate
|
||||
---
|
||||
|
||||
If you want to migrate from an older version of `wg-easy` to the new version, you can find the migration guides listed below.
|
||||
|
||||
- [Migrate from v14 to v15](./from-14-to-15.md) : This guide should also work for any version before `v14`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: AdGuard Home
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Auto Updates
|
||||
---
|
||||
|
||||
## Docker Compose
|
||||
|
||||
With Docker Compose `wg-easy` can be updated with a single command:
|
||||
|
||||
Replace `$DIR` with the directory where your `docker-compose.yml` is located.
|
||||
|
||||
```shell
|
||||
cd $DIR
|
||||
sudo docker compose up -d --pull always
|
||||
```
|
||||
|
||||
## Docker Run
|
||||
|
||||
```shell
|
||||
sudo docker stop wg-easy
|
||||
sudo docker rm wg-easy
|
||||
sudo docker pull ghcr.io/wg-easy/wg-easy
|
||||
```
|
||||
|
||||
And then run the `docker run -d \ ...` command from [Docker Run][docker-run] again.
|
||||
|
||||
[docker-run]: ./docker-run.md
|
||||
|
||||
## Podman
|
||||
|
||||
To update `wg-easy` (and every container that has auto updates enabled), you can run the following command:
|
||||
|
||||
```shell
|
||||
sudo podman auto-update
|
||||
```
|
||||
@@ -27,7 +27,7 @@ Follow the Docs here: <https://docs.docker.com/engine/install/> and install Dock
|
||||
2. Download docker compose file
|
||||
|
||||
```shell
|
||||
sudo curl -o $URL/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
|
||||
sudo curl -o $DIR/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
|
||||
```
|
||||
|
||||
3. Start `wg-easy`
|
||||
@@ -54,3 +54,18 @@ TODO
|
||||
Open your browser and navigate to `https://<your-domain>:51821` or `https://<your-ip>:51821`.
|
||||
|
||||
Follow the instructions to set up your WireGuard VPN.
|
||||
|
||||
## Update `wg-easy`
|
||||
|
||||
To update `wg-easy` to the latest version, run:
|
||||
|
||||
```shell
|
||||
sudo docker-compose -f $DIR/docker-compose.yml pull
|
||||
sudo docker-compose -f $DIR/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
## Auto Update
|
||||
|
||||
If you want to enable auto-updates, follow the instructions here: [Auto Updates][auto-updates]
|
||||
|
||||
[auto-updates]: ./auto-updates.md
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Caddy
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Docker Run
|
||||
---
|
||||
|
||||
To setup the IPv6 Network, simply run once:
|
||||
|
||||
```shell
|
||||
docker network create \
|
||||
-d bridge --ipv6 \
|
||||
-d default \
|
||||
--subnet 10.42.42.0/24 \
|
||||
--subnet fdcc:ad94:bacf:61a3::/64 wg \
|
||||
```
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
To automatically install & run ``wg-easy, simply run:
|
||||
|
||||
```shell
|
||||
docker run -d \
|
||||
--net wg \
|
||||
-e INSECURE=true \
|
||||
--name wg-easy \
|
||||
--ip6 fdcc:ad94:bacf:61a3::2a \
|
||||
--ip 10.42.42.42 \
|
||||
-v ~/.wg-easy:/etc/wireguard \
|
||||
-v /lib/modules:/lib/modules:ro \
|
||||
-p 51820:51820/udp \
|
||||
-p 51821:51821/tcp \
|
||||
--cap-add NET_ADMIN \
|
||||
--cap-add SYS_MODULE \
|
||||
--sysctl net.ipv4.ip_forward=1 \
|
||||
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
||||
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
|
||||
--sysctl net.ipv6.conf.all.forwarding=1 \
|
||||
--sysctl net.ipv6.conf.default.forwarding=1 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/wg-easy/wg-easy:15
|
||||
```
|
||||
|
||||
The Web UI will now be available on `http://0.0.0.0:51821`.
|
||||
|
||||
> 💡 Your configuration files will be saved in `~/.wg-easy`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: NGINX
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -19,16 +19,23 @@ sudo mkdir -p /etc/containers/volumes/wg-easy
|
||||
|
||||
Create a file `/etc/containers/systemd/wg-easy/wg-easy.container` with the following content:
|
||||
|
||||
<!-- ref: major version -->
|
||||
|
||||
```ini
|
||||
[Container]
|
||||
ContainerName=wg-easy
|
||||
Image=ghcr.io/wg-easy/wg-easy:latest
|
||||
Image=ghcr.io/wg-easy/wg-easy:15
|
||||
AutoUpdate=registry
|
||||
|
||||
Volume=/etc/containers/volumes/wg-easy:/etc/wireguard:Z
|
||||
Network=wg-easy.network
|
||||
PublishPort=51820:51820/udp
|
||||
PublishPort=51821:51821/tcp
|
||||
|
||||
# this is used to allow access over HTTP
|
||||
# remove this when using a reverse proxy
|
||||
Environment=INSECURE=true
|
||||
|
||||
AddCapability=NET_ADMIN
|
||||
AddCapability=SYS_MODULE
|
||||
AddCapability=NET_RAW
|
||||
@@ -81,7 +88,7 @@ In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the f
|
||||
1. PostUp
|
||||
|
||||
```shell
|
||||
apk add nftables; nft add table inet wg_table; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy drop \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy drop \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
|
||||
apk add nftables; nft add table inet wg_table; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy drop \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy drop \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
|
||||
```
|
||||
|
||||
2. PostDown
|
||||
@@ -90,6 +97,16 @@ In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the f
|
||||
nft delete table inet wg_table
|
||||
```
|
||||
|
||||
If you don't have iptables loaded on your server, you could see many errors in the logs or in the UI. You can ignore them.
|
||||
|
||||
## Restart the Container
|
||||
|
||||
Restart the container to apply the new hooks:
|
||||
|
||||
```shell
|
||||
sudo systemctl restart wg-easy
|
||||
```
|
||||
|
||||
<!--
|
||||
TODO: improve docs after better nftables support
|
||||
TODO: fix accept web ui port
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Traefik
|
||||
---
|
||||
|
||||
TODO
|
||||
@@ -13,7 +13,7 @@ hide:
|
||||
|
||||
This documentation provides you not only with the basic setup and configuration of wg-easy but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more.
|
||||
|
||||
[docs-tagging]: ./usage.md#tagging-convention
|
||||
[docs-tagging]: ./getting-started.md#tagging-convention
|
||||
|
||||
## About
|
||||
|
||||
@@ -23,9 +23,9 @@ This documentation provides you not only with the basic setup and configuration
|
||||
|
||||
### Getting Started
|
||||
|
||||
If you're new to wg-easy, make sure to read the [_Usage_ chapter][docs-usage] first. If you want to look at examples for Docker Run and Compose, we have an [_Examples_ page][docs-examples].
|
||||
If you're new to wg-easy, make sure to read the [_Getting Started_ chapter][docs-getting-started] first. If you want to look at examples for Docker Run and Compose, we have an [_Examples_ page][docs-examples].
|
||||
|
||||
[docs-usage]: ./usage.md
|
||||
[docs-getting-started]: ./getting-started.md
|
||||
[docs-examples]: ./examples/tutorials/basic-installation.md
|
||||
|
||||
### Contributing
|
||||
@@ -33,3 +33,9 @@ If you're new to wg-easy, make sure to read the [_Usage_ chapter][docs-usage] fi
|
||||
We are always happy to welcome new contributors. For guidelines and entrypoints please have a look at the [Contributing section][docs-contributing].
|
||||
|
||||
[docs-contributing]: ./contributing/issues-and-pull-requests.md
|
||||
|
||||
### Migration
|
||||
|
||||
If you are migrating from an older version of `wg-easy`, please read the [_Migration_ chapter][docs-migration].
|
||||
|
||||
[docs-migration]: ./advanced/migrate/from-14-to-15.md
|
||||
|
||||
+8
-2
@@ -1,7 +1,13 @@
|
||||
site_name: "wg-easy"
|
||||
site_description: "The easiest way to run WireGuard VPN + Web-based Admin UI."
|
||||
site_author: "wg-easy (Github Organization)"
|
||||
copyright: '<p>© <a href="https://github.com/wg-easy"><em>Wireguard Easy Organization</em></a><br/><span>This project is licensed under the GNU Affero General Public License v3.0 or later.</span></p>'
|
||||
site_author: "WireGuard Easy"
|
||||
copyright: >
|
||||
<p>
|
||||
© <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
|
||||
<span>This project is licensed under AGPL-3.0-only.</span><br/>
|
||||
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
|
||||
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
|
||||
</p>
|
||||
|
||||
repo_url: https://github.com/wg-easy/wg-easy
|
||||
repo_name: wg-easy
|
||||
|
||||
+3
-2
@@ -4,7 +4,8 @@
|
||||
"scripts": {
|
||||
"dev": "docker compose -f docker-compose.dev.yml up --build",
|
||||
"build": "docker build -t wg-easy .",
|
||||
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080"
|
||||
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080",
|
||||
"scripts:version": "bash scripts/version.sh"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2"
|
||||
"packageManager": "pnpm@10.6.2"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
package_json="src/package.json"
|
||||
|
||||
# Function to update the version in package.json
|
||||
update_version() {
|
||||
local new_version=$1
|
||||
jq --arg new_version "$new_version" '.version = $new_version' $package_json > tmp.json && mv tmp.json $package_json
|
||||
}
|
||||
|
||||
# Get the current version from package.json
|
||||
current_version=$(jq -r '.version' $package_json)
|
||||
echo "Current version: $current_version"
|
||||
|
||||
# Prompt the user for the new version
|
||||
read -p "Enter the new version (following SemVer): " new_version
|
||||
|
||||
# Official SemVer regex for validation
|
||||
semver_regex="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
|
||||
# Validate the new version
|
||||
if ! echo "$new_version" | grep -Eq "$semver_regex"; then
|
||||
echo "Invalid version format. Please use SemVer format (e.g., 1.0.0 or 1.0.0-alpha)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update the version in package.json
|
||||
update_version $new_version
|
||||
echo "Updated package.json to version $new_version"
|
||||
|
||||
echo "----"
|
||||
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
|
||||
echo "----"
|
||||
|
||||
echo "If you did everything press 'y' to commit the changes and create a new tag"
|
||||
read -p "Do you want to continue? (y/n): " confirm
|
||||
|
||||
if [ "$confirm" != "y" ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Commit the changes
|
||||
git add $package_json
|
||||
git commit -m "Bump version to $new_version"
|
||||
echo "Committed the changes"
|
||||
|
||||
# Create a new Git tag
|
||||
git tag -a "v$new_version" -m "Release version $new_version"
|
||||
echo "Created Git tag v$new_version"
|
||||
|
||||
# Push the commit & tag to the remote repository
|
||||
git push origin master --follow-tags
|
||||
echo "Pushed Git commit and tag v$new_version to remote repository"
|
||||
@@ -6,7 +6,7 @@
|
||||
class="fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50"
|
||||
/>
|
||||
<DialogContent
|
||||
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
|
||||
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md bg-white p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
|
||||
>
|
||||
<DialogTitle
|
||||
class="m-0 text-lg font-semibold text-gray-900 dark:text-neutral-200"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div v-if="data?.length === 0">
|
||||
{{ emptyText || $t('form.noItems') }}
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<div v-for="(item, i) in data" :key="i">
|
||||
<div class="flex flex-row gap-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="data?.length === 0">
|
||||
{{ emptyText || $t('form.noItems') }}
|
||||
</div>
|
||||
<div v-for="(item, i) in data" v-else :key="i">
|
||||
<div class="mt-1 flex flex-row gap-1">
|
||||
<input
|
||||
:value="item"
|
||||
:name="name"
|
||||
@@ -12,13 +12,20 @@
|
||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||
@input="update($event, i)"
|
||||
/>
|
||||
<BaseButton as="input" type="button" value="-" @click="del(i)" />
|
||||
<BaseButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
value="-"
|
||||
@click="del(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BaseButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="data === null">
|
||||
{{ emptyText || $t('form.nullNoItems') }}
|
||||
</div>
|
||||
<div v-for="(item, i) in data" v-else :key="i">
|
||||
<div class="mt-1 flex flex-row gap-1">
|
||||
<input
|
||||
:value="item"
|
||||
:name="name"
|
||||
type="text"
|
||||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
|
||||
@input="update($event, i)"
|
||||
/>
|
||||
<BaseButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
value="-"
|
||||
@click="del(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<BaseButton
|
||||
as="input"
|
||||
type="button"
|
||||
class="rounded-lg"
|
||||
:value="$t('form.add')"
|
||||
@click="add"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const data = defineModel<string[] | null>();
|
||||
defineProps<{ emptyText?: string[]; name: string }>();
|
||||
|
||||
function update(e: Event, i: number) {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
if (!data.value) {
|
||||
return;
|
||||
}
|
||||
data.value[i] = v;
|
||||
}
|
||||
|
||||
function add() {
|
||||
if (data.value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (data.value === null) {
|
||||
data.value = [''];
|
||||
} else {
|
||||
data.value.push('');
|
||||
}
|
||||
}
|
||||
|
||||
function del(i: number) {
|
||||
if (!data.value) {
|
||||
return;
|
||||
}
|
||||
data.value.splice(i, 1);
|
||||
if (data.value.length === 0) {
|
||||
data.value = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -24,7 +24,7 @@
|
||||
·
|
||||
<a
|
||||
class="hover:underline"
|
||||
href="https://github.com/sponsors/WeeJeWel"
|
||||
href="https://github.com/wg-easy/wg-easy#donate"
|
||||
target="_blank"
|
||||
>{{ $t('layout.donate') }}</a
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('admin.config.dnsDesc')">{{
|
||||
$t('admin.config.dns')
|
||||
$t('general.dns')
|
||||
}}</FormHeading>
|
||||
<FormArrayField v-model="data.defaultDns" name="defaultDns" />
|
||||
</FormGroup>
|
||||
|
||||
@@ -2,10 +2,26 @@
|
||||
<main v-if="data">
|
||||
<FormElement @submit.prevent="submit">
|
||||
<FormGroup>
|
||||
<FormTextField id="PreUp" v-model="data.preUp" label="PreUp" />
|
||||
<FormTextField id="PostUp" v-model="data.postUp" label="PostUp" />
|
||||
<FormTextField id="PreDown" v-model="data.preDown" label="PreDown" />
|
||||
<FormTextField id="PostDown" v-model="data.postDown" label="PostDown" />
|
||||
<FormTextField
|
||||
id="PreUp"
|
||||
v-model="data.preUp"
|
||||
:label="$t('hooks.preUp')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostUp"
|
||||
v-model="data.postUp"
|
||||
:label="$t('hooks.postUp')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PreDown"
|
||||
v-model="data.preDown"
|
||||
:label="$t('hooks.preDown')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostDown"
|
||||
v-model="data.postDown"
|
||||
:label="$t('hooks.postDown')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||
|
||||
@@ -41,21 +41,26 @@
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.allowedIpsDesc')">{{
|
||||
$t('general.allowedIps')
|
||||
}}</FormHeading>
|
||||
<FormArrayField v-model="data.allowedIps" name="allowedIps" />
|
||||
<FormHeading :description="$t('client.allowedIpsDesc')">
|
||||
{{ $t('general.allowedIps') }}
|
||||
</FormHeading>
|
||||
<FormNullArrayField v-model="data.allowedIps" name="allowedIps" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.serverAllowedIpsDesc')">{{
|
||||
$t('client.serverAllowedIps')
|
||||
}}</FormHeading>
|
||||
<FormHeading :description="$t('client.serverAllowedIpsDesc')">
|
||||
{{ $t('client.serverAllowedIps') }}
|
||||
</FormHeading>
|
||||
<FormArrayField
|
||||
v-model="data.serverAllowedIps"
|
||||
name="serverAllowedIps"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup></FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.dnsDesc')">
|
||||
{{ $t('general.dns') }}
|
||||
</FormHeading>
|
||||
<FormNullArrayField v-model="data.dns" name="dns" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
|
||||
<FormNumberField
|
||||
@@ -71,6 +76,35 @@
|
||||
:label="$t('general.persistentKeepalive')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading :description="$t('client.hooksDescription')">
|
||||
{{ $t('client.hooks') }}
|
||||
</FormHeading>
|
||||
<FormTextField
|
||||
id="PreUp"
|
||||
v-model="data.preUp"
|
||||
:description="$t('client.hooksLeaveEmpty')"
|
||||
:label="$t('hooks.preUp')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostUp"
|
||||
v-model="data.postUp"
|
||||
:description="$t('client.hooksLeaveEmpty')"
|
||||
:label="$t('hooks.postUp')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PreDown"
|
||||
v-model="data.preDown"
|
||||
:description="$t('client.hooksLeaveEmpty')"
|
||||
:label="$t('hooks.preDown')"
|
||||
/>
|
||||
<FormTextField
|
||||
id="PostDown"
|
||||
v-model="data.postDown"
|
||||
:description="$t('client.hooksLeaveEmpty')"
|
||||
:label="$t('hooks.postDown')"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<FormHeading>{{ $t('form.actions') }}</FormHeading>
|
||||
<FormActionField type="submit" :label="$t('form.save')" />
|
||||
@@ -113,8 +147,12 @@ const _submit = useSubmit(
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
revert: async () => {
|
||||
await navigateTo('/');
|
||||
revert: async (success) => {
|
||||
if (success) {
|
||||
await navigateTo('/');
|
||||
} else {
|
||||
await revert();
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
id="confirm-password"
|
||||
v-model="confirmPassword"
|
||||
autocomplete="new-password"
|
||||
:label="$t('me.confirmPassword')"
|
||||
:label="$t('general.confirmPassword')"
|
||||
/>
|
||||
<FormActionField
|
||||
type="submit"
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
:label="$t('general.password')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<FormPasswordField
|
||||
id="confirmPassword"
|
||||
v-model="confirmPassword"
|
||||
autocomplete="new-password"
|
||||
:label="$t('general.confirmPassword')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
|
||||
</div>
|
||||
@@ -37,6 +45,7 @@ setupStore.setStep(2);
|
||||
|
||||
const username = ref<null | string>(null);
|
||||
const password = ref<string>('');
|
||||
const confirmPassword = ref<string>('');
|
||||
|
||||
const _submit = useSubmit(
|
||||
'/api/setup/2',
|
||||
@@ -54,6 +63,10 @@ const _submit = useSubmit(
|
||||
);
|
||||
|
||||
function submit() {
|
||||
return _submit({ username: username.value, password: password.value });
|
||||
return _submit({
|
||||
username: username.value,
|
||||
password: password.value,
|
||||
confirmPassword: confirmPassword.value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
+23
-11
@@ -14,8 +14,7 @@
|
||||
"email": "E-Mail"
|
||||
},
|
||||
"me": {
|
||||
"currentPassword": "Current Password",
|
||||
"confirmPassword": "Confirm Password"
|
||||
"currentPassword": "Current Password"
|
||||
},
|
||||
"general": {
|
||||
"name": "Name",
|
||||
@@ -25,13 +24,15 @@
|
||||
"updatePassword": "Update Password",
|
||||
"mtu": "MTU",
|
||||
"allowedIps": "Allowed IPs",
|
||||
"dns": "DNS",
|
||||
"persistentKeepalive": "Persistent Keepalive",
|
||||
"logout": "Logout",
|
||||
"continue": "Continue",
|
||||
"host": "Host",
|
||||
"port": "Port",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
"no": "No",
|
||||
"confirmPassword": "Confirm Password"
|
||||
},
|
||||
"setup": {
|
||||
"welcome": "Welcome to your first setup of wg-easy !",
|
||||
@@ -95,10 +96,14 @@
|
||||
"noPrivKey": "This client has no known private key. Cannot create Configuration.",
|
||||
"showQR": "Show QR Code",
|
||||
"downloadConfig": "Download Configuration",
|
||||
"allowedIpsDesc": "Which IPs will be routed through the VPN",
|
||||
"allowedIpsDesc": "Which IPs will be routed through the VPN (overrides global config)",
|
||||
"serverAllowedIpsDesc": "Which IPs the server will route to the client",
|
||||
"mtuDesc": "Sets the maximum transmission unit (packet size) for the VPN tunnel",
|
||||
"persistentKeepaliveDesc": "Sets the interval (in seconds) for keep-alive packets. 0 disables it"
|
||||
"persistentKeepaliveDesc": "Sets the interval (in seconds) for keep-alive packets. 0 disables it",
|
||||
"hooks": "Hooks",
|
||||
"hooksDescription": "Hooks only work with wg-quick",
|
||||
"hooksLeaveEmpty": "Only for wg-quick. Otherwise, leave it empty",
|
||||
"dnsDesc": "DNS server clients will use (overrides global config)"
|
||||
},
|
||||
"dialog": {
|
||||
"change": "Change",
|
||||
@@ -118,6 +123,7 @@
|
||||
"sectionGeneral": "General",
|
||||
"sectionAdvanced": "Advanced",
|
||||
"noItems": "No items",
|
||||
"nullNoItems": "No items. Using global config",
|
||||
"add": "Add"
|
||||
},
|
||||
"admin": {
|
||||
@@ -136,11 +142,10 @@
|
||||
"connection": "Connection",
|
||||
"hostDesc": "Public hostname clients will connect to (invalidates config)",
|
||||
"portDesc": "Public UDP port clients will connect to (invalidates config)",
|
||||
"allowedIpsDesc": "Allowed IPs clients will use (invalidates config)",
|
||||
"dns": "DNS",
|
||||
"dnsDesc": "DNS server clients will use (invalidates config)",
|
||||
"mtuDesc": "MTU clients will use (invalidates config)",
|
||||
"persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (invalidates config)"
|
||||
"allowedIpsDesc": "Allowed IPs clients will use (global config)",
|
||||
"dnsDesc": "DNS server clients will use (global config)",
|
||||
"mtuDesc": "MTU clients will use (only for new clients)",
|
||||
"persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (only for new clients)"
|
||||
},
|
||||
"interface": {
|
||||
"cidrSuccess": "Changed CIDR",
|
||||
@@ -193,7 +198,8 @@
|
||||
},
|
||||
"interface": {
|
||||
"cidr": "CIDR",
|
||||
"device": "Device"
|
||||
"device": "Device",
|
||||
"cidrValid": "CIDR must be valid"
|
||||
},
|
||||
"otl": "One Time link",
|
||||
"stringMalformed": "String is malformed",
|
||||
@@ -207,5 +213,11 @@
|
||||
"dns": "DNS",
|
||||
"allowedIps": "Allowed IPs",
|
||||
"file": "File"
|
||||
},
|
||||
"hooks": {
|
||||
"preUp": "PreUp",
|
||||
"postUp": "PostUp",
|
||||
"preDown": "PreDown",
|
||||
"postDown": "PostDown"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wg-easy",
|
||||
"version": "15.0.0-beta.1",
|
||||
"version": "15.0.0-beta.8",
|
||||
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -20,8 +20,8 @@
|
||||
"dependencies": {
|
||||
"@eschricht/nuxt-color-mode": "^1.1.5",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@nuxtjs/i18n": "^9.2.1",
|
||||
"@nuxtjs/tailwindcss": "^6.13.1",
|
||||
"@nuxtjs/i18n": "^9.3.1",
|
||||
"@nuxtjs/tailwindcss": "^6.13.2",
|
||||
"@pinia/nuxt": "^0.10.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"apexcharts": "^4.5.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"is-ip": "^5.0.1",
|
||||
"js-sha256": "^0.11.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"nuxt": "^3.15.4",
|
||||
"nuxt": "^3.16.0",
|
||||
"pinia": "^3.0.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-vue": "^1.9.17",
|
||||
@@ -48,17 +48,17 @@
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "1.1.0",
|
||||
"@nuxt/eslint": "1.2.0",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-prettier": "^10.0.2",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"typescript": "^5.8.2",
|
||||
"vue-tsc": "^2.2.8"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2"
|
||||
"packageManager": "pnpm@10.6.2"
|
||||
}
|
||||
|
||||
Generated
+1225
-1602
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,8 @@ export default defineSetupEventHandler('migrate', async ({ event }) => {
|
||||
clients: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
// not used
|
||||
id: z.string(),
|
||||
// not used, breaks compatibility with older versions
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
address: z.string(),
|
||||
privateKey: z.string(),
|
||||
|
||||
@@ -4,15 +4,19 @@ CREATE TABLE `clients_table` (
|
||||
`name` text NOT NULL,
|
||||
`ipv4_address` text NOT NULL,
|
||||
`ipv6_address` text NOT NULL,
|
||||
`pre_up` text DEFAULT '' NOT NULL,
|
||||
`post_up` text DEFAULT '' NOT NULL,
|
||||
`pre_down` text DEFAULT '' NOT NULL,
|
||||
`post_down` text DEFAULT '' NOT NULL,
|
||||
`private_key` text NOT NULL,
|
||||
`public_key` text NOT NULL,
|
||||
`pre_shared_key` text NOT NULL,
|
||||
`expires_at` text,
|
||||
`allowed_ips` text NOT NULL,
|
||||
`allowed_ips` text,
|
||||
`server_allowed_ips` text NOT NULL,
|
||||
`persistent_keepalive` integer NOT NULL,
|
||||
`mtu` integer NOT NULL,
|
||||
`dns` text NOT NULL,
|
||||
`dns` text,
|
||||
`enabled` integer NOT NULL,
|
||||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
@@ -60,13 +64,12 @@ CREATE TABLE `interfaces_table` (
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `interfaces_table_port_unique` ON `interfaces_table` (`port`);--> statement-breakpoint
|
||||
CREATE TABLE `one_time_links_table` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`id` integer PRIMARY KEY NOT NULL,
|
||||
`one_time_link` text NOT NULL,
|
||||
`expires_at` text NOT NULL,
|
||||
`client_id` integer NOT NULL,
|
||||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
|
||||
FOREIGN KEY (`client_id`) REFERENCES `clients_table`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
FOREIGN KEY (`id`) REFERENCES `clients_table`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `one_time_links_table_one_time_link_unique` ON `one_time_links_table` (`one_time_link`);--> statement-breakpoint
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "b1dde023-d141-4eab-9226-89a832b2ed2b",
|
||||
"id": "8c2af02b-c4bd-4880-a9ad-b38805636208",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"clients_table": {
|
||||
@@ -42,6 +42,38 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_up": {
|
||||
"name": "pre_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_up": {
|
||||
"name": "post_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"pre_down": {
|
||||
"name": "pre_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_down": {
|
||||
"name": "post_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"private_key": {
|
||||
"name": "private_key",
|
||||
"type": "text",
|
||||
@@ -74,7 +106,7 @@
|
||||
"name": "allowed_ips",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"server_allowed_ips": {
|
||||
@@ -102,7 +134,7 @@
|
||||
"name": "dns",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"enabled": {
|
||||
@@ -420,7 +452,7 @@
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
"autoincrement": false
|
||||
},
|
||||
"one_time_link": {
|
||||
"name": "one_time_link",
|
||||
@@ -436,13 +468,6 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"client_id": {
|
||||
"name": "client_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
@@ -470,12 +495,12 @@
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"one_time_links_table_client_id_clients_table_id_fk": {
|
||||
"name": "one_time_links_table_client_id_clients_table_id_fk",
|
||||
"one_time_links_table_id_clients_table_id_fk": {
|
||||
"name": "one_time_links_table_id_clients_table_id_fk",
|
||||
"tableFrom": "one_time_links_table",
|
||||
"tableTo": "clients_table",
|
||||
"columnsFrom": [
|
||||
"client_id"
|
||||
"id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "720d420c-361f-4427-a45b-db0ca613934d",
|
||||
"prevId": "b1dde023-d141-4eab-9226-89a832b2ed2b",
|
||||
"id": "a61263b1-9af1-4d2e-99e9-80d08127b545",
|
||||
"prevId": "8c2af02b-c4bd-4880-a9ad-b38805636208",
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"tables": {
|
||||
@@ -42,6 +42,38 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pre_up": {
|
||||
"name": "pre_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_up": {
|
||||
"name": "post_up",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"pre_down": {
|
||||
"name": "pre_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"post_down": {
|
||||
"name": "post_down",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"private_key": {
|
||||
"name": "private_key",
|
||||
"type": "text",
|
||||
@@ -74,7 +106,7 @@
|
||||
"name": "allowed_ips",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"server_allowed_ips": {
|
||||
@@ -102,7 +134,7 @@
|
||||
"name": "dns",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"enabled": {
|
||||
@@ -420,7 +452,7 @@
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
"autoincrement": false
|
||||
},
|
||||
"one_time_link": {
|
||||
"name": "one_time_link",
|
||||
@@ -436,13 +468,6 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"client_id": {
|
||||
"name": "client_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
@@ -470,11 +495,11 @@
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"one_time_links_table_client_id_clients_table_id_fk": {
|
||||
"name": "one_time_links_table_client_id_clients_table_id_fk",
|
||||
"one_time_links_table_id_clients_table_id_fk": {
|
||||
"name": "one_time_links_table_id_clients_table_id_fk",
|
||||
"tableFrom": "one_time_links_table",
|
||||
"columnsFrom": [
|
||||
"client_id"
|
||||
"id"
|
||||
],
|
||||
"tableTo": "clients_table",
|
||||
"columnsTo": [
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1739266828300,
|
||||
"when": 1741355094140,
|
||||
"tag": "0000_short_skin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1739266837347,
|
||||
"when": 1741355098159,
|
||||
"tag": "0001_classy_the_stranger",
|
||||
"breakpoints": true
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
import { oneTimeLink, user } from '../../schema';
|
||||
|
||||
/** null means use value from userConfig */
|
||||
|
||||
export const client = sqliteTable('clients_table', {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
userId: int('user_id')
|
||||
@@ -14,17 +16,21 @@ export const client = sqliteTable('clients_table', {
|
||||
name: text().notNull(),
|
||||
ipv4Address: text('ipv4_address').notNull().unique(),
|
||||
ipv6Address: text('ipv6_address').notNull().unique(),
|
||||
preUp: text('pre_up').default('').notNull(),
|
||||
postUp: text('post_up').default('').notNull(),
|
||||
preDown: text('pre_down').default('').notNull(),
|
||||
postDown: text('post_down').default('').notNull(),
|
||||
privateKey: text('private_key').notNull(),
|
||||
publicKey: text('public_key').notNull(),
|
||||
preSharedKey: text('pre_shared_key').notNull(),
|
||||
expiresAt: text('expires_at'),
|
||||
allowedIps: text('allowed_ips', { mode: 'json' }).$type<string[]>().notNull(),
|
||||
allowedIps: text('allowed_ips', { mode: 'json' }).$type<string[]>(),
|
||||
serverAllowedIps: text('server_allowed_ips', { mode: 'json' })
|
||||
.$type<string[]>()
|
||||
.notNull(),
|
||||
persistentKeepalive: int('persistent_keepalive').notNull(),
|
||||
mtu: int().notNull(),
|
||||
dns: text({ mode: 'json' }).$type<string[]>().notNull(),
|
||||
dns: text({ mode: 'json' }).$type<string[]>(),
|
||||
enabled: int({ mode: 'boolean' }).notNull(),
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
@@ -38,7 +44,7 @@ export const client = sqliteTable('clients_table', {
|
||||
export const clientsRelations = relations(client, ({ one }) => ({
|
||||
oneTimeLink: one(oneTimeLink, {
|
||||
fields: [client.id],
|
||||
references: [oneTimeLink.clientId],
|
||||
references: [oneTimeLink.id],
|
||||
}),
|
||||
user: one(user, {
|
||||
fields: [client.userId],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { parseCidr } from 'cidr-tools';
|
||||
import { containsCidr, parseCidr } from 'cidr-tools';
|
||||
import { client } from './schema';
|
||||
import type {
|
||||
ClientCreateFromExistingType,
|
||||
@@ -115,8 +115,6 @@ export class ClientService {
|
||||
ipv4Address,
|
||||
ipv6Address,
|
||||
mtu: clientConfig.defaultMtu,
|
||||
allowedIps: clientConfig.defaultAllowedIps,
|
||||
dns: clientConfig.defaultDns,
|
||||
persistentKeepalive: clientConfig.defaultPersistentKeepalive,
|
||||
serverAllowedIps: [],
|
||||
enabled: true,
|
||||
@@ -134,7 +132,27 @@ export class ClientService {
|
||||
}
|
||||
|
||||
update(id: ID, data: UpdateClientType) {
|
||||
return this.#db.update(client).set(data).where(eq(client.id, id)).execute();
|
||||
return this.#db.transaction(async (tx) => {
|
||||
const clientInterface = await tx.query.wgInterface
|
||||
.findFirst({
|
||||
where: eq(wgInterface.name, 'wg0'),
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!clientInterface) {
|
||||
throw new Error('WireGuard interface not found');
|
||||
}
|
||||
|
||||
if (!containsCidr(clientInterface.ipv4Cidr, data.ipv4Address)) {
|
||||
throw new Error('IPv4 address is not within the CIDR range');
|
||||
}
|
||||
|
||||
if (!containsCidr(clientInterface.ipv6Cidr, data.ipv6Address)) {
|
||||
throw new Error('IPv6 address is not within the CIDR range');
|
||||
}
|
||||
|
||||
await tx.update(client).set(data).where(eq(client.id, id)).execute();
|
||||
});
|
||||
}
|
||||
|
||||
async createFromExisting({
|
||||
|
||||
@@ -57,11 +57,15 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
|
||||
expiresAt: expiresAt,
|
||||
ipv4Address: address4,
|
||||
ipv6Address: address6,
|
||||
allowedIps: AllowedIpsSchema,
|
||||
preUp: HookSchema,
|
||||
postUp: HookSchema,
|
||||
preDown: HookSchema,
|
||||
postDown: HookSchema,
|
||||
allowedIps: AllowedIpsSchema.nullable(),
|
||||
serverAllowedIps: serverAllowedIps,
|
||||
mtu: MtuSchema,
|
||||
persistentKeepalive: PersistentKeepaliveSchema,
|
||||
dns: DnsSchema,
|
||||
dns: DnsSchema.nullable(),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { wgInterface } from '../../schema';
|
||||
|
||||
export const hooks = sqliteTable('hooks_table', {
|
||||
/** same as `wgInterface.name` */
|
||||
id: text()
|
||||
.primaryKey()
|
||||
.references(() => wgInterface.name, {
|
||||
|
||||
@@ -6,13 +6,11 @@ export type HooksType = InferSelectModel<typeof hooks>;
|
||||
|
||||
export type HooksUpdateType = Omit<HooksType, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
|
||||
const hook = z.string({ message: t('zod.hook') }).pipe(safeStringRefine);
|
||||
|
||||
export const HooksUpdateSchema = schemaForType<HooksUpdateType>()(
|
||||
z.object({
|
||||
preUp: hook,
|
||||
postUp: hook,
|
||||
preDown: hook,
|
||||
postDown: hook,
|
||||
preUp: HookSchema,
|
||||
postUp: HookSchema,
|
||||
preDown: HookSchema,
|
||||
postDown: HookSchema,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import isCidr from 'is-cidr';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { parseCidr } from 'cidr-tools';
|
||||
import { wgInterface } from './schema';
|
||||
@@ -58,10 +57,18 @@ export class InterfaceService {
|
||||
}
|
||||
|
||||
updateCidr(data: InterfaceCidrUpdateType) {
|
||||
if (!isCidr(data.ipv4Cidr) || !isCidr(data.ipv6Cidr)) {
|
||||
throw new Error('Invalid CIDR');
|
||||
}
|
||||
return this.#db.transaction(async (tx) => {
|
||||
const oldCidr = await tx.query.wgInterface
|
||||
.findFirst({
|
||||
where: eq(wgInterface.name, 'wg0'),
|
||||
columns: { ipv4Cidr: true, ipv6Cidr: true },
|
||||
})
|
||||
.execute();
|
||||
|
||||
if (!oldCidr) {
|
||||
throw new Error('Interface not found');
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(wgInterface)
|
||||
.set(data)
|
||||
@@ -74,8 +81,17 @@ export class InterfaceService {
|
||||
// TODO: optimize
|
||||
const clients = await tx.query.client.findMany().execute();
|
||||
|
||||
const nextIpv4 = nextIP(4, parseCidr(data.ipv4Cidr), clients);
|
||||
const nextIpv6 = nextIP(6, parseCidr(data.ipv6Cidr), clients);
|
||||
// only calculate ip if cidr has changed
|
||||
|
||||
let nextIpv4 = client.ipv4Address;
|
||||
if (data.ipv4Cidr !== oldCidr.ipv4Cidr) {
|
||||
nextIpv4 = nextIP(4, parseCidr(data.ipv4Cidr), clients);
|
||||
}
|
||||
|
||||
let nextIpv6 = client.ipv6Address;
|
||||
if (data.ipv6Cidr !== oldCidr.ipv6Cidr) {
|
||||
nextIpv6 = nextIP(6, parseCidr(data.ipv6Cidr), clients);
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(clientSchema)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { InferSelectModel } from 'drizzle-orm';
|
||||
import z from 'zod';
|
||||
import isCidr from 'is-cidr';
|
||||
import type { wgInterface } from './schema';
|
||||
|
||||
export type InterfaceType = InferSelectModel<typeof wgInterface>;
|
||||
@@ -22,6 +23,7 @@ const device = z
|
||||
const cidr = z
|
||||
.string({ message: t('zod.interface.cidr') })
|
||||
.min(1, { message: t('zod.interface.cidr') })
|
||||
.refine((value) => isCidr(value), { message: t('zod.interface.cidrValid') })
|
||||
.pipe(safeStringRefine);
|
||||
|
||||
export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()(
|
||||
|
||||
@@ -4,12 +4,15 @@ import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { client } from '../../schema';
|
||||
|
||||
export const oneTimeLink = sqliteTable('one_time_links_table', {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
/** same as `client.id` */
|
||||
id: int()
|
||||
.primaryKey()
|
||||
.references(() => client.id, {
|
||||
onDelete: 'cascade',
|
||||
onUpdate: 'cascade',
|
||||
}),
|
||||
oneTimeLink: text('one_time_link').notNull().unique(),
|
||||
expiresAt: text('expires_at').notNull(),
|
||||
clientId: int('client_id')
|
||||
.notNull()
|
||||
.references(() => client.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(CURRENT_TIMESTAMP)`),
|
||||
@@ -21,7 +24,7 @@ export const oneTimeLink = sqliteTable('one_time_links_table', {
|
||||
|
||||
export const oneTimeLinksRelations = relations(oneTimeLink, ({ one }) => ({
|
||||
client: one(client, {
|
||||
fields: [oneTimeLink.clientId],
|
||||
fields: [oneTimeLink.id],
|
||||
references: [client.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -12,7 +12,7 @@ function createPreparedStatement(db: DBType) {
|
||||
create: db
|
||||
.insert(oneTimeLink)
|
||||
.values({
|
||||
clientId: sql.placeholder('id'),
|
||||
id: sql.placeholder('id'),
|
||||
oneTimeLink: sql.placeholder('oneTimeLink'),
|
||||
expiresAt: sql.placeholder('expiresAt'),
|
||||
})
|
||||
@@ -20,7 +20,12 @@ function createPreparedStatement(db: DBType) {
|
||||
erase: db
|
||||
.update(oneTimeLink)
|
||||
.set({ expiresAt: sql.placeholder('expiresAt') as never as string })
|
||||
.where(eq(oneTimeLink.clientId, sql.placeholder('id')))
|
||||
.where(eq(oneTimeLink.id, sql.placeholder('id')))
|
||||
.prepare(),
|
||||
findByOneTimeLink: db.query.oneTimeLink
|
||||
.findFirst({
|
||||
where: eq(oneTimeLink.oneTimeLink, sql.placeholder('oneTimeLink')),
|
||||
})
|
||||
.prepare(),
|
||||
};
|
||||
}
|
||||
@@ -36,6 +41,10 @@ export class OneTimeLinkService {
|
||||
return this.#statements.delete.execute({ id });
|
||||
}
|
||||
|
||||
getByOtl(oneTimeLink: string) {
|
||||
return this.#statements.findByOneTimeLink.execute({ oneTimeLink });
|
||||
}
|
||||
|
||||
generate(id: ID) {
|
||||
const key = `${id}-${Math.floor(Math.random() * 1000)}`;
|
||||
const oneTimeLink = Math.abs(CRC32.str(key)).toString(16);
|
||||
@@ -45,7 +54,7 @@ export class OneTimeLinkService {
|
||||
}
|
||||
|
||||
erase(id: ID) {
|
||||
const expiresAt = Date.now() + 10 * 1000;
|
||||
const expiresAt = new Date(Date.now() + 10 * 1000).toISOString();
|
||||
return this.#statements.erase.execute({ id, expiresAt });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,15 @@ export const UserLoginSchema = z.object({
|
||||
remember: remember,
|
||||
});
|
||||
|
||||
export const UserSetupSchema = z.object({
|
||||
username: username,
|
||||
password: password,
|
||||
});
|
||||
export const UserSetupSchema = z
|
||||
.object({
|
||||
username: username,
|
||||
password: password,
|
||||
confirmPassword: password,
|
||||
})
|
||||
.refine((val) => val.password === val.confirmPassword, {
|
||||
message: t('zod.user.passwordMatch'),
|
||||
});
|
||||
|
||||
const name = z
|
||||
.string({ message: t('zod.user.name') })
|
||||
|
||||
@@ -5,6 +5,7 @@ import { wgInterface } from '../../schema';
|
||||
|
||||
// default* means clients store it themselves
|
||||
export const userConfig = sqliteTable('user_configs_table', {
|
||||
/** same as `wgInterface.name` */
|
||||
id: text()
|
||||
.primaryKey()
|
||||
.references(() => wgInterface.name, {
|
||||
|
||||
@@ -5,20 +5,28 @@ export default defineEventHandler(async (event) => {
|
||||
event,
|
||||
validateZod(OneTimeLinkGetSchema, event)
|
||||
);
|
||||
const clients = await WireGuard.getAllClients();
|
||||
// TODO: filter on the database level
|
||||
const client = clients.find(
|
||||
(client) => client.oneTimeLink?.oneTimeLink === oneTimeLink
|
||||
);
|
||||
|
||||
const otl = await Database.oneTimeLinks.getByOtl(oneTimeLink);
|
||||
if (!otl) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Invalid One Time Link',
|
||||
});
|
||||
}
|
||||
|
||||
const client = await Database.clients.get(otl.id);
|
||||
if (!client) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Invalid One Time Link',
|
||||
});
|
||||
}
|
||||
const clientId = client.id;
|
||||
const config = await WireGuard.getClientConfiguration({ clientId });
|
||||
await Database.oneTimeLinks.erase(clientId);
|
||||
|
||||
const config = await WireGuard.getClientConfiguration({
|
||||
clientId: client.id,
|
||||
});
|
||||
await Database.oneTimeLinks.erase(otl.id);
|
||||
|
||||
setHeader(
|
||||
event,
|
||||
'Content-Disposition',
|
||||
|
||||
@@ -212,8 +212,8 @@ class WireGuard {
|
||||
client.oneTimeLink !== null &&
|
||||
new Date() > new Date(client.oneTimeLink.expiresAt)
|
||||
) {
|
||||
WG_DEBUG(`Client ${client.id} One Time Link expired.`);
|
||||
await Database.oneTimeLinks.delete(client.oneTimeLink.id);
|
||||
WG_DEBUG(`OneTimeLink for Client ${client.id} expired.`);
|
||||
await Database.oneTimeLinks.delete(client.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,11 +222,10 @@ class WireGuard {
|
||||
}
|
||||
|
||||
if (OLD_ENV.PASSWORD || OLD_ENV.PASSWORD_HASH) {
|
||||
// TODO: change url before release
|
||||
throw new Error(
|
||||
`
|
||||
You are using an invalid Configuration for wg-easy
|
||||
Please follow the instructions on https://wg-easy.github.io/wg-easy/ to migrate
|
||||
Please follow the instructions on https://wg-easy.github.io/wg-easy/latest/advanced/migrate/from-14-to-15/ to migrate
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,4 +15,16 @@ export const OLD_ENV = {
|
||||
export const WG_ENV = {
|
||||
/** UI is hosted on HTTP instead of HTTPS */
|
||||
INSECURE: process.env.INSECURE === 'true',
|
||||
/** Port the UI is listening on */
|
||||
PORT: assertEnv('PORT'),
|
||||
};
|
||||
|
||||
function assertEnv<T extends string>(env: T) {
|
||||
const val = process.env[env];
|
||||
|
||||
if (!val) {
|
||||
throw new Error(`Missing environment variable: ${env}`);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
+25
-25
@@ -138,34 +138,27 @@ export const defineMetricsHandler = <
|
||||
handler: MetricsHandler<TReq, TRes>
|
||||
) => {
|
||||
return defineEventHandler(async (event) => {
|
||||
const auth = getHeader(event, 'Authorization');
|
||||
|
||||
if (!auth) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
|
||||
const [method, value] = auth.split(' ');
|
||||
|
||||
if (method !== 'Bearer' || !value) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Bearer Auth required',
|
||||
});
|
||||
}
|
||||
|
||||
const metricsConfig = await Database.general.getMetricsConfig();
|
||||
|
||||
if (metricsConfig[type] !== true) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Metrics not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
if (metricsConfig.password) {
|
||||
const auth = getHeader(event, 'Authorization');
|
||||
|
||||
if (!auth) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
|
||||
const [method, value] = auth.split(' ');
|
||||
|
||||
if (method !== 'Bearer' || !value) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Bearer Auth required',
|
||||
});
|
||||
}
|
||||
|
||||
const tokenValid = await isPasswordValid(value, metricsConfig.password);
|
||||
|
||||
if (!tokenValid) {
|
||||
@@ -176,6 +169,13 @@ export const defineMetricsHandler = <
|
||||
}
|
||||
}
|
||||
|
||||
if (metricsConfig[type] !== true) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Metrics not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
return await handler({ event });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export function template(templ: string, values: Record<string, string>) {
|
||||
* - ipv6Cidr: IPv6 CIDR
|
||||
* - device: Network device
|
||||
* - port: Port number
|
||||
* - uiPort: UI port number
|
||||
*/
|
||||
export function iptablesTemplate(templ: string, wgInterface: InterfaceType) {
|
||||
return template(templ, {
|
||||
@@ -22,5 +23,6 @@ export function iptablesTemplate(templ: string, wgInterface: InterfaceType) {
|
||||
ipv6Cidr: wgInterface.ipv6Cidr,
|
||||
device: wgInterface.device,
|
||||
port: wgInterface.port.toString(),
|
||||
uiPort: WG_ENV.PORT,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ export const FileSchema = z.object({
|
||||
file: z.string({ message: t('zod.file') }),
|
||||
});
|
||||
|
||||
export const HookSchema = z
|
||||
.string({ message: t('zod.hook') })
|
||||
.pipe(safeStringRefine);
|
||||
|
||||
export const schemaForType =
|
||||
<T>() =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -49,16 +49,23 @@ PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`;
|
||||
const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix;
|
||||
const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix;
|
||||
|
||||
const hookLines = [
|
||||
client.preUp ? `PreUp = ${client.preUp}` : null,
|
||||
client.postUp ? `PostUp = ${client.postUp}` : null,
|
||||
client.preDown ? `PreDown = ${client.preDown}` : null,
|
||||
client.postDown ? `PostDown = ${client.postDown}` : null,
|
||||
].filter((v) => v !== null);
|
||||
|
||||
return `[Interface]
|
||||
PrivateKey = ${client.privateKey}
|
||||
Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block}
|
||||
DNS = ${client.dns.join(', ')}
|
||||
DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')}
|
||||
MTU = ${client.mtu}
|
||||
|
||||
${hookLines.length ? `${hookLines.join('\n')}\n` : ''}
|
||||
[Peer]
|
||||
PublicKey = ${wgInterface.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
AllowedIPs = ${client.allowedIps.join(', ')}
|
||||
AllowedIPs = ${(client.allowedIps ?? userConfig.defaultAllowedIps).join(', ')}
|
||||
PersistentKeepalive = ${client.persistentKeepalive}
|
||||
Endpoint = ${userConfig.host}:${userConfig.port}`;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user